Advertisement
  1. Game Development
  2. WebGL
Gamedevelopment

Creare Shaders con Babylon,js e WebGL: teoria ed esempi

by
Difficulty:AdvancedLength:LongLanguages:
Sponsored Content

This sponsored post features a product relevant to our readers while meeting our editorial guidelines for being objective and educational.

Italian (Italiano) translation by Chip (you can also view the original English article)

Nel keynote for Day 2 of //Build 2014 (cfr 2: 24-2: 28), gli esperti Microsoft Steven Guggenheimer e John Shewchuk hanno dimostrato come è stato aggiunto il supporto per Oculus Rift in Babylon.js. E una delle cose fondamentali per questa demo è stato il lavoro che abbiamo fatto su uno specifico shader per simulare le lenti, come potete vedere in questa immagine:

Lens simulation image

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

Questo mi ha portato a riflettere su una delle domande che la gente spesso mi ha fatto su su Babylon.js: "Cosa si intende per shader" Quindi, in questo post, ho intenzione di spiegare a voi come funzionano gli shaders e fornire alcuni esempi di tipi comuni di shader.

La teoria

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

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

La GPU è una macchina a stati che si imposta con la CPU. Per esempio la CPU configurerà la GPU per il rendering di linee invece che di triangoli. Oppure stabilizzerà di attivare la trasparenza, e così via.

Una volta che tutti gli stati vengono impostati, la CPU definirà quale geometria visualizzare, che è composta da un elenco di punti (chiamati vertici e memorizzati in un array chiamata vertex buffer), e da un elenco di indici (le facce, o triangoli, memorizzati in un array chiamato index buffer).

Il passo finale per la CPU è definire come rendere la geometria, e per questo compito specifico, la CPU definirà gli shaders per la GPU. Shader sono un pezzo di codice che la GPU esegue per ciascuno dei vertici e pixel che deve renderizzare.

In primo luogo, un pò di vocabolario: pensare ad un vertice (vertici quando sono più di uno) come ad un "punto" in un ambiente 3D (al contrario di un punto in un ambiente 2D).

Ci sono due tipi di shaders: vertex shaders e pixel (o fragment) shades.

Pipeline Grafica

Prima di scavare negli shaders, facciamo un passo indietro. Per renderizzare i pixels, la GPU avrà la geometria definita dalla CPU e farà quanto segue:

Utilizzando l'index buffer, tre vertici sono riuniti per definire un triangolo: il buffer index contiene un elenco di indici di vertice. Ciò significa che ogni voce nel index buffer è il numero di un vertice nel vertex buffer. Questo è veramente utile per evitare la duplicazione dei vertici.

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

Chart showing four vertices

Il vertex shader è applicato su ogni vertice del triangolo. L'obiettivo primario del vertex shader è quello di produrre un pixel per ogni vertice (proiezione sullo schermo 2D del vertice 3D):

vertex shader is applied on each vertex of the triangle

Utilizzando questi tre pixel (che definiscono un triangolo 2D sullo schermo), la GPU interpolerà tutti i valori collegati al pixel (almeno la sua posizione), ed i pixel shader saranno applicati su ogni singolo pixel incluso nel triangolo 2D per generare su ciascuno un colore:

pixel shader will be applied on every pixel included into the 2D triangle

Questo processo viene eseguito per ogni faccia definita dal index buffer.

Ovviamente, a causa della sua natura parallela, la GPU è in grado di elaborare contemporaneamente questo passaggio per molte facce ottenendo così prestazioni molto buone.

GLSL

Abbiamo appena visto che per il rendering di triangoli la GPU ha bisogno di due shader: vertex shader e pixel shader. Questi shader sono scritti utilizzando un linguaggio chiamato GLSL (Graphics Library Shader Language). Che assomiglia al C

Per Internet Explorer 11, abbiamo sviluppato un compilatore per trasformare il GLSL in HLSL (High Level Shader Language), che è la lingua degli shader di DirectX 11. Questo permette a IE11 di garantire che il codice degli shader sia sicuro (non vogliamo usare WebGL per resettare il vostro computer!):

Flow chart of transforming GLSL to HLSL

Ecco un esempio di un vertex shader molto comune:

Struttura del Vertex Shader

