Costruire shader con Babylon.js e WebGL teoria ed esempi

Nel keynote del Day 2 di // Build 2014 (vedi 2: 24-2: 28), gli evangelisti Microsoft Steven Guggenheimer e John Shewchuk hanno dimostrato come il supporto Oculus Rift è stato aggiunto a Babylon.js. E una delle cose fondamentali per questa demo è stato il lavoro che abbiamo fatto su uno shader specifico per simulare gli obiettivi, come puoi vedere in questa immagine:

Ho anche presentato una sessione con Frank Olivier e Ben Constable sulla grafica su IE e Babylon.js. 

Questo mi porta a una delle domande che mi vengono poste spesso su Babylon.js: "Cosa intendi con shaders?"Quindi, in questo post, ti spiegherò come funzionano gli shader e fornirò alcuni esempi di tipi comuni di shader.

La teoria

Prima di iniziare a sperimentare, dobbiamo prima vedere come funzionano le cose internamente.

Quando si tratta di 3D con accelerazione hardware, stiamo discutendo di due CPU: la CPU principale e la GPU. La GPU è una specie di CPU estremamente specializzata.

La GPU è una macchina a stati che hai impostato usando la CPU. Ad esempio, la CPU configurerà la GPU per il rendering delle linee anziché dei triangoli. O definirà che la trasparenza è attiva e così via.

Una volta impostati tutti gli stati, la CPU definirà cosa renderizzare - la geometria, che è composta da un elenco di punti (chiamato il vertici e memorizzato in un array chiamato buffer dei vertici), e una lista di indici (le facce, o triangoli, memorizzati in una matrice chiamata buffer di indice).

Il passaggio finale per la CPU è definire come eseguire il rendering della geometria e, per questo specifico compito, la CPU definirà shaders per la GPU. Gli shader sono una parte di codice che verrà eseguita dalla GPU per ciascuno dei vertici e dei pixel che deve rappresentare.

Innanzitutto, alcuni vocaboli: pensate a un vertice (i vertici quando ce ne sono molti) come "punto" in un ambiente 3D (al contrario di un punto in un ambiente 2D).

Esistono due tipi di shader: vertex shader e pixel (o frammenti) shader.

Pipeline grafica

Prima di scavare negli shader, facciamo un passo indietro. Per rendere i pixel, la GPU prenderà la geometria definita dalla CPU e farà quanto segue:

Utilizzando il buffer indice, vengono raccolti tre vertici per definire un triangolo: il buffer indice contiene un elenco di indici vertici. Ciò significa che ogni voce nel buffer dell'indice è il numero di un vertice nel buffer del vertice. Questo è davvero utile per evitare la duplicazione dei vertici. 

Ad esempio, il seguente indice buffer è un elenco di due facce: [1 2 3 1 3 4]. La prima faccia contiene il vertice 1, il vertice 2 e il vertice 3. La seconda faccia contiene il vertice 1, il vertice 3 e il vertice 4. Quindi ci sono quattro vertici in questa geometria: 

Lo shader del vertice viene applicato su ciascun vertice del triangolo. L'obiettivo principale del vertex shader è produrre un pixel per ogni vertice (la proiezione sullo schermo 2D del vertice 3D): 

Usando questi tre pixel (che definiscono un triangolo 2D sullo schermo), la GPU interpolerà tutti i valori collegati al pixel (almeno la sua posizione), e il pixel shader verrà applicato su ogni pixel incluso nel triangolo 2D per genera un colore per ogni pixel: 

Questo processo viene eseguito per ogni faccia definita dal buffer dell'indice. 

Ovviamente, a causa della sua natura parallela, la GPU è in grado di elaborare questo passaggio per un sacco di volti contemporaneamente, ottenendo così ottime prestazioni.

GLSL

Abbiamo appena visto che per rendere i triangoli, la GPU ha bisogno di due shader: il vertex shader e il pixel shader. Questi shader sono scritti usando un linguaggio chiamato GLSL (Graphics Library Shader Language). Sembra C.

