Construcția Shaders cu Babylon.js și WebGL teorie și exemple

În cheia pentru ziua 2 din // Build 2014 (vezi 2: 24-2: 28), evangheliștii Microsoft Steven Guggenheimer și John Shewchuk au demonstrat cum a fost adăugat sprijinul lui Oculus Rift la Babylon.js. Și unul dintre lucrurile cheie pentru acest demo a fost lucrarea pe care am făcut-o pe un shader specific pentru simularea lentilelor, după cum puteți vedea în această imagine:

De asemenea, am prezentat o sesiune cu Frank Olivier și Ben Constable despre grafica pe IE și Babylon.js. 

Acest lucru mă duce la una din întrebările pe care oamenii mă întreabă frecvent despre Babylon.js: "Ce vrei să spui prin shader?"Deci, în acest post, vă voi explica cum funcționează shaders și dați câteva exemple de tipuri comune de shaders.

Teoria

Înainte de a începe experimentarea, trebuie să vedem mai întâi cum funcționează lucrurile intern.

Când ne confruntăm cu 3D accelerat hardware, discutăm două CPU-uri: CPU principal și GPU. GPU-ul este un fel de CPU extrem de specializat.

GPU-ul este o mașină de stat pe care o configurați utilizând CPU-ul. De exemplu, CPU-ul va configura GPU-ul pentru a face linii în loc de triunghiuri. Sau va defini că transparența este activă și așa mai departe.

Odată ce toate stările sunt setate, CPU-ul va defini ce trebuie făcut - geometria, care este compusă dintr-o listă de puncte (numită noduri și stocate într-o matrice numită vertex tampon) și o listă a indexurilor (fețele sau triunghiurile stocate într-un matrice numită index tampon).

Ultimul pas pentru CPU este definirea modului de redare a geometriei, iar pentru această sarcină specifică, CPU-ul va defini shader pentru GPU. Shaderele sunt o bucată de cod pe care GPU-ul o va executa pentru fiecare dintre vârfurile și pixelii pe care trebuie să le facă.

În primul rând, un anumit vocabular: gândiți-vă la un vertex (vârfuri când există mai multe dintre ele) ca un "punct" într-un mediu 3D (spre deosebire de un punct într-un mediu 2D).

Există două tipuri de shadere: shadere de vârf și shadere pixel (sau fragmente).

Conductă grafică

Înainte să sapi în shadere, hai să facem un pas înapoi. Pentru a face pixeli, GPU-ul va lua geometria definită de CPU și va face următoarele:

Folosind tamponul index, trei noduri sunt adunate pentru a defini un triunghi: tamponul index conține o listă de indexuri de vârfuri. Aceasta înseamnă că fiecare intrare din indexul tampon este numărul unui vertex din tamponul de vârf. Acest lucru este foarte util pentru a evita suprapunerea nodurilor. 

De exemplu, următoarea tampon index este o listă cu două fețe: [1 2 3 1 3 4]. Prima față conține vertex 1, vertex 2 și vertex 3. A doua față conține vertex 1, vertex 3 și vertex 4. Deci, există patru vârfuri în această geometrie: 

Shader-ul de vârfuri este aplicat pe fiecare vârf al triunghiului. Scopul principal al shader-ului de vertex este de a produce un pixel pentru fiecare vertex (proiecția pe ecranul 2D al vertexului 3D): 

Folosind acești trei pixeli (care definesc un triunghi 2D pe ecran) GPU va interpola toate valorile atașate la pixel (cel puțin poziția sa), iar pixelul shader va fi aplicat pe fiecare pixel inclus în triunghiul 2D pentru a generați o culoare pentru fiecare pixel: 

Acest proces se face pentru fiecare față definită de tampon index. 

Evident, datorita naturii sale paralele, GPU-ul este capabil sa proceseze acest pas pentru o multime de fete simultan, si astfel sa obtina o performanta foarte buna.

GLSL

Tocmai am văzut că pentru a face triunghiuri, GPU are nevoie de două shadere: shader-ul de vârfuri și shader-ul pixelilor. Aceste shadere sunt scrise folosind o limbă numită GLSL (Graphics Library Shader Language). Se pare că C.

