WebGL Essentials Del III

Velkommen tilbake til denne tredje og siste avdrag i vår mini-serie av WebGL Essentials. I denne leksjonen tar vi en titt på belysning og legger til 2D-objekter på scenen din. Det er mye ny informasjon her, så la oss dykke rett inn!


Lys

Belysning kan være det mest tekniske og vanskelige aspektet av et 3D-program for å forstå. En fast forståelse av belysning er helt avgjørende.

Hvordan virker lett?

Før vi går inn i ulike typer lys- og kodeteknikker, er det viktig å vite hvordan lys fungerer i den virkelige verden. Hver lyskilde (f.eks. En lyspære, solen, osv.) Genererer partikler som kalles fotoner. Disse fotonene spretter rundt objekter til de til slutt kommer inn i våre øyne. Øynene våre konverterer fotonene til å produsere et visuelt "bilde". Slik ser vi. Lys er også additiv, noe som betyr at et objekt med mer farge er lysere enn et objekt uten farger (svart). Svart er det totale fraværet av farge, mens hvitt inneholder alle farger. Dette er et viktig skille når du arbeider med svært lyse eller "overmattende" lys.

Lysstyrke er bare ett prinsipp som har flere stater. Refleksjon, for eksempel, kan ha en rekke forskjellige nivåer. Et objekt, som et speil, kan være helt reflekterende, mens andre gjenstander kan ha en matt overflate. Transparens bestemmer hvordan objekter bøyer lyset og forårsaker brytning; ett objekt kan være helt gjennomsiktig mens andre kan være ugjennomsiktig (eller et hvilket som helst stadium i mellom).

Listen fortsetter, men jeg tror du allerede kan se at lyset ikke er enkelt.

Hvis du ville ha en liten scene for å simulere ekte lys, ville det løpe på noe som 4 rammer i timen, og det er på en kraftig datamaskin. For å omgå dette problemet, bruker programmerere triks og teknikker for å simulere halvrealistisk belysning med en rimelig rammefrekvens. Du må komme opp med noen form for kompromiss mellom realisme og fart. La oss ta en titt på noen av disse teknikkene.

Før jeg begynner å utdype seg på ulike teknikker, vil jeg gjerne gi deg en liten ansvarsfraskrivelse. Det er mye kontrovers på de eksakte navnene til de forskjellige belysningsteknikkene, og forskjellige personer vil gi deg forskjellige forklaringer på hva "Ray Casting" eller "Light Mapping" er. Så før jeg begynner å hate posten, vil jeg gjerne si at jeg skal bruke navnene som jeg lærte; Noen mennesker er kanskje ikke enige om mine eksakte titler. I alle fall er det viktig å vite hva de forskjellige teknikkene er. Så uten videre, la oss komme i gang.

Du må komme opp med noen form for kompromiss mellom realisme og fart.

Ray Tracing

Ray tracing er en av de mer realistiske belysningsteknikkene, men det er også en av de mer kostbare. Ray tracing emulerer ekte lys; det avgir "fotoner" eller "stråler" fra lyskilden og spretter dem rundt. I de fleste ray tracing implementeringer kommer strålene fra "kameraet" og spretter på scenen i motsatt retning. Denne teknikken brukes vanligvis i filmer eller scener som kan gjengis på forhånd. Dette er ikke å si at du ikke kan bruke strålesporing i en sanntidsapplikasjon, men det tvinger deg til å tone ned andre ting i scenen. For eksempel må du kanskje redusere mengden "bounces" som strålene skal utføre, eller du kan sørge for at det ikke er gjenstander som har reflekterende eller brytende overflater. Ray tracing kan også være et levedyktig alternativ hvis søknaden din har svært få lys og gjenstander.

Hvis du har en sanntidsapplikasjon, kan du kanskje forkompilere deler av scenen din.

Hvis lysene i søknaden din ikke beveger seg rundt eller bare beveger seg rundt i et lite område av gangen, kan du forkompilere belysningen med en meget avansert strålesporingsalgoritme og omberegne et lite område rundt den bevegelige lyskilden. For eksempel, hvis du lager et spill hvor lysene ikke beveger seg, kan du forkompilere verden med alle ønskede lys og effekter. Deretter kan du bare legge til en skygge rundt karakteren din når han beveger seg. Dette gir et svært høy kvalitet utseende med minimal behandling.

