WebGL Essentials Del I

WebGL er en 3D-gjengivelse i nettleser basert på OpenGL, som lar deg vise 3D-innholdet ditt direkte inn i en HTML5-side. I denne opplæringen vil jeg dekke alle de grunnleggende elementene du trenger for å komme i gang med å bruke dette rammeprogrammet.


Introduksjon

Det er et par ting du bør vite før vi kommer i gang. WebGL er en JavaScript API som gjør 3D-innhold til et HTML5 lerret. Det gjør dette ved å bruke to skript som er kjent i "3D verden" som shaders. De to shaders er:

  • Vertex shader
  • Fragmentet skygger

Nå ikke bli for nervøs når du hører disse navnene; Det er bare en fancy måte å si "henholdsvis posisjonskalkulator" og "fargekoder". Fragmentfargeren er lettere å forstå; det forteller bare WebGL hvilken farge et bestemt punkt på modellen din skal være. Vertex shader er litt mer teknisk, men i utgangspunktet konverterer poengene i 3D-modellene til 2D-koordinater. Fordi alle dataskjermer er flate 2D-overflater, og når du ser 3D-objekter på skjermen, er de bare en illusjon av perspektiv.

Hvis du vil vite nøyaktig hvordan denne beregningen fungerer, må du spørre en matematiker, fordi den bruker avanserte 4 x 4 matriksmultiplikasjoner, som er litt utover "Essentials" -opplæringen. Heldigvis trenger du ikke å vite hvordan det fungerer fordi WebGL vil ta vare på det meste. Så la oss komme i gang.


Trinn 1: Konfigurere WebGL

WebGL har mange små innstillinger som du må sette opp nesten hver gang du tegner noe på skjermen. For å spare tid og gjøre koden din nydelig, skal jeg lage et JavaScript-objekt som vil inneholde alle "bakom scenen" ting i en egen fil. For å komme i gang, opprett en ny fil kalt 'WebGL.js' og legg inn følgende kode i den:

funksjonen WebGL (CID, FSID, VSID) var canvas = document.getElementById (CID); hvis (! canvas.getContext ("webgl") &&! canvas.getContext ("eksperimental-webgl")) varsling ("Din nettleser støtter ikke WebGL"); ellers this.GL = (canvas.getContext ("webgl"))? canvas.getContext ("webgl"): canvas.getContext ("eksperimental-webgl"); this.GL.clearColor (1.0, 1.0, 1.0, 1.0); // dette er fargen this.GL.enable (this.GL.DEPTH_TEST); // Aktiver dybdeprøving this.GL.depthFunc (this.GL.LEQUAL); // Sett perspektiv Se dette.AspectRatio = canvas.width / canvas.height; // Last Shaders Here

Denne konstruktørfunksjonen tar inn lerretets ID og de to shaderobjektene. Først får vi lerretelementet og sørger for at det støtter WebGL. Hvis det gjør det, tilordner vi WebGL-konteksten til en lokal variabel kalt "GL". Den klare fargen er bare bakgrunnsfargen, og det er verdt å merke seg at i WebGL går de fleste parametrene fra 0,0 til 1,0, så du må dele dine rgb-verdier med 255. Så i vårt eksempel 1.0, 1.0, 1.0, 1.0 betyr En hvit bakgrunn med 100% synlighet (ingen gjennomsiktighet). De neste to linjene forteller WebGL å beregne dybde og perspektiv, slik at en gjenstand nærmere deg vil blokkere objekter bak den. Til slutt bestemmer vi aspektforholdet som beregnes ved å dele lerretets bredde med sin høyde.

Før vi fortsetter og laster de to shaders, la oss skrive dem. Jeg skal skrive disse i HTML-filen der vi skal sette selve lerretelementet. Opprett en HTML-fil, og plasser følgende to skriptelementer like før den avsluttende kroppstegnet:

 

