I keynote for dag 2 av // Build 2014 (se 2: 24-2: 28) dempet Microsoft evangelister Steven Guggenheimer og John Shewchuk hvordan Oculus Rift-støtte ble lagt til Babylon.js. Og en av de viktigste tingene for denne demoen var det arbeidet vi gjorde på en bestemt skygge for å simulere linser, som du kan se på dette bildet:
Jeg presenterte også en økt med Frank Olivier og Ben Constable om grafikk på IE og Babylon.js.
Dette fører meg til et av spørsmålene folk ofte spør meg om Babylon.js: "Hva mener du med shaders?"Så i dette innlegget skal jeg forklare for deg hvordan shaders fungerer, og gi noen eksempler på vanlige typer shaders.
Før vi begynner å eksperimentere, må vi først se hvordan ting fungerer internt.
Når vi arbeider med hardware-akselerert 3D, diskuterer vi to CPUer: Hovedprosessoren og GPU. GPU er en slags ekstremt spesialisert CPU.
GPU er en statlig maskin som du har satt opp ved hjelp av CPU. For eksempel vil CPUen konfigurere GPUen til å gjengi linjer i stedet for trekanter. Eller det vil definere at gjennomsiktighet er på, og så videre.
Når alle statene er satt, vil CPU definere hva som skal gjengis - geometrien, som består av en liste over punkter (kalt toppunkter og lagret i en oppringt gruppe vertex buffer), og en liste over indekser (ansikter eller trekanter, lagret i en oppringt gruppe indeksbuffer).
Det endelige trinnet for CPU er å definere hvordan geometrien skal gjengis, og for denne spesifikke oppgaven vil CPUen definere shaders for GPU. Shaders er et stykke kode som GPUen vil utføre for hver av kryssene og pikslene den skal gjengi.
Først noen ordforråd: tenk på et toppunkt (vertices når det er flere av dem) som et "punkt" i et 3D-miljø (i motsetning til et punkt i et 2D-miljø).
Det finnes to typer shaders: vertex shaders og piksel (eller fragment) shaders.
Før vi graver inn shaders, la oss ta et skritt tilbake. For å gjøre piksler, vil GPUen ta geometrien definert av CPUen og vil gjøre følgende:
Ved hjelp av indeksbufferen samles tre vertikaler for å definere en trekant: indeksbufferen inneholder en liste over verteksindekser. Dette betyr at hver oppføring i indeksbufferen er tallet til et toppunkt i toppunktbufferen. Dette er veldig nyttig for å unngå duplisering av hjørner.
For eksempel er følgende indeksbuffer en liste over to ansikter: [1 2 3 1 3 4]
. Det første ansiktet inneholder vertex 1, vertex 2 og toppunkt 3. Det andre ansiktet inneholder vertex 1, vertex 3 og toppunkt 4. Så det er fire hjørner i denne geometrien:
Vertex shader brukes på hvert toppunkt av trekanten. Det primære målet for vertex shader er å produsere en piksel for hvert toppunkt (projeksjonen på 2D-skjermen i 3D-vertexet):
Ved å bruke disse tre pikslene (som definerer en 2D-trekant på skjermen), vil GPUen interpolere alle verdiene som er knyttet til pikselet (i det minste sin posisjon), og pikselskyggeren vil bli brukt på hver piksel som er innbefattet i 2D-trekant for å generer en farge for hver piksel:
Denne prosessen er gjort for hvert ansikt definert av indeksbufferen.
Åpenbart, på grunn av sin parallelle karakter, er GPUen i stand til å behandle dette trinnet for mange ansikter samtidig, og dermed oppnå virkelig god ytelse.
Vi har nettopp sett det for å gi trekanter, GPU trenger to shaders: vertex shader og pixel shader. Disse shaders er skrevet ved hjelp av et språk som heter GLSL (Graphics Library Shader Language). Det ser ut som C.
For Internet Explorer 11 har vi utviklet en kompilator for å omdanne GLSL til HLSL (High Level Shader Language), som er skyggespråket til DirectX 11. Dette gjør at IE11 kan sikre at shader-koden er trygg (du vil ikke bruke WebGL for å tilbakestille datamaskinen din!):
Her er et eksempel på en felles vertex shader:
presisjon highp float; // Attributter attributt vec3 posisjon; attributt vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varierende varierende vec2 vUV; void main (void) gl_Position = worldViewProjection * vec4 (posisjon, 1.0); vUV = uv;
En toppunktsskygge inneholder følgende:
vektor3: x, y, z
). Men som utvikler kan du bestemme deg for å legge til mer informasjon. For eksempel, i den tidligere shader, er det en Vektor2
oppkalt uv
(teksturkoordinater som tillater oss å bruke en 2D-tekstur på et 3D-objekt).(x, y, z)
til skjermen (x, y)
.VUV
(en enkel kopi av uv
) verdi til pixel shader. Dette betyr at en piksel er definert her med en posisjon og teksturkoordinater. Disse verdiene blir interpolert av GPU og brukes av pikselskyggeren. hoved()
er koden utført av GPU for hvert vertex og må minst gi en verdi for gl_position
(posisjonen på skjermen av dagens vertex). Vi kan se i vår prøve at vertex shader er ganske enkelt. Det genererer en systemvariabel (starter med gl_
) oppkalt gl_position
å definere plasseringen til den tilknyttede piksel, og den angir en varierende variabel som kalles VUV
.
I vår shader har vi en matrise kalt worldViewProjection
. Vi bruker denne matrisen til å projisere toppunktet til gl_position
variabel. Det er kult, men hvordan får vi verdien av denne matrisen? Det er en uniform, så vi må definere den på CPU-siden (ved hjelp av JavaScript).
Dette er en av de komplekse delene av å gjøre 3D. Du må forstå komplisert matte (eller du må bruke en 3D-motor, som Babylon.js, som vi skal se senere).
De worldViewProjection
Matrise er kombinasjonen av tre forskjellige matriser:
Ved å bruke den resulterende matrisen kan vi omdanne 3D-hjørner til 2D-piksler mens du tar hensyn til synspunktet og alt relatert til posisjonen / skalaen / rotasjonen av det nåværende objektet.
Dette er ditt ansvar som en 3D-utvikler: å opprette og holde denne matrisen oppdatert.
Når vertex shader er utført på hvert vertex (tre ganger, da) har vi tre piksler med riktig gl_position
og a VUV
verdi. GPUen vil da interpolere disse verdiene på hver piksel som finnes i trekanten produsert av disse pikslene.
Så, for hver piksel, vil den utføre pixel shader:
presisjon highp float; varierende vec2 vUV; uniform sampler2D textureSampler; void main (void) gl_FragColor = texture2D (textureSampler, vUV);
Strukturen til en pikselskygger ligner en toppskygger:
VUV
verdi fra vertex shader. hoved-
er koden utført av GPU for hver piksel og må minst gi en verdi for gl_FragColor
(fargen på den nåværende piksel). Denne pikselskyggeren er ganske enkel: Den leser fargen fra tekstur ved hjelp av teksturkoordinater fra vertex shader (som igjen fikk den fra vertexen).
Vil du se resultatet av en slik shader? Her er det:
Dette blir gjengitt i sanntid; du kan trekke sfæren med musen.For å oppnå dette resultatet må du håndtere a mye av WebGL-koden. Faktisk er WebGL en veldig kraftig, men veldig lav nivå-API, og du må gjøre alt for deg selv, fra å lage buffere til å definere topptekststrukturer. Du må også gjøre all matte og sette alle statene og håndtere teksturbelastning og så videre ...
Jeg vet hva du tenker: shaders er veldig kule, men jeg vil ikke bry meg med WebGL intern VVS eller til og med matte.
Og det er greit! Dette er en helt legitim spør, og det er nettopp derfor jeg opprettet Babylon.js.
La meg presentere koden som ble brukt av den tidligere rullende sfæredemoen. Først av alt trenger du en enkel nettside:
Babylon.js
Du vil merke at shaders er definert av >