Ray Casting

Ray-støping er svært lik strålingssporing, men "fotonene" spretter ikke av objekter eller interagerer med forskjellige materialer. I en typisk applikasjon vil du i utgangspunktet starte med en mørk scene, og da vil du tegne linjer fra lyskilden. Alt lyset treff er tent; alt annet forblir mørkt. Denne teknikken er betydelig raskere enn strålesporing, samtidig som du gir deg en realistisk skyggeeffekt. Men problemet med stråling er dets restriktivitet; du har ikke mye plass til å jobbe med når du prøver å legge til effekter som refleksjoner. Vanligvis må du komme opp med noen form for kompromiss mellom stråling og strålingssporing, balanse mellom hastighet og visuelle effekter.

Hovedproblemet med begge disse teknikkene er at WebGL ikke gir deg tilgang til noen vinkler bortsett fra den aktive enheten.

Dette betyr at du enten må utføre alt på CPU-en (som tilordnet grafikkortet), eller du har gjort en ny skygge som beregner all belysning og lagrer informasjonen i en falsk tekstur. Du må da dekomprimere teksturdataene tilbake til belysningsinformasjonen og kartlegge den til kryssene. Så i utgangspunktet er den nåværende versjonen av WebGL ikke veldig godt egnet for dette. Jeg sier ikke det kan ikke gjøres, jeg sier bare at WebGL ikke hjelper deg.

Shadow Mapping

Ray tracing kan også være et levedyktig alternativ hvis søknaden din har svært få lys og gjenstander.

Et mye bedre alternativ til ray casting i WebGL kalles skygge kartlegging. Det gir deg samme effekt som stråling, men det bruker en annen tilnærming. Skygge kartlegging vil ikke løse alle dine problemer, men WebGL er halvoptimalisert for det. Du kan tenke på det som en slags hack, men skygge kartlegging brukes i ekte PC og konsoll applikasjoner.

Så hva er det du spør?

Du må forstå hvordan WebGL gjør sine scener for å svare på dette spørsmålet. WebGL skyver alle kryssene i vertex shader, som beregner de endelige koordinatene for hvert toppunkt etter at transformasjonene er brukt. Da for å spare tid, kaster WebGL bort kryssene som er skjult bak andre gjenstander, og trekker bare de viktigste objektene. Hvis du husker hvordan stråstøping fungerer, støtter det bare lysstråler på de synlige gjenstandene. Så vi stiller kameraets "kamera" til lyskildens koordinater og peker det i retningen vi vil ha lyset til ansiktet. Da fjerner WebGL automatisk alle kryssene som ikke er i lys av lyset. Vi kan da lagre disse dataene og bruke den når vi gjengir scenen til å vite hvilken av punktene som er tent.

Denne teknikken lyder bra på papir, men det har noen ulemper:

  • WebGL tillater ikke deg tilgang til dybdebufferen; du må være kreativ i fragment shader når du prøver å lagre disse dataene.
  • Selv om du lagrer alle dataene, må du likevel kartlegge den til toppunktene før de går inn i toppunktet når du gjengir scenen din. Dette krever ekstra CPU-tid.

Alle disse teknikkene krever en god del av tinkering med WebGL. Men jeg vil vise deg en veldig grunnleggende teknikk for å produsere et diffust lys for å gi en liten personlighet til dine gjenstander. Jeg ville ikke kalle det realistisk lys, men det gir definisjonene til objektene dine. Denne teknikken bruker objektets normalmatrise til å beregne vinkelen av lyset i forhold til objektets overflate. Det er raskt, effektivt og krever ingen hacking med WebGL. La oss komme i gang.


Legge til lys

La oss starte med å oppdatere shaders for å innlemme belysning. Vi må legge til en boolsk som bestemmer om objektet skal lyse eller ikke. Deretter trenger vi den faktiske normals vertex og forvandler den slik at den justerer seg med modellen. Til slutt må vi lage en variabel for å passere det endelige resultatet til fragmentskader. Dette er den nye vertex shader:

Hvis vi ikke bruker lys, så sender vi bare et tomt toppunkt til fragmentskader og fargen forblir den samme. Når lysene er slått på, beregner vi vinkelen mellom lysets retning og objektets overflate ved hjelp av prikkfunksjonen på normalt, og vi multipliserer resultatet av lysets farge som en slags maske for å legge på objektet.

Bilde av overflate normaler av Oleg Alexandrov.

Dette virker fordi normalene allerede er vinkelrett på objektets overflate, og prikkfunksjonen gir oss et tall basert på lysets vinkel til det normale. Hvis normal og lys er nesten parallelle, returnerer punktfunksjonen et positivt tall, noe som betyr at lyset vender mot overflaten. Når det normale og lyset er vinkelrett, er overflaten parallell med lyset, og funksjonen returnerer null. Alt høyere enn 90 grader mellom lyset og det normale resulterer i et negativt tall, men vi filtrerer dette ut med "max null" -funksjonen.

La meg vise deg fragment shader:

Denne shader er stort sett den samme fra tidligere deler av serien. Den eneste forskjellen er at vi multipliserer teksturens farge ved lysnivået. Dette lyser eller mørker forskjellige deler av objektet, noe som gir det litt dybde.

Det er alt for shaders, nå la oss gå til WebGL.js fil og modifiser våre to klasser.

Oppdaterer rammeverket vårt

La oss begynne med GLObject klasse. Vi må legge til en variabel for normals array. Her er hva den øverste delen av din GLObject skal nå se ut som:

funksjon GLObject (VertexArr, TriangleArr, TextureArr, ImageSrc, NormalsArr) this.Pos = X: 0, Y: 0, Z: 0; this.Scale = X: 1.0, Y: 1.0, Z: 1.0; this.Rotation = X: 0, Y: 0, Z: 0; this.Vertices = VertexArr; // Array for å holde normaldataene this.Normals = NormalsArr; // Resten av GLObject fortsetter her

Denne koden er ganske rett frem. La oss nå gå tilbake til HTML-filen og legge til normals-arrayet i vårt objekt.

I Klar() funksjon hvor vi laster vår 3D-modell, må vi legge til parameteren for normals-arrayet. En tom rekkefølge betyr at modellen ikke inneholder noen normalsdata, og vi må tegne objektet uten lys. I tilfelle at normals array inneholder data, vil vi bare sende den inn på GLObject gjenstand.

Vi må også oppdatere WebGL klasse. Vi må koble variabler til shaders umiddelbart etter at vi laster shaders. La oss legge til normals vertex; Koden din skal nå se slik ut:

// Link Vertex Position Egenskap fra Shader this.VertexPosition = this.GL.getAttribLocation (this.ShaderProgram, "VertexPosition"); this.GL.enableVertexAttribArray (this.VertexPosition); // Link Texture Coordinate Attribut fra Shader this.VertexTexture = this.GL.getAttribLocation (this.ShaderProgram, "TextureCoord"); this.GL.enableVertexAttribArray (this.VertexTexture); // Dette er den nye Normals-array-attributten this.VertexNormal = this.GL.getAttribLocation (this.ShaderProgram, "VertexNormal"); this.GL.enableVertexAttribArray (this.VertexNormal);

Neste, la oss oppdatere PrepareModel () funksjon og legg til noe kode for å buffere normals data når den er tilgjengelig. Legg til den nye koden rett før Model.Ready uttalelse nederst:

hvis (false! == Model.Normals) Buffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Buffer); this.GL.bufferData (this.GL.ARRAY_BUFFER, ny Float32Array (Model.Normals), this.GL.STATIC_DRAW); Model.Normals = Buffer;  Model.Ready = true;

Sist men ikke minst, oppdater den faktiske Tegne funksjon for å inkorporere alle disse endringene. Det er et par endringer her, så vær med meg. Jeg skal gå stykke for stykke gjennom hele funksjonen:

this.Draw = funksjon (Modell) if (Model.Image.ReadyState == true && Model.Ready == false) this.PrepareModel (Model);  hvis (Model.Ready) this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Model.Vertices); this.GL.vertexAttribPointer (this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Model.TextureMap); this.GL.vertexAttribPointer (this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);

