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.
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.
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.
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;
Un vertex shader contiene quanto segue:
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).(x, y, z)
sullo schermo (x, y)
.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()
è 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
.
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.
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);
La struttura di un pixel shader è simile a un vertex shader:
VUV
valore dal vertex shader. 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 ...
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 >