Per Internet Explorer 11, abbiamo sviluppato un compilatore per trasformare GLSL in HLSL (High Level Shader Language), che è il linguaggio shader di DirectX 11. Ciò consente a IE11 di garantire che il codice dello shader sia sicuro (non si desidera utilizzare WebGL per ripristinare il tuo computer!):

Ecco un esempio di uno shader vertex comune:

highp float di precisione; // Attributi attributo posizione vec3; attributo vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Variabile vec2 vUV variabile; void main (void) gl_Position = worldViewProjection * vec4 (position, 1.0); vUV = uv; 

Struttura dello shader del vertice

Un vertex shader contiene quanto segue:

  • attributi: Un attributo definisce una parte di un vertice. Di default un vertice dovrebbe contenere almeno una posizione (a vector3: x, y, z). Ma come sviluppatore, puoi decidere di aggiungere ulteriori informazioni. Ad esempio, nel precedente shader, c'è un Vector2 di nome uv (coordinate di trama che ci permettono di applicare una texture 2D su un oggetto 3D).
  • divise: Una divisa è una variabile utilizzata dallo shader e definita dalla CPU. L'unica uniforme che abbiamo qui è una matrice usata per proiettare la posizione del vertice (x, y, z) sullo schermo (x, y).
  • Variando: Le variabili variabili sono valori creati dal vertex shader e trasmessi al pixel shader. Qui, il vertex shader trasmetterà a VUV (una semplice copia di uv) valore per il pixel shader. Ciò significa che qui viene definito un pixel con coordinate di posizione e trama. Questi valori saranno interpolati dalla GPU e usati dal pixel shader. 
  • principale: La funzione denominata principale() è il codice eseguito dalla GPU per ogni vertice e deve almeno produrre un valore per GL_POSITION (la posizione sullo schermo del vertice corrente). 

Possiamo vedere nel nostro esempio che il vertex shader è piuttosto semplice. Genera una variabile di sistema (a partire da gl_) di nome GL_POSITION per definire la posizione del pixel associato e imposta una variabile variabile chiamata VUV

Le matrici Voodoo Behind

Nel nostro shader abbiamo una matrice chiamata worldViewProjection. Usiamo questa matrice per proiettare la posizione del vertice al GL_POSITION variabile. È fantastico, ma come possiamo ottenere il valore di questa matrice? È una divisa, quindi dobbiamo definirlo dal lato CPU (usando JavaScript).

Questa è una delle parti complesse del fare 3D. Devi capire matematica complessa (o dovrai usare un motore 3D, come Babylon.js, che vedremo in seguito).

Il worldViewProjection matrix è la combinazione di tre matrici differenti:

L'utilizzo della matrice risultante ci consente di essere in grado di trasformare i vertici 3D in pixel 2D tenendo conto del punto di vista e di tutto ciò che riguarda la posizione / scala / rotazione dell'oggetto corrente.

Questa è la tua responsabilità come sviluppatore 3D: per creare e mantenere aggiornata questa matrice.

Torna agli shaders

Una volta che il vertex shader viene eseguito su ogni vertice (tre volte, quindi) abbiamo tre pixel con un corretto GL_POSITION e a VUV valore. La GPU quindi interpolerà questi valori su ogni pixel contenuto nel triangolo prodotto da questi pixel.

Quindi, per ogni pixel, eseguirà il pixel shader:

highp float di precisione; vec2 vUV variabile; sampler2D textureSampler uniforme; void main (void) gl_FragColor = texture2D (textureSampler, vUV); 

Struttura dello shader pixel (o frammento)

La struttura di un pixel shader è simile a un vertex shader:

  • Variando: Le variabili variabili sono valori creati dal vertex shader e trasmessi al pixel shader. Qui il pixel shader riceverà a VUV valore dal vertex shader. 
  • divise: Una divisa è una variabile utilizzata dallo shader e definita dalla CPU. L'unica uniforme che abbiamo qui è un campionatore, che è uno strumento utilizzato per leggere i colori della trama.
  • principale: La funzione denominata principale è il codice eseguito dalla GPU per ciascun pixel e deve almeno produrre un valore per gl_FragColor (il colore del pixel corrente). 