Hittil er det samme som før. Nå kommer normalenes del:

 // Sjekk for Normaler hvis (false! == Model.Normals) // Koble Normals buffer til Shader this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Model.Normals); this.GL.vertexAttribPointer (this.VertexNormal, 3, this.GL.FLOAT, false, 0, 0); // Fortell Shader å bruke belysning var UseLights = this.GL.getUniformLocation (this.ShaderProgram, "UseLights"); this.GL.uniform1i (UseLights, true);  ellers // Selv om vårt objekt ikke har noen normale data, må vi fortsatt passere noe // Så jeg passerer i Vertices i stedet this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Model.Vertices); this.GL.vertexAttribPointer (this.VertexNormal, 3, this.GL.FLOAT, false, 0, 0); // Fortell Shader å bruke belysning var UseLights = this.GL.getUniformLocation (this.ShaderProgram, "UseLights"); this.GL.uniform1i (UseLights, false); 

Vi ser etter om modellen har normalsdata. Hvis det er tilfelle, kobles det til bufferen og setter den boolske. Hvis ikke, trenger shader fortsatt noen slags data, eller det vil gi deg en feil. Så i stedet passerte jeg vertices buffer og satt UseLight boolsk til falsk. Du kan komme seg rundt dette ved å bruke flere shaders, men jeg trodde det ville være enklere for det vi prøver å gjøre.

 this.GL.bindBuffer (this.GL.ELEMENT_ARRAY_BUFFER, Model.Triangles); // Generer Perspektiv Matrix var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 1000.0); var TransformMatrix = Model.GetTransforms ();

Igjen er denne delen av funksjonen fortsatt den samme.

 var NormalsMatrix = MatrixTranspose (InverseMatrix (TransformMatrix));

Her beregner vi normals transformasjonsmatrisen. Jeg vil diskutere MatrixTranspose () og InverseMatrix () Fungerer om et minutt. For å beregne transformasjonsmatrisen for normals-arrayet, må du omdanne den inverse matrisen til objektets vanlige transformasjonsmatrise. Mer om dette senere.

 // Sett spor 0 som den aktive Texturen this.GL.activeTexture (this.GL.TEXTURE0); // Legg i tekstur til minne this.GL.bindTexture (this.GL.TEXTURE_2D, Model.Image); // Oppdater Tekstur Sampler i fragmentet skygger for å bruke spor 0 this.GL.uniform1i (this.GL.getUniformLocation (this.ShaderProgram, "uSampler"), 0); // Sett perspektiver og transformasjonsmatriser var pmatrix = this.GL.getUniformLocation (this.ShaderProgram, "PerspectiveMatrix"); this.GL.uniformMatrix4fv (pmatrix, false, new Float32Array (PerspectiveMatrix)); var tmatrix = this.GL.getUniformLocation (this.ShaderProgram, "TransformationMatrix"); this.GL.uniformMatrix4fv (tmatrix, false, new Float32Array (TransformMatrix)); var nmatrix = this.GL.getUniformLocation (this.ShaderProgram, "NormalTransformation"); this.GL.uniformMatrix4fv (nmatrix, false, new Float32Array (NormalsMatrix)); // Tegn Trianglene this.GL.drawElements (this.GL.TRIANGLES, Model.TriangleCount, this.GL.UNSIGNED_SHORT, 0); ;

Du kan enkelt se kilden til et hvilket som helst WebGL-program for å lære mer.

Dette er resten av Tegne() funksjon. Det er nesten det samme som før, men det er den ekstra koden som forbinder normalmatrisen til shaders. Nå, la oss gå tilbake til de to funksjonene jeg pleide å få normals transformasjonsmatrisen.