Vertex shader er opprettet først, og vi definerer to attributter:

  • toppunktet, som er plasseringen i x-, y- og z-koordinatene til det nåværende toppunktet (Punkt i modellen)
  • tekstur koordinat; plasseringen i teksturbildet som skal tilordnes til dette punktet

Deretter oppretter vi variabler for transformasjons- og perspektivmatriser. Disse brukes til å konvertere 3D-modellen til et 2D-bilde. Den neste linjen oppretter en delt variabel til fragmentet shader, og i hovedfunksjonen beregner vi gl_Position (den endelige 2D-stillingen). Vi tilordner deretter den nåværende teksturkoordinat til den delte variabelen.

I fragmentskader tar vi bare de koordinatene vi definerte i vertex shader og vi "prøver" tekstur på den koordinaten. I utgangspunktet får vi bare fargen i tekstur som tilsvarer det nåværende punktet på vår geometri.

Nå som vi har skrevet shaders, kan vi gå tilbake for å laste dem i vår JS-fil. Så erstatt "// Load Shaders Here" med følgende kode:

var FShader = document.getElementById (FSID); var VShader = document.getElementById (VSID); hvis (! FShader ||! VShader) varsling ("Feil, kan ikke finne Shaders"); ellers // Load and Compile Fragment Shader var Code = LoadShader (FShader); FShader = this.GL.createShader (this.GL.FRAGMENT_SHADER); this.GL.shaderSource (FShader, Code); this.GL.compileShader (FShader); // Last og kompilere Vertex Shader Code = LoadShader (VShader); VShader = this.GL.createShader (this.GL.VERTEX_SHADER); this.GL.shaderSource (VShader, Code); this.GL.compileShader (VShader); // Opprett skyggeprogrammet this.ShaderProgram = this.GL.createProgram (); this.GL.attachShader (this.ShaderProgram, FShader); this.GL.attachShader (this.ShaderProgram, VShader); this.GL.linkProgram (this.ShaderProgram); this.GL.useProgram (this.ShaderProgram); // 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); 

Dine teksturer må være i størrelser med jevne byte, eller du får en feil ... som 2x2, 4x4, 16x16, 32x32 ...

Vi sørger først for at shaders eksisterer, og så fortsetter vi å laste dem en om gangen. Prosessen får i utgangspunktet shaderens kildekode, kompilerer den, og legger den til det sentrale shader-programmet. Det er en funksjon, kalt LoadShader, som får skyggekoden fra HTML-filen. vi kommer til det på et sekund. Vi bruker "shader-programmet" for å knytte de to shaders sammen, og det gir oss tilgang til deres variabler. Vi lagrer de to attributene vi definerte i shaders; slik at vi kan legge inn geometrien til dem senere.

La oss se på LoadShader-funksjonen. Du bør sette dette utenfor WebGL-funksjonen:

funksjon LoadShader (Script) var Code = ""; var CurrentChild = Script.firstChild; mens (CurrentChild) if (CurrentChild.nodeType == CurrentChild.TEXT_NODE) ​​Kode + = CurrentChild.textContent; CurrentChild = CurrentChild.nextSibling;  returkode; 

Det sykler i utgangspunktet bare gjennom skyggeren og samler kildekoden.


Trinn 2: Den "enkle" kuben

For å tegne objekter i WebGL skal du ha følgende tre arrays:

  • toppunkter; poengene som utgjør dine objekter
  • trekanter; forteller WebGL hvordan du kan koble hjørnene til overflater
  • tekstur koordinater; definerer hvordan kryssene er kartlagt på teksturbildet

Dette kalles UV-kartlegging. For vårt eksempel, la oss lage en grunnleggende terning. Jeg vil dele kuben i 4 hjørner per side som kobles til to trekanter. la oss lage en variabel som vil holde en terningens arrays.

