WebGL-Grundlagen Teil I

WebGL ist ein auf OpenGL basierender In-Browser-3D-Renderer, mit dem Sie 3D-Inhalte direkt in einer HTML5-Seite anzeigen können. In diesem Tutorial werde ich Ihnen alles Notwendige vermitteln, um mit diesem Framework beginnen zu können.


Einführung

Es gibt ein paar Dinge, die Sie wissen sollten, bevor wir loslegen. WebGL ist eine JavaScript-API, die 3D-Inhalte in eine HTML5-Leinwand konvertiert. Dazu werden zwei Skripte verwendet, die in der "3D-Welt" als bekannt sind Shader. Die zwei Shader sind:

  • Der Vertex-Shader
  • Der Fragment-Shader

Werden Sie nicht zu nervös, wenn Sie diese Namen hören; es ist nur eine schicke Art zu sagen, "Positionsrechner" bzw. "Farbauswahl". Der Fragment-Shader ist der verständlichere; es teilt WebGL lediglich mit, welche Farbe ein bestimmter Punkt in Ihrem Modell haben soll. Der Vertex-Shader ist etwas technischer, konvertiert jedoch im Grunde die Punkte in Ihren 3D-Modellen in 2D-Koordinaten. Da alle Computermonitore flache 2D-Oberflächen sind und 3D-Objekte auf dem Bildschirm angezeigt werden, sind sie lediglich eine Illusion von Perspektive.

Wenn Sie genau wissen möchten, wie diese Berechnung funktioniert, müssen Sie einen Mathematiker fragen, da er erweiterte 4 x 4-Matrixmultiplikationen verwendet, die etwas außerhalb des Lernprogramms "Essentials" liegen. Glücklicherweise müssen Sie nicht wissen, wie es funktioniert, da WebGL das meiste erledigen wird. Also lasst uns anfangen.


Schritt 1: WebGL einrichten

WebGL hat viele kleine Einstellungen, die Sie fast jedes Mal vornehmen müssen, wenn Sie etwas auf den Bildschirm zeichnen. Um Zeit zu sparen und Ihren Code übersichtlich zu gestalten, werde ich ein JavaScript-Objekt erstellen, das alle Dinge hinter der Szene in einer separaten Datei enthält. Erstellen Sie zunächst eine neue Datei mit dem Namen 'WebGL.js', und fügen Sie den folgenden Code ein:

Funktion WebGL (CID, FSID, VSID) var canvas = document.getElementById (CID); if (! canvas.getContext ("webgl") &&! canvas.getContext ("experimental-webgl")) alert ("Ihr Browser unterstützt WebGL nicht"); else this.GL = (canvas.getContext ("webgl"))? canvas.getContext ("webgl"): canvas.getContext ("experimental-webgl"); this.GL.clearColor (1,0, 1,0, 1,0, 1,0); // Dies ist die Farbe this.GL.enable (this.GL.DEPTH_TEST); // Enable Testing aktivieren this.GL.depthFunc (this.GL.LEQUAL); // Set Perspective View this.AspectRatio = canvas.width / canvas.height; // Shader hier laden

Diese Konstruktorfunktion nimmt die IDs der Zeichenfläche und die zwei Shader-Objekte auf. Zuerst erhalten wir das Canvas-Element und stellen sicher, dass es WebGL unterstützt. Wenn dies der Fall ist, weisen wir den WebGL-Kontext einer lokalen Variablen namens "GL" zu. Die klare Farbe ist einfach die Hintergrundfarbe, und es ist erwähnenswert, dass in WebGL die meisten Parameter von 0,0 bis 1,0 reichen, sodass Sie Ihre rgb-Werte durch 255 dividieren müssen. In unserem Beispiel bedeutet dies 1.0, 1.0, 1.0, 1.0 ein weißer Hintergrund mit 100% Sichtbarkeit (keine Transparenz). In den nächsten beiden Zeilen wird WebGL angewiesen, Tiefe und Perspektive zu berechnen, sodass ein Objekt in Ihrer Nähe Objekte dahinter blockiert. Zum Schluss legen wir das Seitenverhältnis fest, das berechnet wird, indem die Breite der Leinwand durch ihre Höhe geteilt wird.

Bevor wir fortfahren und die beiden Shader laden, schreiben wir sie. Ich werde diese in die HTML-Datei schreiben, wo wir das eigentliche Canvas-Element einfügen werden. Erstellen Sie eine HTML-Datei und platzieren Sie die folgenden beiden Skriptelemente unmittelbar vor dem schließenden body-Tag:

 