De InverseMatrix () funksjonen aksepterer en matrise og returnerer sin inverse matrise. En invers matris er en matrise som, når den multipliceres med den opprinnelige matrisen, returnerer en identitetsmatrise. La oss se på et grunnleggende algebra eksempel for å avklare dette. Den inverse av nummer 4 er 1/4 fordi når 1/4 x 4 = 1. Den "en" ekvivalenten i matriser er en identitetsmatrise. derfor InverseMatrix () funksjonen returnerer identitetsmatrisen for argumentet. Her er denne funksjonen:

 funksjon InverseMatrix (A) var s0 = A [0] * A [5] - A [4] * A [1]; var s1 = A [0] * A [6] - A [4] * A [2]; var s2 = A [0] * A [7] - A [4] * A [3]; var s3 = A [1] * A [6] - A [5] * A [2]; var s4 = A [1] * A [7] - A [5] * A [3]; var s5 = A [2] * A [7] - A [6] * A [3]; var c5 = A [10] * A [15] - A [14] * A [11]; var c4 = A [9] * A [15] - A [13] * A [11]; var c3 = A [9] * A [14] - A [13] * A [10]; var c2 = A [8] * A [15] - A [12] * A [11]; var c1 = A [8] * A [14] - A [12] * A [10]; var c0 = A [8] * A [13] - A [12] * A [9]; var invdet = 1.0 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0); var B = []; B [0] = (A [5] * c5 - A [6] * c4 + A [7] * c3) * invdet; B [1] = (-A [1] * c5 + A [2] * c4 - A [3] * c3) * invdet; B [2] = (A [13] * s5 - A [14] * s4 + A [15] * s3) * invdet; B [3] = (-A [9] * s5 + A [10] * s4 - A [11] * s3) * invdet; B [4] = (-A [4] * c5 + A [6] * c2 - A [7] * c1) * invdet; B [5] = (A [0] * c5 - A [2] * c2 + A [3] * c1) * invdet; B [6] = (-A [12] * s5 + A [14] * s2 - A [15] * s1) * invdet; B [7] = (A [8] * s5 - A [10] * s2 + A [11] * s1) * invdet; B [8] = (A [4] * c4 - A [5] * c2 + A [7] * c0) * invdet; B [9] = (-A [0] * c4 + A [1] * c2 - A [3] * c0) * invdet; B [10] = (A [12] * s4 - A [13] * s2 + A [15] * s0) * invdet; B [11] = (-A [8] * s4 + A [9] * s2 - A [11] * s0) * invdet; B [12] = (-A [4] * c3 + A [5] * c1 - A [6] * c0) * invdet; B [13] = (A [0] * c3 - A [1] * c1 + A [2] * c0) * invdet; B [14] = (-A [12] * s3 + A [13] * s1 - A [14] * s0) * invdet; B [15] = (A [8] * s3 - A [9] * s1 + A [10] * s0) * invdet; returnere B; 

Denne funksjonen er ganske komplisert, og for å fortelle deg sannheten, forstår jeg ikke helt hvorfor matematikken fungerer. Men jeg har allerede forklart kjernen av det ovenfor. Jeg kom ikke opp med denne funksjonen; Den ble skrevet i ActionScript av Robin Hilliard.

Den neste funksjonen, MatrixTranspose (), er mye enklere å forstå. Den returnerer den "transponerte" versjonen av sin inngangsmatrise. Kort sagt, det roterer bare matrisen på sin side. Her er koden:

Funksjon MatrixTranspose (A) return [A [0], A [4], A [8], A [12], A [1], A [5], A [9], A [13], A [ 2], A [6], A [10], A [14], A [3], A [7], A [11], A [15]]; 

I stedet for å gå i horisontale rader (dvs. A [0], A [1], A [2] ...) går denne funksjonen vertikalt (A [0], A [4], A [8] ...).

Du er god til å gå etter at du har lagt til disse to funksjonene til din WebGL.js fil, og enhver modell som inneholder normaldataene skal være skyggelagt. Du kan leke med lysets retning og farge i vertex shader for å få forskjellige effekter.

Det er et siste emne som jeg ønsker å dekke, og det er å legge 2D-innhold til vår scene. Legge til 2D-komponenter på en 3D-scene kan ha mange fordeler. Det kan for eksempel brukes til å vise koordinatinformasjon, et mini kart, instruksjoner for appen din, og listen fortsetter. Denne prosessen er ikke like rett frem som du kanskje tror, ​​så la oss sjekke det ut.


2D V.S. 2.5D

HTML vil ikke la deg bruke WebGL API og 2D API fra samme lerret.

Du kan tenke, "Hvorfor ikke bare bruke lerretet som er innebygd i HTML5 2D API?" Vel, problemet er at HTML ikke lar deg bruke WebGL API og 2D API fra samme lerret. Når du har tildelt lerretets kontekst til WebGL, kan du ikke bruke det med 2D API. HTML5 returnerer bare null når du prøver å få 2D-konteksten. Så hvordan går det da? Vel, jeg gir deg to alternativer.