Pentru Internet Explorer 11, am dezvoltat un compilator pentru a transforma GLSL în HLSL (High Level Shader Language), care este limbajul shader al DirectX 11. Aceasta permite IE11 să se asigure că codul shader este sigur (nu doriți să utilizați WebGL pentru a reseta calculatorul!):

Iată o mostră a unui shader de vârf comun:

precizie highp float; // Atribute atribut vec3 position; atribut vec2 uv; Uniforme uniforme mat4 worldViewProjection; // variază variabilă vec2 vUV; void principal (void) gl_Pozi = lumeViewProjecție * vec4 (poziție, 1.0); vUV = uv; 

Vertex Shader Structure

Un shader de vertex conține următoarele:

  • Atribute: Un atribut definește o porțiune a unui vârf. Implicit, un vârf ar trebui să conțină cel puțin o poziție (a vector3: x, y, z). Dar, ca dezvoltator, puteți decide să adăugați mai multe informații. De exemplu, în fostul shader, există a vector2 numit uv (coordonatele texturilor care ne permit să aplicăm o textură 2D pe un obiect 3D).
  • Uniforme: O uniformă este o variabilă folosită de shader și definită de CPU. Singura uniformă pe care o avem aici este o matrice folosită pentru a proiecta poziția vârfului (x, y, z) pe ecran (X y).
  • Variabil: Variabile variabile sunt valori create de shader-ul de vârf și transmise la shader-ul pixelului. Aici, shaderul de vertex va transmite a VUV (o copie simplă a lui uv) pentru pixel shader. Aceasta înseamnă că aici este definit un pixel cu coordonate de poziție și textură. Aceste valori vor fi interpolate de unitatea de procesare grafică și vor fi utilizate de shaderul pixelilor. 
  • principal: Funcția numită principal() este codul executat de GPU pentru fiecare vârf și trebuie să producă cel puțin o valoare pentru gl_position (poziția de pe ecran a vârfului curent). 

Putem vedea în eșantionul nostru că shaderul de vertex este destul de simplu. Acesta generează o variabilă de sistem (începând cu gl_) numit gl_position pentru a defini poziția pixelului asociat și stabilește o variabilă variabilă numită VUV

Voodoo în spatele matricelor

În shaderul nostru avem o matrice numită worldViewProjection. Utilizăm această matrice pentru a proiecta poziția vârfului la gl_position variabil. Este cool, dar cum obținem valoarea acestei matrice? Este o uniformă, deci trebuie să o definim pe partea CPU (folosind JavaScript).

Aceasta este una dintre părțile complexe de a face 3D. Trebuie să înțelegeți matematica complexă (sau va trebui să utilizați un motor 3D, cum ar fi Babylon.js, pe care o vom vedea mai târziu).

worldViewProjection matricea este combinația a trei matrici diferite:

Folosind matricea rezultată ne permite să putem transforma vârfurile 3D în pixeli 2D luând în considerare punctul de vedere și tot ceea ce se referă la poziția / scala / rotația obiectului curent.

Aceasta este responsabilitatea dvs. ca dezvoltator 3D: să creați și să păstrați această matrice actualizată.

Înapoi la Shaders

Odată ce shaderul de vârfuri este executat pe fiecare vertex (de trei ori, atunci) avem trei pixeli cu o valoare corectă gl_position și a VUV valoare. GPU-ul va interpola aceste valori pe fiecare pixel conținut în triunghiul produs de acești pixeli.

Apoi, pentru fiecare pixel, acesta va executa pixel shader:

precizie highp float; variabile vec2 vUV; uniform sampler2D textureSampler; void principal (void) gl_FragColor = textură2D (texturăSampler, vUV); 

Structura Shader Pixel (sau Fragment)

Structura unui shader pixel este similară cu un shader de vârf:

  • Variabil: Variabile variabile sunt valori create de shader-ul de vârf și transmise la shader-ul pixelului. Aici shaderul de pixeli va primi a VUV valoare de la shaderul de vârfuri. 
  • Uniforme: O uniformă este o variabilă folosită de shader și definită de CPU. Singura uniformă pe care o avem aici este un eșantionator, care este o unealtă folosită pentru a citi culorile texturilor.
  • principal: Funcția numită principal este codul executat de GPU pentru fiecare pixel și trebuie să producă cel puțin o valoare pentru gl_FragColor (culoarea pixelului curent). 