Un vertex shader contiene quanto segue:

  • Attributes: Un attribute definisce una porzione di un vertice. Di default un vertex dovrebbe contenere almeno una posizione (un vector3: x, y, z). Ma come sviluppatore, possiamo decidere di aggiungere altre informazioni. Ad esempio, nel primo degli shader, c'è un vector2 chiamato uv (le coordinate delle texture che ci permettono di applicare una texture 2D su un oggetto 3D).
  • Uniforms: Una uniform è una variabile utilizzata dallo shader e definita dalla CPU. L'unica uniform abbiamo qui è una matrice utilizzata per proiettare la posizione del vertice (x, y, z) sullo schermo (x, y).
  • Varying : le variabili Varying sono valori creati dal vertex shader e trasmessi al pixel shader. Qui, il vertex shader trasmette un valore vUV (una semplice copia di uv) al pixel shader. Ciò significa che un pixel è definito qui con posizione e coordinata di texture. Questi valori saranno interpolati dalla GPU ed utilizzati dal pixel shader.
  • main: La funzione chiamata main() è 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. Esso genera una variabile di sistema (che inizia per gl_) denominata gl_position per definire la posizione associata al pixel ed imposta una variabile varying denominata vUV.

La magia dietro le matrici

Nella nostro shader abbiamo una matrice chimamata worldViewProjection. Usiamo questa matrice per proiettare la posizione del vertice nella variabile gl_position. Questo è interessante, ma come possiamo ottenere il valore di questa matrice? Si tratta di una uniform, quindi dobbiamo definirla dal lato CPU (utilizzando JavaScript).

Questa è una delle complessità del fare 3D. È necessario comprendere la matematica complessa (o si dovrà utilizzare un motore 3D, come Babylon.js, che vedremo più avanti).

La matrice worldViewProjection è la combinazione di tre diverse matrici:

The worldViewProjection matrix is the combination of three different matrices

L'utilizzo della matrice risultante ci permette di essere in grado di trasformare i vertici 3D in pixel 2D, tenendo in considerazione il punto di vista (della camera) e tutto ciò che riguarda la posizione/scalatura/rotazione dell'oggetto corrente.

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

Torniamo agli shaders

Una volta che il vertex shader viene eseguito su ogni vertice (quindi tre volte) abbiamo tre pixel con una corretta gl_position e un valore vUV. La GPU quindi interpola i valori su ogni pixel contenuto nel triangolo prodotto da questi pixel.

Poi, per ogni pixel, si eseguirà il pixel shader:

