WebGL is een 3D-renderer in de browser op basis van OpenGL, waarmee u uw 3D-inhoud direct op een HTML5-pagina kunt weergeven. In deze tutorial zal ik alle essentiële dingen bespreken die je nodig hebt om aan de slag te gaan met dit framework.
Er zijn een aantal dingen die u moet weten voordat we aan de slag gaan. WebGL is een JavaScript-API die 3D-inhoud weergeeft op een HTML5-canvas. Het doet dit door twee scripts te gebruiken die bekend zijn in de "3D-wereld" als shaders. De twee shaders zijn:
Word nu niet te nerveus als je deze namen hoort; het is gewoon een mooie manier om respectievelijk "positiecalculator" en "kleurenkiezer" te zeggen. De fragmentshader is gemakkelijker om te begrijpen; het vertelt WebGL eenvoudig welke kleur een bepaald punt op uw model zou moeten hebben. De vertex-arcering is iets technischer, maar in feite converteert deze de punten in uw 3D-modellen naar 2D-coördinaten. Omdat alle computerbeeldschermen platte 2D-oppervlakken zijn en wanneer u 3D-objecten op uw scherm ziet, zijn ze slechts een illusie van perspectief.
Als u precies wilt weten hoe deze berekening werkt, moet u een wiskundige vragen, omdat deze geavanceerde 4 x 4 matrixvermenigvuldigingen gebruikt, die een beetje verder gaan dan de zelfstudie 'Essentials'. Gelukkig hoef je niet te weten hoe het werkt, want WebGL zal er het meeste voor zorgen. Dus laten we beginnen.
WebGL heeft veel kleine instellingen die je bijna elke keer dat je iets naar het scherm tekent moet instellen. Om tijd te besparen en je code netjes te maken, ga ik een JavaScript-object maken dat alle 'behind the scene'-dingen in een afzonderlijk bestand bevat. Maak om te beginnen een nieuw bestand met de naam 'WebGL.js' en plaats de volgende code erin:
function WebGL (CID, FSID, VSID) var canvas = document.getElementById (CID); if (! canvas.getContext ("webgl") &&! canvas.getContext ("experimental-webgl")) alert ("Uw browser ondersteunt WebGL niet"); else this.GL = (canvas.getContext ("webgl"))? canvas.getContext ("webgl"): canvas.getContext ("experimentental-webgl"); this.GL.clearColor (1.0, 1.0, 1.0, 1.0); // dit is de kleur this.GL.enable (this.GL.DEPTH_TEST); // Schakel Depthtesting this.GL.depthFunc (this.GL.LEQUAL) in; // Stel perspectief in Bekijk dit.AspectRatio = canvas.width / canvas.height; // Shaders laden hier
Deze constructorfunctie neemt de ID's van het canvas en de twee shader-objecten op. Eerst krijgen we het canvas-element en zorgen we ervoor dat het WebGL ondersteunt. Als dit het geval is, wijzen we de WebGL-context toe aan een lokale variabele met de naam "GL". De heldere kleur is gewoon de achtergrondkleur, en het is vermeldenswaard dat in WebGL de meeste parameters van 0,0 naar 1,0 gaan, dus je zou je rgb-waarden met 255 moeten delen. Dus in ons voorbeeld 1.0, 1.0, 1.0, 1.0 betekent een witte achtergrond met 100% zichtbaarheid (geen transparantie). De volgende twee regels vertellen WebGL om diepte en perspectief te berekenen, zodat een object dichterbij u objecten erachter blokkeert. Ten slotte stellen we de beeldverhouding in die wordt berekend door de breedte van het canvas te delen door zijn hoogte.
Voordat we verder gaan en de twee shaders laden, laten we ze schrijven. Ik ga deze in het HTML-bestand schrijven waar we het daadwerkelijke canvaselement gaan plaatsen. Maak een HTML-bestand en plaats de volgende twee scriptelementen vlak voor de afsluitende body-tag:
De hoekpuntshader wordt als eerste gemaakt en we definiëren twee kenmerken:
Vervolgens maken we variabelen voor de transformatie- en perspectiefmatrices. Deze worden gebruikt om het 3D-model om te zetten in een 2D-afbeelding. De volgende regel maakt een gedeelde variabele voor de fragmentshader en in de hoofdfunctie berekenen we de gl_Position (de uiteindelijke 2D-positie). Vervolgens wijzen we de 'huidige textuurcoördinaat' toe aan de gedeelde variabele.
In de fragmentshader nemen we gewoon de coördinaten die we in de hoekshader hebben gedefinieerd en we 'samplen' de textuur op die coördinaat. In feite krijgen we alleen de kleur in de textuur die overeenkomt met het huidige punt op onze geometrie.
Nu we de shaders hebben geschreven, kunnen we teruggaan om ze in ons JS-bestand te laden. Vervang dus de "// Shaders laden hier" met de volgende code:
var FShader = document.getElementById (FSID); var VShader = document.getElementById (VSID); if (! FShader ||! VShader) alert ("Error, Could not Find Shaders"); else // 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); // Load en compileer Vertex Shader Code = LoadShader (VShader); VShader = this.GL.createShader (this.GL.VERTEX_SHADER); this.GL.shaderSource (VShader, Code); this.GL.compileShader (VShader); // Maak het Shader-programma 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 Positie Attribuut van Shader this.VertexPosition = this.GL.getAttribLocation (this.ShaderProgram, "VertexPosition"); this.GL.enableVertexAttribArray (this.VertexPosition); // Link Textuur Coordinate Attribuut van Shader this.VertexTexture = this.GL.getAttribLocation (this.ShaderProgram, "TextureCoord"); this.GL.enableVertexAttribArray (this.VertexTexture);
Uw texturen moeten een even byte-formaat hebben, anders krijgt u een foutmelding ... zoals 2x2, 4x4, 16x16, 32x32 ...
We zorgen er eerst voor dat de shaders bestaan en gaan vervolgens door met het laden van ze een voor een. Het proces krijgt in principe de broncode van de arcering, compileert deze en maakt deze aan het centrale shaderprogramma. Er is een functie, LoadShader genaamd, die de arceringscode uit het HTML-bestand krijgt; daar komen we snel bij. We gebruiken het 'shader-programma' om de twee shaders aan elkaar te koppelen en geven ons toegang tot hun variabelen. We slaan de twee attributen op die we in de shaders hebben gedefinieerd; zodat we onze geometrie er later in kunnen invoeren.
Laten we nu naar de functie LoadShader kijken, dit moet u buiten de WebGL-functie plaatsen:
functie LoadShader (Script) var Code = ""; var CurrentChild = Script.firstChild; while (CurrentChild) if (CurrentChild.nodeType == CurrentChild.TEXT_NODE) Code + = CurrentChild.textContent; CurrentChild = CurrentChild.nextSibling; retourcode;
Het loopt in principe gewoon door de arcering en verzamelt de broncode.
Om objecten in WebGL te tekenen, hebt u de volgende drie arrays nodig:
Dit wordt UV-mapping genoemd. Laten we voor ons voorbeeld een eenvoudige kubus maken. Ik zal de kubus opsplitsen in 4 hoekpunten per zijde die in twee driehoeken worden verbonden. laten we een variabele maken die de arrays van een kubus bevat.
var Cube = Vertices: [// X, Y, Z Coordinates // 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, // Back 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, // Right 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0 , -1.0, 1.0, -1.0, -1.0, // Left -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, // Top 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, // Onder 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: [// Ook in groepen van drie om de drie punten van elke driehoek te definiëren // De cijfers hier zijn de indexnummers in de hoekpuntenserie // Voorkant 0, 1, 2, 1, 2, 3, // Achterzijde 4, 5, 6, 5, 6, 7, // Rechts 8, 9, 10, 9, 10, 11, // Links 12, 13, 14, 13, 14, 15, // Top 16, 17, 18, 17, 18, 19, // Bottom 20, 21, 22, 21, 22, 23], Texture: [// Deze array bevindt zich in groepen van twee, de x- en y-coördinaten (alias U, V) in de textuur // De cijfers gaan van 0,0 tot 1,0, één paar voor elke vertex // Voor 1,0, 1,0, 1,0, 0,0 , 0.0, 1.0, 0.0, 0.0, // Terug 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, // Rechts 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // Links 0,0, 1,0, 0,0, 0,0, 1,0, 1,0, 1,0, 0,0, // Top 1,0, 0,0, 1,0, 1,0, 0,0, 0,0, 0,0, 1,0, // Bottom 0,0, 0,0, 0,0, 1,0, 1,0, 0,0, 1.0, 1.0];
Het lijkt misschien veel data voor een eenvoudige kubus, maar in deel twee van deze tutorial zal ik een script maken dat je 3D-modellen zal importeren, zodat je je geen zorgen hoeft te maken over het berekenen van deze.
Je vraagt je misschien ook af waarom ik 24 punten heb gemaakt (4 voor elke zijde), terwijl er eigenlijk maar acht totaal unieke punten op een kubus zijn? Ik deed dit omdat u slechts één textuurcoördinaat per vertex kunt toewijzen; dus als we alleen de 8 punten zouden invoeren, dan zou de hele kubus er hetzelfde uit moeten zien, omdat het de textuur rond alle zijden zou wikkelen die de vertex aanraakt. Maar op deze manier heeft elke kant zijn eigen punten, zodat we aan elke kant een ander deel van de textuur kunnen plaatsen.
We hebben nu deze kubusvariabele en zijn klaar om te beginnen met tekenen. Laten we teruggaan naar de WebGL-methode en een toevoegen Trek
functie.
De procedure voor het tekenen van objecten in WebGL heeft veel stappen; dus, het is een goed idee om een functie te maken om het proces te vereenvoudigen. Het basisidee is om de drie arrays in WebGL-buffers te laden. Vervolgens koppelen we deze buffers aan de kenmerken die we in de shaders hebben gedefinieerd, samen met de transformatie- en perspectiefmatrices. Vervolgens moeten we de textuur in het geheugen laden en ten slotte kunnen we de textuur oproepen trek
commando. Dus laten we beginnen.
De volgende code komt in de WebGL-functie:
this.Draw = function (Object, Textuur) var VertexBuffer = this.GL.createBuffer (); // Maak een nieuwe buffer // Bind deze als de huidige buffer this.GL.bindBuffer (this.GL.ARRAY_BUFFER, VertexBuffer); // Vul het met de gegevens this.GL.bufferData (this.GL.ARRAY_BUFFER, nieuwe Float32Array (Object.Vertices), this.GL.STATIC_DRAW); // Connect Buffer To Shader's attribuut this.GL.vertexAttribPointer (this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); // Herhaal dit voor de volgende twee var TextureBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, TextureBuffer); this.GL.bufferData (this.GL.ARRAY_BUFFER, nieuwe 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); // Genereer de Perspectiefmatrix var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 10000.0); var TransformMatrix = MakeTransform (Object); // Stel slot 0 in als de actieve structuur this.GL.activeTexture (this.GL.TEXTURE0); // Laad in de textuur in het geheugen this.GL.bindTexture (this.GL.TEXTURE_2D, Texture); // Update The Texture Sampler in de fragmentshader om slot 0 this.GL.uniform1i (this.GL.getUniformClassificatie (this.ShaderProgram, "uSampler"), 0) te gebruiken; // Stel de perspectieven en transformatiematrices var pmatrix = this.GL.getUniformClassificatie (this.ShaderProgram, "PerspectiveMatrix"); this.GL.uniformMatrix4fv (pmatrix, false, nieuwe Float32Array (PerspectiveMatrix)); var tmatrix = this.GL.getUniformClassificatie (this.ShaderProgram, "TransformationMatrix"); this.GL.uniformMatrix4fv (tmatrix, false, nieuwe Float32Array (TransformMatrix)); // Teken de driehoeken this.GL.drawElements (this.GL.TRIANGLES, Object.Trinagles.length, this.GL.UNSIGNED_SHORT, 0); ;
De hoekpuntsader positioneert, roteert en schaalt uw object op basis van de transformatie- en perspectivische matrices. We zullen dieper ingaan op transformaties in het tweede deel van deze serie.
Ik heb twee functies toegevoegd: MakePerspective ()
en MakeTransform ()
. Deze genereren alleen de benodigde 4x4 Matrices voor WebGL. De MakePerspective ()
functie accepteert het verticale gezichtsveld, de beeldverhouding en de dichtstbijzijnde en meest verre punten als argumenten. Alles dat dichterbij dan 1 eenheid en verder dan 10.000 eenheden is, wordt niet weergegeven, maar u kunt deze waarden bewerken om het effect te krijgen waarnaar u op zoek bent. Laten we nu eens kijken naar deze twee functies:
function MakePerspective (FOV, AspectRatio, Closest, Farest) var YLimit = Closest * Math.tan (FOV * Math.PI / 360); var A = - (Farest + Closest) / (Farest - Closest); var B = -2 * Farest * Closest / (Farest - Closest); var C = (2 * Dichtstbijzijnde) / ((YLimit * AspectRatio) * 2); var D = (2 * Dichtst) / (YLimit * 2); retourneer [C, 0, 0, 0, 0, D, 0, 0, 0, 0, A, -1, 0, 0, B, 0]; functie MakeTransform (Object) return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -6, 1];
Beide matrices beïnvloeden het uiteindelijke uiterlijk van je objecten, maar de perspectiefmatrix bewerkt je '3D-wereld' zoals het gezichtsveld en de zichtbare objecten, terwijl de transformatiematrix de individuele objecten bewerkt zoals hun rotatieschaal en positie. Met dit klaar zijn we bijna klaar om te tekenen, het enige dat overblijft is een functie om een afbeelding om te zetten in een WebGL-structuur.
Het laden van een textuur is een proces in twee stappen. Eerst moeten we een afbeelding laden zoals in een standaard JavaScript-toepassing, en dan moeten we deze converteren naar een WebGL-structuur. Dus laten we beginnen met het tweede deel omdat we al in het JS-bestand zitten. Voeg het volgende toe onderaan de WebGL-functie direct na de Draw-opdracht:
this.LoadTexture = function (Img) // Maak een nieuwe structuur en wijs deze toe als de actieve var TempTex = this.GL.createTexture (); this.GL.bindTexture (this.GL.TEXTURE_2D, TempTex); // Flip Positive Y (Optioneel) this.GL.pixelStorei (this.GL.UNPACK_FLIP_Y_WEBGL, true); // Laad in de afbeelding this.GL.texImage2D (this.GL.TEXTURE_2D, 0, this.GL.RGBA, this.GL.RGBA, this.GL.UNSIGNED_BYTE, Img); // Setup Schalingseigenschappen 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); // Ontbind de textuur en plaats deze terug. this.GL.bindTexture (this.GL.TEXTURE_2D, null); terugkeer TempTex; ;
Het is vermeldenswaard dat uw texturen in byte-groottes moeten zijn, anders ontvangt u een foutmelding; het moeten dus dimensies zijn, zoals 2x2, 4x4, 16x16, 32x32, enzovoort. Ik heb de regel toegevoegd om de Y-coördinaten om te draaien, simpelweg omdat de Y-coördinaten van mijn 3D-toepassing achteruit waren, maar dit is afhankelijk van wat u gebruikt. Dit komt door sommige programma's waarbij 0 in de Y-as de linkerbovenhoek is en sommige applicaties de linkerbenedenhoek. De schalingseigenschappen die ik heb ingesteld, vertellen WebGL hoe de afbeelding up-scale en down-scale moet worden. Je kunt met verschillende opties spelen om verschillende effecten te krijgen, maar ik dacht dat deze het beste werkten.
Nu we klaar zijn met het JS-bestand, gaan we terug naar het HTML-bestand en implementeren dit allemaal.
Zoals ik eerder heb vermeld, wordt WebGL teruggegeven aan een canvaselement. Dat is alles wat we nodig hebben in de lichaamssectie. Na het toevoegen van het canvaselement, zou je html-pagina er als volgt uit moeten zien:
Het is een vrij eenvoudige pagina. In het hoofdgedeelte heb ik een koppeling gemaakt met ons JS-bestand. Laten we nu onze Ready-functie implementeren, die wordt aangeroepen wanneer de pagina wordt geladen:
// Dit zal onze WebGL variabele var GL bevatten; // Onze afgewerkte textuur var Texture; // Dit houdt de texturen image var TextureImage; function Ready () GL = new WebGL ("GLCanvas", "FragmentShader", "VertexShader"); TextureImage = nieuwe afbeelding (); TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); GL.Draw (kubus, textuur); ; TextureImage.src = "Texture.png";
Dus we maken een nieuw WebGL-object en geven de ID's door voor het canvas en de shaders. Vervolgens laden we de textuurafbeelding. Eenmaal geladen, bellen we de Trek()
methode met de kubus en de textuur. Als je meeging, moet je scherm een statische kubus met een textuur erop hebben.
Nu, hoewel ik zei dat we de transformaties de volgende keer zullen behandelen, kan ik je niet gewoon achterlaten met een statisch vierkant; het is niet 3D genoeg. Laten we teruggaan en een kleine rotatie toevoegen. Wijzig in het HTML-bestand de onload
functie om er zo uit te zien:
TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); setInterval (Update, 33); ;
Dit zal een functie genaamd Bijwerken()
elke 33 milliseconden die ons een beeldsnelheid van ongeveer 30 fps geeft. Hier is de update-functie:
functie Update () GL.GL.clear (16384 | 256); GL.Draw (GL.Cube, textuur);
Dit is een vrij eenvoudige functie; het wist het scherm en tekent dan de bijgewerkte kubus. Laten we nu naar het JS-bestand gaan om de rotatiecode toe te voegen.
Ik ga transformaties niet volledig implementeren, omdat ik dat voor de volgende keer bewaar, maar laten we een rotatie rond de Y-as toevoegen. Het eerste wat u moet doen is een rotatievariabele aan ons kubus-object toevoegen. Hiermee wordt de huidige hoek bijgehouden en kunnen we de rotatie blijven verhogen. Dus de bovenkant van uw Kubusvariabele zou er als volgt uit moeten zien:
var Kubus = Rotatie: 0, // De andere drie arrays;
Laten we nu de MakeTransform ()
functie om de rotatie op te nemen:
function 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.Rotatie + = .3; terugkeer [A, 0, B, 0, 0, 1, 0, 0, C, 0, D, 0, 0, 0, -6, 1];
En dat is het! In de volgende tutorial behandelen we laadmodellen en transformaties. Ik hoop dat je deze tutorial leuk vond; voel je vrij om eventuele vragen of opmerkingen achter te laten.