Questo pixel shader è abbastanza semplice: legge il colore dalla texture usando le coordinate della trama dal vertex shader (che a sua volta lo ha ottenuto dal vertice).

Vuoi vedere il risultato di un simile shader? Ecco qui:

Questo viene reso in tempo reale; puoi trascinare la sfera con il tuo mouse.

Per raggiungere questo risultato, dovrai affrontare a lotto del codice WebGL. Infatti, WebGL è un'API davvero potente ma veramente di basso livello, e devi fare tutto da solo, dalla creazione dei buffer alla definizione delle strutture dei vertici. Devi anche fare tutti i calcoli e impostare tutti gli stati e gestire il caricamento della trama e così via ...

Troppo difficile? BABYLON.ShaderMaterial to the Rescue

So cosa stai pensando: gli shader sono davvero fantastici, ma non voglio preoccuparmi delle tubazioni interne WebGL o persino della matematica.

E va bene! Questa è una domanda perfettamente legittima, e questo è esattamente il motivo per cui ho creato Babylon.js.

Permettetemi di presentarvi il codice utilizzato dalla demo precedente di rolling sphere. Prima di tutto, avrai bisogno di una semplice pagina web:

   Babylon.js          

Noterai che gli shader sono definiti da > tag. Con Babylon.js puoi anche definirli in file separati (.fx File).

Puoi scaricare Babylon.js qui o sul nostro repository GitHub. È necessario utilizzare la versione 1.11 o successiva per accedere a BABYLON.StandardMaterial.

E infine il codice JavaScript principale è il seguente:

"usare rigorosamente"; document.addEventListener ("DOMContentLoaded", startGame, false); function startGame () if (BABYLON.Engine.isSupported ()) var canvas = document.getElementById ("renderCanvas"); motore var = new BABYLON.Engine (canvas, false); var scene = new BABYLON.Scene (engine); var camera = new BABYLON.ArcRotateCamera ("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero (), scena); camera.attachControl (tela); // Creating sphere var sphere = BABYLON.Mesh.CreateSphere ("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial ("amiga", scene, vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode",, attributes: ["position", "uv"], uniforms: ["worldViewProjection"] ); amigaMaterial.setTexture ("textureSampler", nuovo BABYLON.Texture ("amiga.jpg", scena)); sphere.material = amigaMaterial; engine.runRenderLoop (function () sphere.rotation.y + = 0.05; scene.render ();); ;

Puoi vedere che io uso a BABYLON.ShaderMaterial per sbarazzarsi di tutto il peso della compilazione, del collegamento e della gestione degli shader.

Quando crei a BABYLON.ShaderMaterial, devi specificare l'elemento DOM usato per memorizzare gli shader o il nome base dei file dove sono gli shader. Se si sceglie di utilizzare i file, è necessario creare un file per ogni shader e utilizzare il seguente modello di nome file: basename.vertex.fx e basename.fragment.fx. Quindi dovrai creare il materiale in questo modo:

var cloudMaterial = new BABYLON.ShaderMaterial ("cloud", scene, "./myShader", attributes: ["position", "uv"], uniforms: ["worldViewProjection"]);

Devi anche specificare i nomi di eventuali attributi e uniformi che usi. Quindi, puoi impostare direttamente il valore delle tue uniformi e campionatori usando il setTexture, setFloat, setFloats, setColor3, setColor4, setVector2, setVector3, setVector4, e setMatrix funzioni.

Abbastanza semplice, giusto?

Ti ricordi il precedente worldViewProjection matrice? Utilizzando Babylon.js e BABYLON.ShaderMaterial, non hai nulla di cui preoccuparti! Il BABYLON.ShaderMaterial lo calcolerà automaticamente per te perché lo dichiari nella lista delle uniformi.

BABYLON.ShaderMaterial può anche gestire le seguenti matrici per te:

  • mondo 
  • vista 
  • proiezione 
  • WorldView 
  • worldViewProjection 

Non c'è più bisogno di matematica. Ad esempio, ogni volta che esegui sphere.rotation.y + = 0.05, la matrice del mondo della sfera viene generata per te e trasmessa alla GPU.

CYOS: crea il tuo shader

Quindi andiamo più in alto e crea una pagina in cui puoi creare dinamicamente i tuoi shader e vedere immediatamente il risultato. Questa pagina userà lo stesso codice che abbiamo discusso in precedenza e userà a BABYLON.ShaderMaterial oggetto di compilazione ed esecuzione di shader che creerai.

Ho usato l'editor di codice ACE per CYOS. Questo è un editor di codice incredibile con evidenziatori di sintassi. Sentitevi liberi di dare un'occhiata qui. Puoi trovare CYOS qui.

Usando la prima casella combinata, sarai in grado di selezionare gli shader predefiniti. Vedremo ognuno di loro subito dopo.

È inoltre possibile modificare la mesh (l'oggetto 3D) utilizzata per visualizzare in anteprima gli shader utilizzando la seconda casella combinata.

Il Compilare il pulsante è usato per creare un nuovo BABYLON.ShaderMaterial dai tuoi shader. Il codice utilizzato da questo pulsante è il seguente: 

// Compile shaderMaterial = new BABYLON.ShaderMaterial ("shader", scene, vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode",, attributes: ["position", "normal", "uv"], uniformi: ["world", "worldView", "worldViewProjection"]); var refTexture = new BABYLON.Texture ("ref.jpg", scena); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture ("amiga.jpg", scena); shaderMaterial.setTexture ("textureSampler", amigaTexture); shaderMaterial.setTexture ("refSampler", refTexture); shaderMaterial.setFloat ("time", 0); shaderMaterial.setVector3 ("cameraPosition", BABYLON.Vector3.Zero ()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;

Brutalmente semplice, vero? Il materiale è pronto per inviarti tre matrici pre-calcolate (mondo, WorldView e worldViewProjection). Vertices verrà con coordinate di posizione, normali e texture. Sono già state caricate anche due trame:

amiga.jpgref.jpg

E infine, ecco il renderLoop dove aggiorno due uniformi convenienti:

  • uno chiamato tempo per ottenere alcune animazioni divertenti 
  • uno chiamato cameraPosition per ottenere la posizione della telecamera negli shader (che sarà utile per le equazioni di illuminazione) 
engine.runRenderLoop (function () mesh.rotation.y + = 0.001; if (shaderMaterial) shaderMaterial.setFloat ("time", time); time + = 0.02; shaderMaterial.setVector3 ("cameraPosition", camera.position) ; scene.render (););

Grazie al lavoro svolto su Windows Phone 8.1, puoi anche utilizzare CYOS sul tuo Windows Phone (è sempre un buon momento per creare uno shader):

Shader di base

Quindi iniziamo con il primissimo shader definito su CYOS: lo shader di base.

Conosciamo già questo shader. Calcola il GL_POSITION e usa le coordinate della trama per recuperare un colore per ogni pixel.

Per calcolare la posizione dei pixel, abbiamo solo bisogno del worldViewProjection matrice e posizione del vertice:

highp float di precisione; // Attributi attributo posizione vec3; attributo vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Variabile vec2 vUV variabile; void main (void) gl_Position = worldViewProjection * vec4 (position, 1.0); vUV = uv; 

Coordinate texture (uv) sono trasmessi non modificati al pixel shader.

Si prega di notare che dobbiamo aggiungere galleggiante mediocre di precisione; sulla prima riga per vertex e pixel shader perché Chrome lo richiede. Definisce che, per prestazioni migliori, non utilizziamo valori fluttuanti a precisione completa.

Il pixel shader è ancora più semplice, perché abbiamo solo bisogno di usare le coordinate della trama e recuperare un colore della trama:

highp float di precisione; vec2 vUV variabile; sampler2D textureSampler uniforme; void main (void) gl_FragColor = texture2D (textureSampler, vUV); 

Abbiamo visto in precedenza che il textureSampler uniforme è riempita con la trama "amiga", quindi il risultato è il seguente:

Shader in bianco e nero

Ora continuiamo con un nuovo shader: lo shader in bianco e nero.

L'obiettivo di questo shader è utilizzare il precedente ma con una modalità di rendering "solo in bianco e nero". Per fare ciò, possiamo mantenere lo stesso shader di vertici, ma il pixel shader deve essere leggermente modificato.

La prima opzione che abbiamo è prendere solo un componente, come quello verde:

highp float di precisione; vec2 vUV variabile; sampler2D textureSampler uniforme; void main (void) gl_FragColor = vec4 (texture2D (textureSampler, vUV) .ggg, 1.0); 

Come puoi vedere, invece di usare .rgb (questa operazione è chiamata a swizzle), abbiamo usato .GGG.

Ma se vogliamo un effetto bianco e nero veramente accurato, sarebbe una buona idea calcolare la luminanza (che tiene conto di tutte le componenti del colore):

highp float di precisione; vec2 vUV variabile; sampler2D textureSampler uniforme; void main (void) float luminance = dot (texture2D (textureSampler, vUV) .rgb, vec3 (0.3, 0.59, 0.11)); gl_FragColor = vec4 (luminanza, luminanza, luminanza, 1.0); 

L'operazione punto (o prodotto punto) è calcolata in questo modo:

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

Quindi nel nostro caso:

luminanza = r * 0,3 + g * 0,59 + b * 0,11 (questi valori sono basati sul fatto che l'occhio umano è più sensibile al verde)

Sembra fantastico, non è vero??

Shader ombreggiatura cellulare

Passiamo ora a uno shader più complesso: lo shading delle ombreggiature delle celle.

Questo ci richiederà di ottenere la posizione normale del vertice e la posizione del vertice nel pixel shader. Quindi il vertex shader sarà simile a questo:

highp float di precisione; // Attributi attributo posizione vec3; attributo vec3 normale; attributo vec2 uv; // Uniforms uniform mat4 world; uniforme mat4 worldViewProjection; // Variabile vec3 vPositionW; variando vec3 vNormalW; vec2 vUV variabile; void main (void) vec4 outPosition = worldViewProjection * vec4 (position, 1.0); gl_Position = outPosition; vPositionW = vec3 (world * vec4 (position, 1.0)); vNormalW = normalize (vec3 (world * vec4 (normal, 0.0))); vUV = uv; 

Si noti che usiamo anche la matrice del mondo, poiché posizione e normale sono memorizzati senza alcuna trasformazione e dobbiamo applicare la matrice del mondo per tenere conto della rotazione dell'oggetto.

Il pixel shader è il seguente:

highp float di precisione; // Luci che variano vec3 vPositionW; variando vec3 vNormalW; vec2 vUV variabile; // Refs uniform sampler2D textureSampler; void main (void) float ToonThresholds [4]; ToonThresholds [0] = 0,95; ToonThresholds [1] = 0.5; ToonThresholds [2] = 0,2; ToonThresholds [3] = 0,03; float 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); // Light vec3 lightVectorW = normalize (vLightPosition - vPositionW); // diffuse float ndl = max (0, punto (vNormalW, lightVectorW)); vec3 color = texture2D (textureSampler, vUV) .rgb; if (ndl> ToonThresholds [0]) color * = ToonBrightnessLevels [0];  else if (ndl> ToonThresholds [1]) color * = ToonBrightnessLevels [1];  else if (ndl> ToonThresholds [2]) color * = ToonBrightnessLevels [2];  else if (ndl> ToonThresholds [3]) color * = ToonBrightnessLevels [3];  else color * = ToonBrightnessLevels [4];  gl_FragColor = vec4 (colore, 1.); 

L'obiettivo di questo shader è simulare una luce, e invece di calcolare un'ombreggiatura uniforme considereremo che la luce si applicherà in base a specifiche soglie di luminosità. Ad esempio, se l'intensità della luce è tra 1 (massimo) e 0.95, il colore dell'oggetto (recuperato dalla trama) verrà applicato direttamente. Se l'intensità è tra 0.95 e 0.5, il colore sarà attenuato da un fattore di 0.8, e così via.

Quindi, ci sono principalmente quattro passaggi in questo shader:

  • Innanzitutto, dichiariamo soglie e livelli di costanti.
  • Quindi, dobbiamo calcolare l'illuminazione usando l'equazione di Phong (assumiamo che la luce non si muova): 
vec3 vLightPosition = vec3 (0, 20, 10); // Light vec3 lightVectorW = normalize (vLightPosition - vPositionW); // diffuse float ndl = max (0, punto (vNormalW, lightVectorW));

L'intensità della luce per pixel dipende dall'angolo tra la normale e la direzione della luce.

  • Quindi otteniamo il colore della trama per il pixel.
  • E finalmente controlliamo la soglia e applichiamo il livello al colore.

Il risultato sembra un oggetto fumetto: 

Phong Shader

Abbiamo usato una parte dell'equazione di Phong nello shader precedente. Quindi proviamo a usare il tutto ora.

Il vertex shader è chiaramente semplice qui, perché tutto sarà fatto nel pixel shader:

highp float di precisione; // Attributi attributo posizione vec3; attributo vec3 normale; attributo vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Variabile vec3 vPosition variabile; variando vec3 vNormal; vec2 vUV variabile; void main (void) vec4 outPosition = worldViewProjection * vec4 (position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = posizione; vNormal = normale; 

Secondo l'equazione, devi calcolare la parte diffusa e speculare usando la direzione della luce e il normale del vertice:

highp float di precisione; // Variabile vec3 vPosition variabile; variando vec3 vNormal; vec2 vUV variabile; // Uniforms uniform mat4 world; // Refs uniforme vec3 cameraPosition; sampler2D textureSampler uniforme; void main (void) vec3 vLightPosition = vec3 (0, 20, 10); // Valori mondiali vec3 vPositionW = vec3 (world * vec4 (vPosition, 1.0)); vec3 vNormalW = normalize (vec3 (world * vec4 (vNormal, 0.0))); vec3 viewDirectionW = normalize (cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize (vLightPosition - vPositionW); vec3 color = texture2D (textureSampler, vUV) .rgb; // diffuse float ndl = max (0, punto (vNormalW, lightVectorW)); // Specular vec3 angleW = normalize (viewDirectionW + lightVectorW); float specComp = max (0, punto (vNormalW, angleW)); specComp = pow (specComp, max (1., 64.)) * 2; gl_FragColor = vec4 (color * ndl + vec3 (specComp), 1.); 

Abbiamo già usato la parte diffusa nello shader precedente, quindi qui abbiamo solo bisogno di aggiungere la parte speculare. Questa immagine tratta da un articolo di Wikipedia spiega come funziona lo shader:

Di Brad Smith aka Rainwarrior.

Il risultato sulla nostra sfera:

Scarta Shader

Per lo shader scartato, vorrei introdurre un nuovo concetto: il scartare parola chiave. Questo shader eliminerà ogni pixel non rosso e creerà l'illusione di un oggetto "scavato".

Il vertex shader è lo stesso usato dallo shader di base:

highp float di precisione; // Attributi attributo posizione vec3; attributo vec3 normale; attributo vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Variabile vec2 vUV variabile; void main (void) gl_Position = worldViewProjection * vec4 (position, 1.0); vUV = uv; 

Il pixel shader dovrà testare il colore e l'uso scartare quando, ad esempio, il componente verde è troppo alto:

highp float di precisione; vec2 vUV variabile; // Refs uniform sampler2D textureSampler; void main (void) vec3 color = texture2D (textureSampler, vUV) .rgb; if (color.g> 0.5) discard;  gl_FragColor = vec4 (colore, 1.); 

Il risultato è divertente:

Wave Shader

Abbiamo giocato molto con i pixel shader, ma volevo anche mostrarti che possiamo fare molte cose con vertex shader.

Per lo shader wave, riutilizzeremo lo shader dei pixel Phong.

Il vertex shader userà l'uniforme chiamata tempo per ottenere alcuni valori animati. Usando questa uniforme, lo shader genererà un'onda con le posizioni dei vertici:

highp float di precisione; // Attributi attributo posizione vec3; attributo vec3 normale; attributo vec2 uv; // Uniforms uniform mat4 worldViewProjection; tempo di galleggiamento uniforme; // Variabile vec3 vPosition variabile; variando vec3 vNormal; vec2 vUV variabile; void main (void) vec3 v = position; v.x + = sin (2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4 (v, 1.0); vPosition = posizione; vNormal = normale; vUV = uv; 

Viene applicato un seno position.y, e il risultato è il seguente:

Mappatura degli ambienti sferici

Questo era in gran parte ispirato da questo tutorial. Ti lascio leggere quell'articolo eccellente e giocare con lo shader associato. 

Fresnel Shader

Vorrei concludere questo articolo con il mio preferito: lo shader Fresnel.

Questo shader viene utilizzato per applicare un'intensità diversa in base all'angolo tra la direzione della vista e la normale del vertice.

Lo shader del vertex è lo stesso utilizzato dallo shader shading delle celle, e possiamo facilmente calcolare il termine Fresnel nel nostro shader di pixel (perché abbiamo la posizione normale e della camera, che può essere usata per valutare la direzione della vista):

highp float di precisione; // Luci che variano vec3 vPositionW; variando vec3 vNormalW; // Refs uniforme vec3 cameraPosition; sampler2D textureSampler uniforme; void main (void) vec3 color = vec3 (1., 1., 1.); vec3 viewDirectionW = normalize (cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot (viewDirectionW, vNormalW); fresnelTerm = clamp (1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4 (color * fresnelTerm, 1.); 

Il tuo Shader?

Ora sei più preparato a creare il tuo shader. Sentiti libero di usare i commenti qui o sul forum Babylon.js per condividere i tuoi esperimenti!

Se vuoi andare oltre, ecco alcuni link utili:

  • Repository Babylon.js 
  • Forum Babylon.js 
  • CYOS
  • GLSL su Wikipedia 
  • Documentazione GLSL

E un po 'più di apprendimento che ho creato sull'argomento:

  • Introduzione a WebGL 3D con HTML5 e Babylon.JS
  • Grafica all'avanguardia in HTML

Oppure, facendo un passo indietro, le serie di apprendimento del nostro team su JavaScript: 

  • Suggerimenti pratici sulle prestazioni per rendere il tuo HTML / JavaScript più veloce (una serie in sette parti dal design reattivo ai giochi casuali all'ottimizzazione delle prestazioni)
  • La piattaforma Web moderna Jump Start (i fondamenti di HTML, CSS e JS)
  • Sviluppo di un'applicazione Windows universale con HTML e JavaScript Jump Start (usa il JS che hai già creato per creare un'app)

E, naturalmente, sei sempre libero di utilizzare alcuni dei nostri strumenti gratuiti per creare la tua prossima esperienza web: Visual Studio Community, Azure Trial e strumenti di test cross-browser per Mac, Linux o Windows.

Questo articolo fa parte della serie di web dev tech di Microsoft. Siamo entusiasti di condividere Microsoft Edge e il nuovo Motore di rendering EdgeHTML con te. Ottieni macchine virtuali gratuite o test in remoto sul tuo dispositivo Mac, iOS, Android o Windows @ http://dev.modern.ie/.