Struttura dei Pixel (o Fragment) Shader

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

  • Varying: le  variabili varying sono valori creati dal vertex shader e trasmessi al pixel shader. Qui il pixel shader riceverà un valore vUV dal vertex shader.
  • Uniforms: Una uniforms è una variabile definita dalla CPU e utilizzata dallo shader. L'unica uniform che abbiamo qui è un campionatore, che è uno strumento utilizzato per leggere i colori della texture.
  • main: La funzione main è 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: si legge il colore dalla texture utilizzando le coordinate texture dal vertex shader (che a sua volta l'ha ottenuto dal vertex).

Vuoi vedere il risultato di questo shader? Eccolo:

Questo rendering è eseguito in tempo reale; è possibile trascinare la sfera con il mouse.

Per ottenere questo risultato, si avrà a che fare con un molto codice WebGL. Infatti, WebGL è un davvero potente ma è una API di veramente basso livello, si deve fare tutto da soli, dalla creazione dei buffer alla definizione delle strutture dei vertici. È inoltre necessario fare tutti i calcoli, impostare tutti gli stati, gestire il caricamento delle texture e così via ...

Troppo difficile? BABYLON.ShaderMaterial alla riscossa

So cosa state pensando: gli shaders sono davvero interessanti, ma io non voglio perdere tempo con gli impanti interni di WebGL o addirittura con la matematica.

E va bene! E' perfettamente legittimo da chiedere e questo è esattamente il motivo per cui ho creato Babylon.js.

Lasciate che vi mostri il codice utilizzato dal demo precedente della sfera ruotante. Prima di tutto, avrete bisogno di una semplice pagina web:

Si noterà che gli shader sono definite dal tag <script>. Con Babylon.js si può anche definirli in file separati (.fx file).

È possibile ottenere Babylon.js qui o sulla nostra repo GitHub. È necessario utilizzare la versione 1.11 o superiore per ottenere l'accesso al BABYLON.StandardMaterial.

Infine il codice principale JavaScript è il seguente:

Si può vedere che uso un BABYLON.ShaderMaterial per sbarazzarsi di tutto il peso della compilazione, il collegamento e la gestione degli shader.

Quando si crea un BABYLON.ShaderMaterial, è necessario specificare l'elemento DOM utilizzato per memorizzare gli shaders o il nome di 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 schema nei nomi: basename.vertex.fx e basename.fragment.fx. Poi si dovrà creare il materiale in questo modo:

È inoltre necessario specificare i nomi di tutti gli attributes e uniform che si utilizzano. Quindi, è possibile impostare direttamente il valore delle vostre uniforms e campionatori utilizzando il setTexture, setFloat, setFloats, funzioni setColor3, setColor4, setVector2, setVector3, setVector4, e setMatrix.

Abbastanza semplice, giusto?

Vi ricordate la precedente matrice worldViewProjection? Utilizzando Babylon.js e BABYLON.ShaderMaterial, non hai niente di cui preoccuparti! BABYLON.ShaderMaterial lo calcolerà automaticamente per voi, perché si lo dichiarate nella lista delle uniforms.

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

  • world
  • view
  • projection
  • worldView
  • worldViewProjection

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

CYOS: Crea il tuo Shader

Così andiamo più in grande e creiamo una pagina dove è possibile creare in modo dinamico i propri shader e vederne immediatamente il risultato. Questa pagina sta per utilizzare lo stesso codice che abbiamo precedentemente discusso e utilizzerà un oggetto BABYLON.ShaderMaterial per compilare ed eseguire shader che creerete.

Ho usato l'editor ACE per CYOS. Si tratta di un incredibile editor di codice con evidenziatori della sintassi. Sentitevi liberi di dare un'occhiata qui. Potete trovare CYOS qui.

Utilizzando la prima combo box, saremo in grado di selezionare shader predefiniti. Vedremo ciascuno di loro subito dopo.

È inoltre possibile, utilizzando la seconda casella combinata, modificare la mesh (l'oggetto 3D) usata per visualizzare i vostri shader .

Il pulsante Compile viene utilizzato per creare un nuovo BABYLON.ShaderMaterial dai vostri shaders. Il codice utilizzato da questo pulsante è il seguente:

Brutalmente semplice, giusto? Il materiale è pronto per inviarvi tre matrici pre-calcolate (world, worldview e worldViewProjection). Vertici avranno le coordinate di posizione, le normali e le coordinate di texture. Due texture sono già state caricate per voi:

amiga texture
amiga.jpg
ref texture
ref.jpg

E, infine, ecco il renderLoop dove aggiorno due utili uniform:

  • una chiamata time al fine di ottenere alcune divertenti animazioni
  • una chiamata cameraPosition per ottenere la posizione della fotocamera nel vostro shader (che sarà utile per le equazioni di illuminazione)

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

CYOS on Windows Phone

Shader base

Quindi cominciamo con il primo degli shader definito sul CYOS: Basic shader (shader base).

Conosciamo già questo shader. Calcola la gl_position e usa le coordinate texture per andare a prendere un colore per ogni pixel.

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

Le coordinate di texture (uv) vengono trasmesse senza modifiche al pixel shader.

Si prega di notare che abbiamo bisogno di aggiungere precision mediump float; sulla prima linea sia per il vertex shader che per il pixel shader, perché lo richiede Chrome. Per migliorare le prestazioni, indichiamo che non useremo valori float a piena precisione.

Il pixel shader è ancora più semplice, perché abbiamo solo bisogno di utilizzare le coordinate di texture a prendere un colore della texture:

Abbiamo visto in precedenza che la uniform textureSampler viene riempita con la texture "amiga", in modo che il risultato sia il seguente:

Basic Shader result

Shader bianco e nero

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

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

La prima opzione che abbiamo è prendere una sola componente, ad esempio quella verde:

Come si può vedere, invece di utilizzare .rgb (questa operazione si chiama swizzle), abbiamo utilizzato .ggg.

Ma se vogliamo davvero un accurato effetto bianco e nero, l'idea migliore sarebbe calcolare la luminanza (che tiene conto di tutte le componenti di colore):

L'operazione dot (o dot product, proddoto vettoriale) è calcolato in questo modo:

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

Quindi, nel nostro caso:

luminance = r * 0.3 + 0.59 g * + b * 0,11 (questi valori si basano sul fatto che l'occhio umano è più sensibile al verde)

Suona bene, non è vero?

Black and white shader result

Cell Shading Shader

Ora passiamo ad uno shader più complesso: il cell shading shader (n.d.a. ombreggiatura a fumetto).

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

Si prega di notare che usiamo anche la world matrix (matrice mondo), perché la posizione e la normale sono memorizzate senza alcuna trasformazione e dobbiamo applicare la matrice mondo per prendere in considerazione la rotazione dell'oggetto.

Il pixel shader è il seguente:

L'obiettivo di questo shader è quello di simulare una luce ma invece di calcolare un'ombreggiatura uniforme, applicherà una luce in base a specifiche soglie di luminosità. Ad esempio, se l'intensità luminosa è compresa tra 1 (massimo) e 0,95, il colore dell'oggetto (prelevato dalla texture) verrà applicato direttamente. Se l'intensità è compresa tra 0,95 e 0,5, il colore sarà attenuato di un fattore 0,8, e così via.

Quindi, ci sono principalmente quattro passi in questo shader:

  • In primo luogo, dichiariamo soglie e livelli costanti.
  • Poi, abbiamo bisogno di calcolare la luce utilizzando l'equazione Phong (diamo per scontato che la luce non si muove):

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

  • Poi si ottiene il colore della texture per pixel.
  • E infine controlliamo la soglia e applichiamo il livello al colore.

Il risultato si presenta come un oggetto in un cartone animato:

Cell shading shader result

Phong Shader

Abbiamo utilizzato una parte dell'equazione Phong nello shader precedente. Quindi cerchiamo di utilizzare il tutto ora.

Il vertex shader qui è molto semplice, perché tutto sarà fatto dal pixel shader:

Secondo l'equazione, è necessario calcolare la parte diffusa e la parte speculare utilizzando la direzione della luce ed il vertice del normale:

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

Diffuse plus Specular equals Phong Reflection
Da Brad Smith aka Rainwarrior.

Il risultato sulla nostra sfera:

Phong shader result

Shader scarto

Per lo shader scarto, vorrei introdurre un nuovo concetto: la parola chiave discard. Questo shader scarterà ogni pixel non rosso e creerà l'illusione di un oggetto "scavato" ..

Il Vertex Shader è lo stesso di quello utilizzato dallo shader base:

Il pixel shader dovrà verificare il colore e utilizzare discard quando, per esempio, la componente verde è troppo alta:

Il risultato è divertente:

Discard shader result

Shader onda

Abbiamo giocato molto con i pixel shader, ma ho anche voluto dimostrare che siamo in grado di fare un sacco di cose con i vertex shader.

Per lo shader onda, riuseremo il pixel shader Phong.

Il vertex shader utilizzerà la uniform chiamata time per ottenere alcuni valori animati. Usando questa uniform, lo shader genererà un'onda con la posizione dei suoi vertici:

Un funzione sinusoidale viene applicata a position.y, e il risultato è il seguente:

Wave shader result

Mappatura ambienta sferica

E' stata in gran parte ispirata da questa esercitazione. Leggeremo questo eccellente articolo e lavoreremo con lo shader ad esso associato.

Spherical environment mapping shader

Fresnel Shader

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

Questo shader viene usato per applicare una diversa intensità secondo l'angolo tra la direzione della vista e la normale del vertice.

Il vertex shader è lo stesso usato dallo shader cell shading, e possiamo facilmente calcolare il termine di Fresnel nel nostro pixel shader (perché abbiamo la normale e la posizione della fotocamera, che può essere utilizzata per valutare la direzione della vista):

Fresnel Shader result

Il tuo Shader?

Ora siete più preparati a creare il vostro shader. Sentitevi liberi di utilizzare i commenti qui o nel forum Babylon.js per condividere le vostre esperienze!

Se si vuole andare oltre, ecco alcuni link utili:

E un pò di insegnamenti che ho creato sul tema:

Oppure, facendo un passo indietro, la nostra serie di apprendimento del team su JavaScript:

E, naturalmente, siete sempre i benvenuti a utilizzare alcuni dei nostri strumenti gratuiti per costruire la vostra 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 tecnologia web dev di Microsoft. Siamo entusiasti di condividere con voi Microsoft Edge e il nuovo motore di rendering EdgeHTML. Ottenete le macchine virtuali libere o testate da remoto sul dispositivo Mac, iOS, Android o Windows @ http://dev.modern.ie/.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.