var Cube = Vertices: [// X, Y, Z Koordinater // Front 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, // Tilbake 1,0, 1,0, 1,0, 1,0, -1,0, 1,0, -1,0, 1,0, 1,0, -1,0, -1,0, 1,0, // Høyre 1,0, 1,0, 1,0, 1,0, -1,0, 1,0, 1,0, 1,0 , -1,0, 1,0, -1,0, -1,0, // venstre -1,0, 1,0, 1,0, -1,0, -1,0, 1,0, -1,0, 1,0, -1,0, -1,0, -1,0, -1,0, // topp 1,0, 1,0, 1,0, -1,0, -1,0, 1,0, 1,0, -1,0, -1,0, -1,0, -1,0, -1,0, / / ​​bunn 1,0, -1,0, 1,0, -1,0, -1,0, 1,0, 1,0 , -1.0, -1.0, -1.0, -1.0, -1.0], Triangles: [// Også i tre grupper for å definere de tre punktene i hver trekant // Tallene her er indeks tallene i vertexarrangementet // Front 0, 1, 2, 1, 2, 3, / / ​​Tilbake 4, 5, 6, 5, 6, 7, // Høyre 8, 9, 10, 9, 10, 11, // Venstre 12, 13, 14, 13, 14, 15, // Topp 16, 17, 18, 17, 18, 19, // Bunn 20, 21, 22, 21, 22, 23], Texture: [// Dette arrayet er i grupper av to, x- og y-koordinatene (aka U, V) i teksten // Tallene går fra 0,0 til 1,0, Ett par for hvert toppunkt // Front 1.0, 1.0, 1.0, 0.0 , 0,0, 1,0, 0,0, 0,0, / / ​​Tilbake 0,0, 1,0, 0,0, 0,0, 1,0, 1,0, 1,0, 0,0, // Høyre 1,0, 1,0, 1,0, 0,0, 0,0, 1,0, 0,0, 0,0, // Venstre 0,0, 0,0, 1,0, 1,0, 0,0, 1,0, 1,0, 0,0, 1,0, 1,0, 0,0, 1,0, 1,0, 0,0, 1,0, 1,0];

Det kan virke som mye data for en enkel kube, men i del to av denne opplæringen vil jeg lage et skript som vil importere 3D-modellene, slik at du ikke trenger å bekymre deg for å beregne disse.

Du kan også lure på hvorfor jeg laget 24 poeng (4 for hver side), når det egentlig bare er åtte totalt unike poeng på en terning? Jeg gjorde dette fordi du bare kan tilordne en tekstur koordinat per vertex; så hvis vi bare ville sette inn de 8 poengene, så måtte hele kuben se på det samme fordi det ville vikle tekstur rundt alle sidene som toppunktet berører. Men denne måten har hver side sine egne poeng, slik at vi kan legge en annen del av tekstur på hver side.

Vi har nå denne kubevariabelen og er klare til å begynne å tegne den. La oss gå tilbake til WebGL-metoden og legge til en Tegne funksjon.


Trinn 3: Tegnefunksjonen

Prosedyren for tegning av objekter i WebGL har mange trinn; så det er en god ide å gjøre en funksjon for å forenkle prosessen. Den grunnleggende ideen er å laste de tre arrayene i WebGL buffere. Vi kobler deretter disse buffere til egenskapene vi definerte i shaders sammen med transformasjons- og perspektivmatriser. Deretter må vi legge teksten i minnet, og til slutt kan vi ringe til tegne kommando. Så la oss komme i gang.

Følgende kode går inn i WebGL-funksjonen:

this.Draw = funksjonen (Object, Texture) var VertexBuffer = this.GL.createBuffer (); // Opprett en ny buffer // bind den som den nåværende bufferen this.GL.bindBuffer (this.GL.ARRAY_BUFFER, VertexBuffer); // Fyll det med data this.GL.bufferData (this.GL.ARRAY_BUFFER, ny Float32Array (Object.Vertices), this.GL.STATIC_DRAW); // Koble buffer til Shader's attributt this.GL.vertexAttribPointer (this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); // Gjenta for de neste to var TextureBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, TextureBuffer); this.GL.bufferData (this.GL.ARRAY_BUFFER, ny Float32Array (Object.Texture), this.GL.STATIC_DRAW); this.GL.vertexAttribPointer (this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);
 var TriangleBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ELEMENT_ARRAY_BUFFER, TriangleBuffer); // Generer Perspektiv Matrix var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 10000.0); var TransformMatrix = MakeTransform (Object); // 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, Texture); // 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)); // Tegn trekantene this.GL.drawElements (this.GL.TRIANGLES, Object.Trinagles.length, this.GL.UNSIGNED_SHORT, 0); ;