Der Vertex-Shader wird zuerst erstellt und wir definieren zwei Attribute:

  • die Scheitelpunktposition, dh die Position in x-, y- und z-Koordinaten des aktuellen Scheitelpunkts (Punkt in Ihrem Modell)
  • die Texturkoordinate; die Stelle im Texturbild, die diesem Punkt zugewiesen werden soll

Als Nächstes erstellen wir Variablen für die Transformations- und Perspektivenmatrizen. Diese werden verwendet, um das 3D-Modell in ein 2D-Bild umzuwandeln. Die nächste Zeile erstellt eine gemeinsame Variable für den Fragment-Shader, und in der Hauptfunktion berechnen wir die gl_Position (die endgültige 2D-Position). Wir weisen dann die 'aktuelle Texturkoordinate' der gemeinsam genutzten Variablen zu.

Im Fragment-Shader nehmen wir einfach die Koordinaten, die wir im Vertex-Shader definiert haben, und "probieren" die Textur an dieser Koordinate. Grundsätzlich erhalten wir nur die Farbe in der Textur, die dem aktuellen Punkt unserer Geometrie entspricht.

Nun, da wir die Shader geschrieben haben, können wir sie in unsere JS-Datei laden. Ersetzen Sie also die "// Shader hier laden" durch folgenden Code:

var FShader = document.getElementById (FSID); var VShader = document.getElementById (VSID); if (! FShader ||! VShader) alert ("Fehler, Shaders konnten nicht gefunden werden"); else // Fragment Shader laden und kompilieren var Code = LoadShader (FShader); FShader = this.GL.createShader (this.GL.FRAGMENT_SHADER); this.GL.shaderSource (FShader, Code); this.GL.compileShader (FShader); // Laden und Kompilieren von Vertex Shader Code = LoadShader (VShader); VShader = this.GL.createShader (this.GL.VERTEX_SHADER); this.GL.shaderSource (VShader, Code); this.GL.compileShader (VShader); // Das Shader-Programm erstellen 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); // Vertex-Positionsattribut aus Shader verknüpfen this.VertexPosition = this.GL.getAttribLocation (this.ShaderProgram, "VertexPosition"); this.GL.enableVertexAttribArray (this.VertexPosition); // Texture Coordinate-Attribut aus Shader verknüpfen this.VertexTexture = this.GL.getAttribLocation (this.ShaderProgram, "TextureCoord"); this.GL.enableVertexAttribArray (this.VertexTexture); 

Ihre Texturen müssen in geraden Bytes sein oder Sie erhalten einen Fehler… wie 2x2, 4x4, 16x16, 32x32…

Wir stellen zunächst sicher, dass die Shader vorhanden sind, und laden uns dann, sie einzeln zu laden. Der Prozess erhält im Wesentlichen den Quellcode des Shaders, kompiliert ihn und fügt ihn dem zentralen Shader-Programm hinzu. Es gibt eine Funktion namens LoadShader, die den Shader-Code aus der HTML-Datei abruft. Wir werden in einer Sekunde darauf zurückkommen. Wir verwenden das "Shader-Programm", um die beiden Shader miteinander zu verknüpfen, und wir erhalten Zugriff auf ihre Variablen. Wir speichern die beiden von uns definierten Attribute in den Shader; So können wir später unsere Geometrie eingeben.

Schauen wir uns nun die LoadShader-Funktion an. Sie sollten dies außerhalb der WebGL-Funktion platzieren:

Funktion LoadShader (Script) var Code = ""; var CurrentChild = Script.firstChild; while (CurrentChild) if (CurrentChild.nodeType == CurrentChild.TEXT_NODE) ​​Code + = CurrentChild.textContent; CurrentChild = CurrentChild.nextSibling;  Rückgabe Code; 

Es durchläuft im Grunde nur den Shader und sammelt den Quellcode.


Schritt 2: Der "Einfache" Würfel

Um Objekte in WebGL zu zeichnen, benötigen Sie die folgenden drei Arrays:

  • Scheitelpunkte; die Punkte, aus denen sich Ihre Objekte zusammensetzen
  • Dreiecke; teilt WebGL mit, wie die Scheitelpunkte in Flächen verbunden werden
  • Texturkoordinaten; Definiert, wie die Scheitelpunkte auf das Texturbild abgebildet werden