Acest shader pixel este destul de simplu: citește culoarea din textura folosind coordonatele de textură din shaderul de vârfuri (care la rândul său a ajuns de la vârf).

Vrei să vezi rezultatul unui astfel de shader? Aici este:

Acest lucru este redat în timp real; puteți glisa sfera cu mouse-ul.

Pentru a obține acest rezultat, va trebui să faceți față unui rezultat mult din codul WebGL. Într-adevăr, WebGL este un API foarte puternic, dar într-adevăr la un nivel scăzut, și trebuie să faci totul singur, de la crearea buffer-urilor la definirea structurilor de vârfuri. De asemenea, trebuie să faceți tot matematica și să setați toate stările și să vă ocupați de încărcarea texturilor și așa mai departe ...

Prea greu? BABYLON.ShaderMaterial pentru salvare

Știu ce vă gândiți: shaderele sunt foarte cool, dar nu vreau să mă deranjez cu sistemele sanitare interne WebGL sau chiar cu matematica.

Și e bine! Aceasta este o întrebare perfect legitimă, și tocmai de aceea am creat Babylon.js.

Permiteți-mi să vă prezint codul utilizat de demonstrația sferică precedentă. În primul rând, veți avea nevoie de o pagină web simplă:

   Babylon.js          

Veți observa că shaderele sunt definite de > Etichete. Cu Babylon.js le puteți defini și în fișiere separate (.fx fișiere).

Puteți obține Babylon.js aici sau pe repo GitHub. Trebuie să utilizați versiunea 1.11 sau o versiune superioară pentru a avea acces la BABYLON.StandardMaterial.

În final, codul principal JavaScript este următorul:

"folosiți stricte"; document.addEventListener ("DOMContentLoaded", startGame, false); funcția startGame () if (BABYLON.Engine.isSupported ()) var canvas = document.getElementById ("renderCanvas"); var motor = nou BABYLON.Engine (canvas, false); var scena = nou BABYLON.Scene (motor); var camera = nou BABYLON.ArcRotateCamera ("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero (), scenă); camera.attachControl (pânză); // Crearea sferei var sphere = BABYLON.Mesh.CreateSphere ("Sphere", 16, 5, scenă); var amigaMaterial = nou BABYLON.ShaderMaterial ("amiga", scena, vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode",, atribute: ["worldViewProjection" ); amigaMaterial.setTexture ("textureSampler", noul BABYLON.Texture ("amiga.jpg", scena)); sphere.material = amigaMaterial; engine.runRenderLoop (funcția () sphere.rotation.y + = 0.05; scena.render ();); ;

Puteți vedea că folosesc a BABYLON.ShaderMaterial pentru a scăpa de toată povara compilării, legării și manipulării shaderelor.

Când creați o BABYLON.ShaderMaterial, trebuie să specificați elementul DOM utilizat pentru a stoca shaderele sau numele de bază al fișierelor în care sunt shaderele. Dacă alegeți să utilizați fișiere, trebuie să creați un fișier pentru fiecare shader și să utilizați următorul model de fișier: basename.vertex.fx și basename.fragment.fx. Apoi, va trebui să creați materialul astfel:

var cloudMaterial = nou BABYLON.ShaderMaterial ("cloud", scena, "./myShader", atribute: ["position", "uv"], uniforme: ["worldViewProjection"]);

De asemenea, trebuie să specificați numele oricăror atribute și uniforme pe care le utilizați. Apoi, puteți seta direct valoarea uniformelor și a eșantioanelor folosind butonul setTexture, setFloat, setFloats, setColor3, setColor4, setVector2, setVector3, setVector4, și setMatrix funcții.

Destul de simplu, corect?

Îți amintești de cele anterioare worldViewProjection matrice? Utilizând Babylon.js și BABYLON.ShaderMaterial, nu aveți de ce să vă faceți griji! BABYLON.ShaderMaterial va calcula automat pentru tine, deoarece îl declarați în lista uniformelor.

BABYLON.ShaderMaterial se pot ocupa și de următoarele matrici pentru dvs.:

  • lume 
  • vedere 
  • proeminență 
  • WorldView 
  • worldViewProjection 

Nu mai este nevoie de matematică. De exemplu, de fiecare dată când executați sphere.rotation.y + = 0,05, matricea globală a sferei este generată pentru dvs. și transmisă GPU-ului.

CYOS: Creați propriul Shader

Deci, să mergem mai mari și să creăm o pagină în care să puteți crea dinamic propriile shadere și să vedeți rezultatul imediat. Această pagină va folosi același cod pe care l-am discutat anterior și va folosi a BABYLON.ShaderMaterial obiecte pentru a compila și a executa shadere pe care le veți crea.

Am folosit editorul de cod ACE pentru CYOS. Acesta este un editor incredibil de coduri cu indicatoare de sintaxă. Simțiți-vă liber să aruncați o privire aici. Puteți găsi CYOS aici.

Utilizând prima casetă combo, veți putea selecta shadere predefinite. Vom vedea pe fiecare dintre ei imediat după.

De asemenea, puteți schimba plasa (obiectul 3D) utilizată pentru a vă uita shaderele utilizând a doua casetă combo.

Compila butonul este utilizat pentru a crea un nou BABYLON.ShaderMaterial de la shadere. Codul utilizat de acest buton este următorul: 

// Compilează shaderMaterial = nou BABYLON.ShaderMaterial ("shader", scena, vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", atribute: ["lume", "worldview", "worldViewProjection"]); var refTexture = nou BABYLON.Texture ("ref.jpg", scenă); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = nou BABYLON.Texture ("amiga.jpg", scenă); shaderMaterial.setTexture ("textureSampler", amigaTexture); shaderMaterial.setTexture ("refSampler", refTexture); shaderMaterial.setFloat ("timp", 0); shaderMaterial.setVector3 ("CameraPosition", BABYLON.Vector3.Zero ()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;

Brut simplu, nu? Materialul este gata să vă trimită trei matrice precompute (lume, WorldView și worldViewProjection). Vitezele vor veni cu coordonate de poziție, normale și textură. Două texturi sunt deja încărcate pentru dvs.:

amiga.jpgref.jpg

Și în cele din urmă, iată renderLoop unde actualizez două uniforme convenabile:

  • unul a sunat timp pentru a obține niște animații amuzante 
  • unul a sunat cameraPosition pentru a obține poziția camerei în shadere (care va fi utilă pentru ecuațiile de iluminare) 
motor.runRenderLoop (functie () mesh.rotation.y + = 0.001; daca (shaderMaterial) shaderMaterial.setFloat ("time", time); time + = 0.02; shaderMaterial.setVector3 ; scena.render (););

Datorită muncii pe care am făcut-o pe Windows Phone 8.1, puteți utiliza, de asemenea, CYOS pe Windows Phone (este întotdeauna un moment bun pentru a crea un shader):

Shader de bază

Deci, să începem cu primul shader definit pe CYOS: Shader-ul de bază.

Știm deja acest shader. Se calculează gl_position și folosește coordonatele texturilor pentru a obține o culoare pentru fiecare pixel.

Pentru a calcula poziția pixelului, avem nevoie doar de worldViewProjection matricea și poziția vârfului:

precizie highp float; // Atribute atribut vec3 position; atribut vec2 uv; Uniforme uniforme mat4 worldViewProjection; // variază variabilă vec2 vUV; void principal (void) gl_Pozi = lumeViewProjecție * vec4 (poziție, 1.0); vUV = uv; 

Coordonatele texturilor (uv) sunt transmise nemodificate la shaderul pixelilor.

Rețineți că trebuie să adăugăm precizie, cu capăt mediu; pe prima linie pentru shaderele de vârf și pixeli, deoarece Chrome o cere. Acesta definește faptul că, pentru o performanță mai bună, nu folosim valori plutitoare de precizie completă.

Shader-ul pixelului este chiar mai simplu, deoarece trebuie doar să folosim coordonatele texturii și să obținem o culoare textura:

precizie highp float; variabile vec2 vUV; uniform sampler2D textureSampler; void principal (void) gl_FragColor = textură2D (texturăSampler, vUV); 

Am văzut anterior că textureSampler uniforma este umplută cu textura "amiga", deci rezultatul este următorul:

Shader alb-negru

Acum să continuăm cu un nou shader: shader alb-negru.

Scopul acestui shader este acela de a folosi cel anterior, dar cu un mod de redare "alb-negru". Pentru a face acest lucru, putem păstra același shader de vârfuri, dar shaderul pixelilor trebuie să fie ușor modificat.

Prima opțiune pe care o avem este să luăm doar o componentă, cum ar fi cea verde:

precizie highp float; variabile vec2 vUV; uniform sampler2D textureSampler; void principal (void) gl_FragColor = vec4 (textura2D (textureSampler, vUV) .ggg, 1.0); 

După cum puteți vedea, în loc să le folosiți .rgb (această operație este numită a swizzle), noi am folosit .ggg.

Dar dacă vrem un efect alb-negru cu adevărat precis, ar fi o idee mai bună de a calcula luminanța (care ține cont de toate componentele de culoare):

precizie highp float; variabile vec2 vUV; uniform sampler2D textureSampler; void principal (void) float luminanță = punct (textură2D (texturăSampler, vUV) .rgb, vec3 (0,3, 0,59, 0,11)); gl_FragColor = vec4 (luminanță, luminanță, luminanță, 1.0); 

Funcția dot (sau produsul punct) este calculată astfel:

rezultatul = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z

Deci în cazul nostru:

luminanță = r * 0,3 + g * 0,59 + b * 0,11 (aceste valori se bazează pe faptul că ochiul uman este mai sensibil la verde)

Sună rece, nu-i așa??

Shader de Shading al celulelor

Acum, hai să ne mutăm la un shader mai complex: shaderul de umbrire a celulelor.

Aceasta ne va cere să obținem punctul normal al vârfului și poziția vârfului în shaderul pixelilor. Deci, shaderul de vertex va arata astfel:

precizie highp float; // Atribute atribut vec3 position; atributul vec3 normal; atribut vec2 uv; Uniforme uniforme mat4 lume; uniform mat4 worldViewProjection; // variază variabil vec3 vPositionW; variind vec3 vNormalW; variabile vec2 vUV; void principal (void) vec4 outPoziție = lumeViewProjecție * vec4 (poziție, 1.0); gl_Position = outPosition; vPositionW = vec3 (lume * vec4 (poziție, 1.0)); vNormalW = normaliza (vec3 (lumea * vec4 (normal, 0.0))); vUV = uv; 

Rețineți că folosim și matricea mondială, deoarece poziția și normalul sunt stocate fără nici o transformare și trebuie să aplicăm matricea mondială pentru a ține cont de rotația obiectului.

Shader-ul pixelului este următorul:

precizie highp float; // Lumini variind vec3 vPositionW; variind vec3 vNormalW; variabile vec2 vUV; // Refs uniform sampler2D textureSampler; void principal (void) float ToonThresholds [4]; ToonThresholds [0] = 0,95; ToonThresholds [1] = 0,5; ToonThresholds [2] = 0,2; ToonThresholds [3] = 0,03; flotați ToonBrightnessLevels [5]; ToonBrightnessLevels [0] = 1,0; ToonBrightnessLevels [1] = 0,8; ToonBrightnessLevels [2] = 0,6; ToonBrightnessLevels [3] = 0,35; ToonBrightnessLevels [4] = 0,2; vec3 vLightPosition = vec3 (0, 20, 10); // Lumina vec3 lightVectorW = normaliza (vLightPosition - vPositionW); // float difuze ndl = max (0., punct (vNormalW, lightVectorW)); vec3 culoare = textură2D (textureSampler, vUV) .rgb; dacă (ndl> ToonThresholds [0]) color * = ToonBrightnessLevels [0];  altfel dacă (ndl> ToonThresholds [1]) color * = ToonBrightnessLevels [1];  altfel dacă (ndl> ToonThresholds [2]) color * = ToonBrightnessLevels [2];  altfel dacă (ndl> ToonThresholds [3]) color * = ToonBrightnessLevels [3];  altceva color * = ToonBrightnessLevels [4];  gl_FragColor = vec4 (culoare, 1.); 

Scopul acestui shader este de a simula o lumină și, în loc de a calcula o umbrire netedă, vom considera că lumina se va aplica în funcție de pragurile de luminozitate specifice. De exemplu, dacă intensitatea luminii este între 1 (maxim) și 0,95, culoarea obiectului (extras din textură) va fi aplicată direct. Dacă intensitatea este între 0,95 și 0.5, culoarea va fi atenuată cu un factor de 0,8, si asa mai departe.

Deci, există în principal patru pași în acest shader:

  • În primul rând, declarăm praguri și niveluri constante.
  • Apoi, trebuie să calculam iluminarea folosind ecuația Phong (presupunem că lumina nu se mișcă): 
vec3 vLightPosition = vec3 (0, 20, 10); // Lumina vec3 lightVectorW = normaliza (vLightPosition - vPositionW); // float difuze ndl = max (0., punct (vNormalW, lightVectorW));

Intensitatea luminii pe pixel depinde de unghiul dintre direcția normală și cea a luminii.

  • Apoi vom obține culoarea texturii pentru pixel.
  • În cele din urmă, verificăm pragul și aplicăm nivelul culorii.

Rezultatul arată ca un obiect de desene animate: 

Phong Shader

Am folosit o porțiune din ecuația Phong în shaderul precedent. Deci, să încercăm să folosim totul acum.

Shader-ul de vârfuri este clar simplu aici, deoarece totul se va face în pixel shader:

precizie highp float; // Atribute atribut vec3 position; atributul vec3 normal; atribut vec2 uv; Uniforme uniforme mat4 worldViewProjection; // variază variabilă vec3 vPosition; variind vec3 vNormal; variabile vec2 vUV; void principal (void) vec4 outPoziție = lumeViewProjecție * vec4 (poziție, 1.0); gl_Position = outPosition; vUV = uv; vPosition = poziție; vNormal = normal; 

Conform ecuației, trebuie să calculați componenta difuză și speculară folosind direcția luminii și punctul normal al vârfului:

precizie highp float; // variază variabilă vec3 vPosition; variind vec3 vNormal; variabile vec2 vUV; Uniforme uniforme mat4 lume; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void principală (void) vec3 vLightPosition = vec3 (0, 20, 10); // Valorile lumii vec3 vPositionW = vec3 (lumea * vec4 (vPosition, 1.0)); vec3 vNormalW = normaliza (vec3 (lumea * vec4 (vNormal, 0.0))); vec3 viewDirectionW = normalizează (cameraPosition - vPositionW); // Lumina vec3 lightVectorW = normaliza (vLightPosition - vPositionW); vec3 culoare = textură2D (textureSampler, vUV) .rgb; // float difuze ndl = max (0., punct (vNormalW, lightVectorW)); // unghiul Spec vec3W = normalizează (viewDirectionW + lightVectorW); float specComp = max (0., punct (vNormalW, angleW)); specComp = pow (specComp, max (1, 64)) * 2; gl_FragColor = vec4 (culoarea * ndl + vec3 (specComp), 1.); 

Am folosit deja partea difuză în shaderul anterior, deci aici trebuie doar să adăugăm partea speculară. Această imagine dintr-un articol Wikipedia explică cum funcționează shaderul:

De Brad Smith aka Rainwarrior.

Rezultatul asupra sferei noastre:

Aruncați Shader

Pentru shaderul aruncat, aș dori să introduc un nou concept: renunța cuvinte cheie. Acest shader va elimina orice pixel non-roșu și va crea iluzia unui obiect "săpat".

Shader-ul de vârfuri este același cu cel folosit de shader-ul de bază:

precizie highp float; // Atribute atribut vec3 position; atributul vec3 normal; atribut vec2 uv; Uniforme uniforme mat4 worldViewProjection; // variază variabilă vec2 vUV; void principal (void) gl_Pozi = lumeViewProjecție * vec4 (poziție, 1.0); vUV = uv; 

Shader-ul pixelilor va trebui să testeze culoarea și utilizarea renunța când, de exemplu, componenta verde este prea mare:

precizie highp float; variabile vec2 vUV; // Refs uniform sampler2D textureSampler; void principal (void) vec3 culoare = textură2D (textureSampler, vUV) .rgb; dacă (color.g> 0.5) aruncă;  gl_FragColor = vec4 (culoare, 1.); 

Rezultatul este amuzant:

Wave Shader

Am jucat mult cu shadere pixeli, dar am vrut, de asemenea, sa va arat ca putem face multe lucruri cu shader-urile de vertex.

Pentru shaderul valurilor, vom reuși să folosim shaderul de pixeli Phong.

Shaderul de vârfuri va folosi uniforma numită timp pentru a obține niște valori animate. Folosind această uniformă, shaderul va genera un val cu pozițiile nodurilor:

precizie highp float; // Atribute atribut vec3 position; atributul vec3 normal; atribut vec2 uv; Uniforme uniforme mat4 worldViewProjection; timpul de flotare uniform; // variază variabilă vec3 vPosition; variind vec3 vNormal; variabile vec2 vUV; void principal (void) vec3 v = poziție; v.x + = sin (2.0 * poziție.y + (timp)) * 0.5; gl_Position = proiectul worldViewProjection * vec4 (v, 1.0); vPosition = poziție; vNormal = normal; vUV = uv; 

Se aplică un sinus position.y, iar rezultatul este următorul:

Cartografierea mediului sferic

Asta a fost în mare măsură inspirat de acest tutorial. Vă las să citiți acel articol excelent și să jucați cu shader asociat. 

Fresnel Shader

Aș dori să termin acest articol cu ​​favoritul meu: shaderul Fresnel.

Acest shader este folosit pentru a aplica o intensitate diferită în funcție de unghiul dintre direcția vizuală și cea normală a vârfului.

Shaderul de vârfuri este același cu cel utilizat de umbra de umbrire a celulei și putem calcula cu ușurință termenul Fresnel în shaderul pixelului nostru (deoarece avem poziția normală și cea a camerei, care poate fi utilizată pentru a evalua direcția de vizualizare):

precizie highp float; // Lumini variind vec3 vPositionW; variind vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void principal (void) vec3 culoare = vec3 (1, 1, 1); vec3 viewDirectionW = normalizează (cameraPosition - vPositionW); // Fresnel float fresnelTerm = punct (vedereDirecțieW, vNormalW); fresnelTerm = clemă (1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4 (culoare * fresnelTerm, 1.); 

Shaderul tău?

Acum sunteți mai pregătit să vă creați propriul shader. Simțiți-vă liber să utilizați comentariile aici sau pe forumul Babylon.js pentru a vă împărtăși experiențele!

Dacă doriți să mergeți mai departe, iată câteva linkuri utile:

  • Babylon.js repo 
  • Forumul Babylon.js 
  • CYOS
  • GLSL pe Wikipedia 
  • Documentația GLSL

Și mai multe învățări pe care le-am creat pe această temă:

  • Introducere în WebGL 3D cu HTML5 și Babylon.JS
  • Cutting Edge Graphics în HTML

Sau, întoarcerea, seria de învățare a echipei noastre pe JavaScript: 

  • Sfaturi practice de performanță pentru a vă face HTML / JavaScript mai rapid (o serie de șapte părți de la design receptiv la jocuri casual la optimizarea performanțelor)
  • Platforma Modernă de Platformă Web Jump Start (fundamentele HTML, CSS și JS)
  • Dezvoltarea aplicației Windows Universal cu cod HTML și JavaScript Jump Start (utilizați JS pe care l-ați creat deja pentru a crea o aplicație)

Și, bineînțeles, sunteți întotdeauna bineveniți să utilizați câteva dintre instrumentele noastre gratuite pentru a vă construi următoarea experiență web: Visual Studio Community, Azure Trial și instrumente de testare cross-browser pentru Mac, Linux sau Windows.

Acest articol face parte din seriile de tehnologie web dev din Microsoft. Suntem încântați să împărtășim Microsoft Edge și noul EdgeHTML motor de randare cu tine. Obțineți mașini virtuale gratuite sau testați de la distanță pe dispozitivele Mac, iOS, Android sau Windows @ http://dev.modern.ie/.