Vertex shader posisjonerer, roterer og skalerer objektet ditt basert på transformasjons- og perspektivmatriser. Vi vil gå mer i dybden i forandringer i den andre delen av denne serien.

Jeg har lagt til to funksjoner: MakePerspective () og MakeTransform (). Disse genererer bare de nødvendige 4x4-matrices for WebGL. De MakePerspective () funksjonen aksepterer det vertikale synsfeltet, aspektforholdet og de nærmeste og lengste punktene som argumenter. Alt som er nærmere enn 1 enhet og lenger enn 10000 enheter vil ikke bli vist, men du kan redigere disse verdiene for å få effekten du leter etter. La oss nå se på disse to funksjonene:

funksjon MakePerspective (FOV, AspectRatio, Nærmeste, Farest) var YLimit = Nærmeste * Math.tan (FOV * Math.PI / 360); var A = - (Farest + Nærmeste) / (Farest - Nærmeste); var B = -2 * Farest * Nærmeste / (Farst - Nærmeste); var C = (2 * nærmeste) / ((YLimit * AspectRatio) * 2); Var D = (2 * Nærmeste) / (YLimit * 2); returnere [C, 0, 0, 0, 0, D, 0, 0, 0, 0, A, -1, 0, 0, B, 0];  funksjon MakeTransform (Object) return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -6, 1]; 

Begge disse matrices påvirker objektets endelige utseende, men perspektivmatrisen redigerer din 3D-verden som synsfeltet og de synlige objekter, mens transformasjonsmatrisen endrer de enkelte objekter som rotasjonsskala og posisjon. Med dette gjort er vi nesten klare til å tegne, alt som er igjen er en funksjon for å konvertere et bilde til en WebGL tekstur.


Trinn 4: Laster inn teksturer

Lasting av en tekstur er en to-trinns prosess. Først må vi laste inn et bilde som du ville i et standard JavaScript-program, og da må vi konvertere det til en WebGL-tekstur. Så la oss starte med den andre delen siden vi allerede er i JS-filen. Legg til følgende nederst i WebGL-funksjonen rett etter kommandoen Draw:

this.LoadTexture = funksjon (Img) // Opprett en ny tekstur og tilordne den som den aktive var var TempTex = this.GL.createTexture (); this.GL.bindTexture (this.GL.TEXTURE_2D, TempTex); // Flip Positive Y (Valgfritt) this.GL.pixelStorei (this.GL.UNPACK_FLIP_Y_WEBGL, true); // Legg inn bildet this.GL.texImage2D (this.GL.TEXTURE_2D, 0, this.GL.RGBA, this.GL.RGBA, this.GL.UNSIGNED_BYTE, Img); // Setup Scaling egenskaper this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MAG_FILTER, this.GL.LINEAR); this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MIN_FILTER, this.GL.LINEAR_MIPMAP_NEAREST); this.GL.generateMipmap (this.GL.TEXTURE_2D); // Unbind tekstur og returnere den. this.GL.bindTexture (this.GL.TEXTURE_2D, null); returnere TempTex; ;