Dies wird als UV-Mapping bezeichnet. In unserem Beispiel erstellen wir einen Basiswürfel. Ich werde den Würfel in 4 Ecken pro Seite aufteilen, die sich in zwei Dreiecke verbinden. Lassen Sie uns eine Variable erstellen, die die Arrays eines Cubes enthält.

var Cube = Scheitelpunkte: [// X, Y, Z-Koordinaten // Vorderseite 1,0, 1,0, -1,0, 1,0, -1,0, -1,0, -1,0, 1,0, -1,0, -1,0, -1,0, -1,0, // Zurück 1,0, 1,0, 1,0, 1,0, -1,0, 1,0, -1,0, 1,0, 1,0, -1,0, -1,0, 1,0, // Rechts 1,0, 1,0, 1,0, 1,0, 1,0, 1,0, 1,0, 1,0 -1,0, 1,0, -1,0, -1,0, // links -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, // unten 1,0, -1,0, 1,0, -1,0, -1,0, 1,0, 1,0 , -1.0, -1.0, -1.0, -1.0, -1.0], Dreiecke: [// Auch in Dreiergruppen zum Definieren der drei Punkte jedes Dreiecks // Die Zahlen hier sind die Indexnummern im Scheitelpunkt-Array // Vorne 0, 1, 2, 1, 2, 3, // Rücken 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], Textur: [// Dieses Array ist in Gruppen von zwei, die x- und y-Koordinaten (auch bekannt als U, V) in der Textur // Die Zahlen gehen von 0,0 bis 1,0, Ein Paar für jeden Scheitelpunkt // Front 1.0, 1.0, 1.0, 0.0 0,0, 1,0, 0,0, 0,0, // zurück 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, 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, // unten 0,0, 0,0, 0,0, 1,0, 1,0, 0,0, 1,0, 1,0];

Es mag wie eine Menge Daten für einen einfachen Würfel erscheinen, aber in Teil zwei dieses Tutorials werde ich ein Skript erstellen, das Ihre 3D-Modelle importiert, damit Sie sich keine Gedanken über die Berechnung machen müssen.

Sie fragen sich vielleicht auch, warum ich 24 Punkte gemacht habe (4 für jede Seite), wenn es wirklich nur acht eindeutige Gesamtpunkte auf einem Würfel gibt? Ich habe dies getan, weil Sie nur eine Texturkoordinate pro Scheitelpunkt zuweisen können. Wenn wir also nur die 8 Punkte einfügen würden, müsste der gesamte Würfel gleich aussehen, da er die Textur um alle Seiten, die der Scheitelpunkt berührt, umgibt. Auf diese Weise hat jede Seite ihre eigenen Punkte, sodass wir auf jeder Seite einen anderen Teil der Textur einfügen können.

Wir haben jetzt diese Cube-Variable und können jetzt mit dem Zeichnen beginnen. Gehen wir zurück zur WebGL-Methode und fügen Sie ein Zeichnen Funktion.


Schritt 3: Die Draw-Funktion

Das Verfahren zum Zeichnen von Objekten in WebGL umfasst viele Schritte. Daher ist es eine gute Idee, eine Funktion zu erstellen, um den Prozess zu vereinfachen. Die Grundidee besteht darin, die drei Arrays in WebGL-Puffer zu laden. Diese Puffer verbinden wir dann mit den Attributen, die wir in den Shader definiert haben, zusammen mit den Transformations- und Perspektivenmatrizen. Als nächstes müssen wir die Textur in den Speicher laden, und schließlich können wir das aufrufen zeichnen Befehl. Also lasst uns anfangen.

Der folgende Code gehört zur WebGL-Funktion:

this.Draw = Funktion (Object, Texture) var VertexBuffer = this.GL.createBuffer (); // Einen neuen Puffer erstellen // Als aktuellen Puffer binden this.GL.bindBuffer (this.GL.ARRAY_BUFFER, VertexBuffer); // Fülle es mit den Daten this.GL.bufferData (this.GL.ARRAY_BUFFER, neuer Float32Array (Object.Vertices), this.GL.STATIC_DRAW); // Puffer mit Shader-Attribut verbinden this.GL.vertexAttribPointer (this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); // Wiederholung für die nächsten zwei var TextureBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, TextureBuffer); this.GL.bufferData (this.GL.ARRAY_BUFFER, neuer 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); // Generate The Perspective Matrix var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 10000.0); var TransformMatrix = MakeTransform (Objekt); // Setze Slot 0 als aktive Textur this.GL.activeTexture (this.GL.TEXTURE0); // Lade die Textur in den Speicher this.GL.bindTexture (this.GL.TEXTURE_2D, Texture); // Aktualisieren Sie den Texture Sampler im Fragment-Shader, um Steckplatz 0 zu verwenden. This.GL.uniform1i (this.GL.getUniformLocation (this.ShaderProgram, "uSampler"), 0); // Setze die Perspektive und die Transformationsmatrizen var pmatrix = this.GL.getUniformLocation (this.ShaderProgram, "PerspectiveMatrix"); this.GL.uniformMatrix4fv (pmatrix, false, neuer Float32Array (PerspectiveMatrix)); var tmatrix = this.GL.getUniformLocation (this.ShaderProgram, "TransformationMatrix"); this.GL.uniformMatrix4fv (tmatrix, false, neuer Float32Array (TransformMatrix)); // Zeichne die Dreiecke this.GL.drawElements (this.GL.TRIANGLES, Object.Trinagles.length, this.GL.UNSIGNED_SHORT, 0); ;

Der Vertex-Shader positioniert, dreht und skaliert Ihr Objekt basierend auf den Transformations- und Perspektivenmatrizen. Im zweiten Teil dieser Serie werden wir uns näher mit den Transformationen befassen.

Ich habe zwei Funktionen hinzugefügt: MakePerspective () und MakeTransform (). Diese generieren nur die erforderlichen 4x4-Matrizen für WebGL. Das MakePerspective () Die Funktion akzeptiert das vertikale Sichtfeld, das Seitenverhältnis und die nächstgelegenen und am weitesten entfernten Punkte als Argumente. Alles, was näher als 1 Einheit und weiter als 10000 Einheiten liegt, wird nicht angezeigt. Sie können diese Werte jedoch bearbeiten, um den gewünschten Effekt zu erzielen. Schauen wir uns nun diese beiden Funktionen an:

Funktion MakePerspective (FOV, Aspektverhältnis, Am nächsten, Am weitesten entfernt) var YLimit = Closest * Math.tan (FOV * Math.PI / 360); var A = - (am weitesten + am nächsten) / (am weitesten - am nächsten); var B = -2 * am weitesten * am nächsten / (am weitesten - am nächsten); var C = (2 * am nächsten) / ((YLimit * AspectRatio) * 2); var D = (2 * am nächsten) / (YLimit * 2); return [C, 0, 0, 0, 0, D, 0, 0, 0, 0, A, -1, 0, 0, B, 0];  Funktion MakeTransform (Object) return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -6, 1]; 