2.5D

2.5D, for de som er uvitende, er når du legger 2D objekter (objekter uten dybde) i en 3D-scene. Legge til tekst på en scene er et eksempel på 2.5D. Du kan ta teksten fra et bilde og bruke det som en tekstur til et 3D-plan, eller du kan få en 3D-modell for teksten og gjøre den på skjermen.

Fordelene med denne tilnærmingen er at du ikke trenger to lerret, og det ville være raskere å tegne hvis du bare brukte enkle figurer i søknaden din.

Men for å gjøre ting som tekst, må du enten ha bilder av alt du vil skrive, eller en 3D-modell for hvert brev (litt over toppen, etter min mening).

2D

Alternativet er å lage et annet lerret og legge det over på 3D-lerretet. Jeg foretrekker denne tilnærmingen fordi det virker bedre rustet til å tegne 2D-innhold. Jeg skal ikke begynne å lage et nytt 2D-rammeverk, men la oss bare lage et enkelt eksempel der vi viser koordinatene til modellen sammen med den nåværende rotasjonen. La oss legge til et annet lerret i HTML-filen rett etter WebGL-lerretet. Her er det nye lerretet sammen med den nåværende:

  Din nettleser støtter ikke HTML5s lerret.   Din nettleser støtter ikke HTML5s lerret. 

Jeg har også lagt til noen inline CSS for å legge over det andre lerretet på toppen av det første. Det neste trinnet er å skape en variabel for 2D-lerretet og få sin kontekst. Jeg skal gjøre dette i Klar() funksjon. Din oppdaterte kode skal se slik ut:

var GL; bygningen; var Canvas2D; funksjon Klar () // Gl Deklarasjon og lastmodellfunksjon Her Canvas2D = document.getElementById ("2DCanvas"). getContext ("2d"); Canvas2D.fillStyle = "# 000"; 

Øverst kan du se at jeg la til en global variabel for 2D-lerretet. Deretter la jeg to linjer til bunnen av Klar() funksjon. Den første nye linjen får 2D-konteksten, og den andre nye linjen setter fargen til svart.

Det siste trinnet er å tegne teksten inne i Oppdater() funksjon:

funksjon Oppdatering () Building.Rotation.Y + = 0.3 // Fjern lerretet fra forrige tegning Canvas2D.clearRect (0, 0, 600, 400); // Tittel Tekst Canvas2D.font = "25px sans-serif"; Canvas2D.fillText ("Building", 20, 30); // Objektets Properties Canvas2D.font = "16px sans-serif"; Canvas2D.fillText ("X:" + Building.Pos.X, 20, 55); Canvas2D.fillText ("Y:" + Building.Pos.Y, 20, 75); Canvas2D.fillText ("Z:" + Building.Pos.Z, 20, 95); Canvas2D.fillText ("Rotasjon:" + Math.floor (Building.Rotation.Y), 20, 115); GL.GL.clear (16384 | 256); GL.Draw (Building); 

Vi begynner med å rotere modellen på sin y-akse, og så fjerner vi 2D-lerretet av noe tidligere innhold. Deretter setter vi skriftstørrelsen og tegner litt tekst for hver akse. De fillText () Metoden aksepterer tre parametre: teksten som skal tegnes, x-koordinaten og y-koordinaten.

Enkelheten taler for seg selv. Dette kan ha vært litt overkill for å tegne litt enkel tekst; du kunne lett skrive inn teksten i en posisjon

eller

element. Men hvis du gjør noe som tegner figurer, sprites, en helsestang, etc, så er dette sannsynligvis det beste alternativet.


Siste tanker

I omfanget av de tre siste opplæringen skapte vi en ganske fin, om enn grunnleggende 3D-motor. Til tross for sin primitive natur, gir den deg en solid base for å fungere. Fortsett, foreslår jeg å se på andre rammer som three.js eller glge for å få en ide om hva som er mulig. I tillegg kjører WebGL i nettleseren, og du kan enkelt se kilden til et hvilket som helst WebGL-program for å lære mer.

Jeg håper du har hatt denne opplæringsserien, og som alltid, la dine kommentarer og spørsmål stå i kommentarseksjonen nedenfor.