Det er verdt å merke seg at teksturene dine må ligge i størrelser med jevne byte, eller du vil få en feil; så må de være dimensjoner, som 2x2, 4x4, 16x16, 32x32 og så videre. Jeg la til linjen for å vende Y-koordinatene ganske enkelt fordi min 3D-applikasjons Y-koordinater var bakover, men det vil avhenge av hva du bruker. Dette skyldes at noen programmer gjør 0 i Y-aksen øverst til venstre og noen programmer gjør det nederst til venstre. Skaleringsegenskapene som jeg angir, forteller bare for WebGL hvordan bildet skal skaleres opp og ned. Du kan leke med forskjellige alternativer for å få forskjellige effekter, men jeg trodde at disse fungerte best.

Nå som vi er ferdige med JS-filen, la oss gå tilbake til HTML-filen og implementere alt dette.


Trinn 5: Pakker det opp

Som nevnt tidligere, gjør WebGL seg til et lerretelement. Det er alt vi trenger i kroppsseksjonen. Etter at du har lagt til lerretelementet, bør HTML-siden din se ut som følgende:

        Din nettleser støtter ikke HTML5s lerret.     

Det er en ganske enkel side. I hovedområdet har jeg koblet til vår JS-fil. La oss nå implementere Klar-funksjonen, som blir kalt når siden laster:

// Dette vil holde vår WebGL-variabel var GL; // Våre ferdige tekstur Textur; // Dette vil holde teksturen image var TextureImage; funksjon Klar () GL = ny WebGL ("GLCanvas", "FragmentShader", "VertexShader"); TextureImage = nytt bilde (); TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); GL.Draw (Cube, Texture); ; TextureImage.src = "Texture.png"; 

Så vi lager et nytt WebGL-objekt og sender inn ID-ene for lerret og shaders. Deretter laster vi inn teksturbildet. En gang lastet, kaller vi Tegne() Metoden med kuben og strukturen. Hvis du fulgte med, bør skjermen ha en statisk kube med en tekstur på den.

Nå, selv om jeg sa at vi skal dekke transformasjoner neste gang, kan jeg ikke bare forlate deg med et statisk torg; det er ikke 3D nok. La oss gå tilbake og legge til en liten rotasjon. I HTML-filen, endre på Last funksjon å se slik ut:

TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); setInterval (Oppdater, 33); ;

Dette vil ringe en funksjon som heter Oppdater() hver 33 millisekunder som gir oss en bildefrekvens på rundt 30 fps. Her er oppdateringsfunksjonen:

funksjon Oppdatering () GL.GL.clear (16384 | 256); GL.Draw (GL.Cube, Texture); 

Dette er en ganske enkel funksjon; det rydder skjermen og trekker deretter den oppdaterte kuben. Nå, la oss gå til JS-filen for å legge til rotasjonskoden.


Trinn 6: Legge til noen spinn

Jeg skal ikke fullt ut implementere transformasjoner, fordi jeg lagrer det for neste gang, men la oss legge til en rotasjon rundt Y-aksen. Det første du må gjøre er å legge til en rotasjonsvariabel i kubeobjektet. Dette vil holde oversikt over den nåværende vinkelen, og la oss fortsette å øke rotasjonen. Så toppen av Cube-variabelen din skal se slik ut:

var Cube = Rotasjon: 0, // De andre tre arrays;

La oss nå oppdatere MakeTransform () funksjon for å innlemme rotasjonen:

funksjon MakeTransform (Object) var y = Object.Rotation * (Math.PI / 180.0); var A = Math.cos (y); var B = -1 * Math.sin (y); var C = Math.sin (y); var D = Math.cos (y); Object.Rotation + = .3; returnere [A, 0, B, 0, 0, 1, 0, 0, C, 0, D, 0, 0, 0, -6, 1]; 

Konklusjon

Og det er det! I neste opplæring vil vi dekke laste modeller og utføre transformasjoner. Jeg håper du likte denne opplæringen; Gi gjerne spørsmål eller kommentarer som du kanskje har under.