Beide Matrizen wirken sich auf das endgültige Erscheinungsbild Ihrer Objekte aus, die Perspektivmatrix bearbeitet jedoch Ihre '3D-Welt' wie das Sichtfeld und die sichtbaren Objekte, während die Transformationsmatrix die einzelnen Objekte wie ihre Rotationsskala und ihre Position bearbeitet. Damit sind wir fast fertig zum Zeichnen. Alles, was übrig bleibt, ist eine Funktion, um ein Bild in eine WebGL-Textur zu konvertieren.


Schritt 4: Laden von Texturen

Das Laden einer Textur ist ein zweistufiger Prozess. Zuerst müssen wir ein Bild wie in einer Standard-JavaScript-Anwendung laden und dann in eine WebGL-Textur konvertieren. Beginnen wir mit dem zweiten Teil, da wir uns bereits in der JS-Datei befinden. Fügen Sie am Ende der WebGL-Funktion direkt nach dem Befehl Zeichnen Folgendes ein:

this.LoadTexture = function (Img) // Erzeuge eine neue Textur und weise sie als aktive zu. var TempTex = this.GL.createTexture (); this.GL.bindTexture (this.GL.TEXTURE_2D, TempTex); // Positives Kippen (optional) this.GL.pixelStorei (this.GL.UNPACK_FLIP_Y_WEBGL, true); // Lade das Image this.GL.texImage2D (this.GL.TEXTURE_2D, 0, this.GL.RGBA, this.GL.RGBA, this.GL.UNSIGNED_BYTE, Img); // Setup-Skalierungseigenschaften 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); // Entbinden Sie die Textur und geben Sie sie zurück. this.GL.bindTexture (this.GL.TEXTURE_2D, null); return TempTex; ;

Es ist erwähnenswert, dass Ihre Texturen gerade Bytes sein müssen. Andernfalls erhalten Sie eine Fehlermeldung. Sie müssen also Dimensionen wie 2x2, 4x4, 16x16, 32x32 usw. haben. Ich habe die Linie hinzugefügt, um die Y-Koordinaten umzudrehen, einfach weil die Y-Koordinaten meiner 3D-Anwendung rückwärts waren. Dies hängt jedoch von der verwendeten Anwendung ab. Dies ist darauf zurückzuführen, dass einige Programme in der Y-Achse die obere linke Ecke zu 0 und einige Anwendungen zur unteren linken Ecke machen. Die von mir eingestellten Skalierungseigenschaften geben WebGL lediglich an, wie das Bild skaliert und verkleinert werden soll. Sie können mit verschiedenen Optionen herumspielen, um verschiedene Effekte zu erzielen, aber ich dachte, diese funktionierten am besten.

Nun, da wir mit der JS-Datei fertig sind, kehren wir zur HTML-Datei zurück und implementieren all dies.


Schritt 5: Wickeln Sie es ein

Wie bereits erwähnt, wird WebGL in einem Canvas-Element dargestellt. Das ist alles, was wir im Körperteil brauchen. Nach dem Hinzufügen des Canvas-Elements sollte Ihre HTML-Seite wie folgt aussehen:

        Ihr Browser unterstützt nicht die Leinwand von HTML5.     

Es ist eine ziemlich einfache Seite. Im Kopfbereich habe ich auf unsere JS-Datei verlinkt. Lassen Sie uns nun unsere Ready-Funktion implementieren, die beim Laden der Seite aufgerufen wird:

// Dies wird unsere WebGL-Variable enthalten. Var GL; // Unsere fertige Textur var Texture; // Dies wird die Texturen enthalten image var TextureImage; function Ready () GL = neuer WebGL ("GLCanvas", "FragmentShader", "VertexShader"); TextureImage = neues Bild (); TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); GL.Draw (Würfel, Textur); ; TextureImage.src = "Texture.png"; 

Also erstellen wir ein neues WebGL-Objekt und übergeben die IDs für die Zeichenfläche und die Shader. Als Nächstes laden wir das Texturbild. Nach dem Laden rufen wir die Zeichnen() Methode mit dem Cube und der Textur. Wenn Sie mitverfolgen, sollte Ihr Bildschirm einen statischen Würfel mit einer Textur haben.

Auch wenn ich sagte, dass wir das nächste Mal Transformationen behandeln werden, kann ich Sie nicht einfach mit einem statischen Quadrat belassen. es ist nicht 3D genug. Gehen wir zurück und fügen Sie eine kleine Drehung hinzu. Ändern Sie in der HTML-Datei die onload Funktion, um so auszusehen:

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

Dies ruft eine aufgerufene Funktion auf Aktualisieren() alle 33 Millisekunden, was eine Framerate von etwa 30 fps ergibt. Hier ist die Update-Funktion:

Funktion Update () GL.GL.clear (16384 | 256); GL.Draw (GL.Cube, Texture); 

Dies ist eine ziemlich einfache Funktion. Es löscht den Bildschirm und zeichnet den aktualisierten Würfel. Gehen wir nun zur JS-Datei, um den Rotationscode hinzuzufügen.


Schritt 6: Spin hinzufügen

Ich werde Transformationen nicht vollständig implementieren, weil ich das für das nächste Mal speichere, aber fügen wir eine Drehung um die Y-Achse hinzu. Als Erstes fügen Sie unserem Cube-Objekt eine Rotationsvariable hinzu. Dies wird den aktuellen Winkel nachverfolgen und die Drehung weiter erhöhen. Die Oberseite Ihrer Cube-Variablen sollte also so aussehen:

var Cube = Rotation: 0, // Die anderen drei Arrays;

Jetzt aktualisieren wir die MakeTransform () Funktion zum Einbinden der Rotation:

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.Rotation + = .3; return [A, 0, B, 0, 0, 1, 0, 0, C, 0, D, 0, 0, 0, -6, 1]; 

Fazit

Und das ist es! Im nächsten Tutorial behandeln wir das Laden von Modellen und das Durchführen von Transformationen. Ich hoffe, Ihnen hat dieses Tutorial gefallen. Bitte hinterlassen Sie Fragen oder Kommentare.