Sobald der WebGL-Kontext erfolgreich erstellt wurde, können wir anfangen darin zu rendern. Am einfachsten beginnen wir mit einem einfachen, zweidimensionalen, untextuierten Objekt. Fangen wir also damit an, ein Stück Code zu schreiben, um ein Quadrat zu zeichnen.
Beleuchtung der Szene
Das Wichtigste, das wir verstehen müssen bevor wir anfangen können, ist, dass wir, obwohl wir nur ein zweidimensionales Objekt in diesem Beispiel rendern wollen, uns bereits in einer dreidimensionalen Umgebung befinden. Das heißt, wir müssen jetzt bereits Shader einsetzen, die unsere einfache Szene beleuchten und solche erstellen, die unser Objekt zeichnen. Diese Shader werden festlegen wie unser Quadrat später beleuchtet sein wird.
Initialisierung der Shader
Shader sind durch die OpenGL ES Shading Language (pdf) spezifiziert. Damit es einfacher ist unsere Inhalte zu warten und zu aktualisieren, können wir unseren Code so schreiben, dass die Shader im HTML Dokument gefunden werden, anstatt alles mittels JavaScript zu bauen. Werfen wir einen Blick auf unsere initShaders()
Routine, welche diese Aufgabe übernimmt:
function initShaders() { var fragmentShader = getShader(gl, "shader-fs"); var vertexShader = getShader(gl, "shader-vs"); // Erzeuge Shader shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); // Wenn die das Aufrufen der Shader fehlschlägt, // gib eine Fehlermeldung aus: if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Initialisierung des Shaderprogramms nicht möglich."); } gl.useProgram(shaderProgram); vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(vertexPositionAttribute); }
Es werden zwei Shader in dieser Routine geladen; der Erste, der fragmentShader
, wird vom script
Element mit der Id "shader-fs" geladen. Der Zweite, der vertexShader
, wird vom script
Element mit der Id "shader-vs" geladen. Wir werden im nächsten Abschnitt noch näher auf die getShader()
Funktion eingehen. Diese Routine holt sich dann die Shader aus dem DOM.
In diesem Teil erstellen wir noch das Shaderprogramm, in dem wir die Funktion createProgram()
aufrufen, die beiden Shader anhängen und das Shaderprogramm verlinken. Danach wird der Zustand des LINK_STATUS
Parameters überprüft, um sicher zu gehen, dass das Programm erfolgreich verlinkt wurde. Wenn das der Fall ist, aktivieren wir das neue Shaderprogramm.
Shader aus dem DOM laden
Die getShader()
Routine ruft ein Shaderprogramm mit dem festgelegtem Namen aus dem DOM auf, gibt das kompilierte Shaderprogramm zurück oder ist leer, wenn nichts geladen oder kompiliert worden konnte.
function getShader(gl, id) { var shaderScript = document.getElementById(id); if (!shaderScript) { return null; } var theSource = ""; var currentChild = shaderScript.firstChild; while(currentChild) { if (currentChild.nodeType == 3) { theSource += currentChild.textContent; } currentChild = currentChild.nextSibling; }
Wenn das Element mit der festgelegten Id gefunden wurde, wird der Text in die Variable theSource
gespeichert.
var shader; if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); } else { return null; // Unbekannter Shadertyp }
Jetzt wo der Code für die Shader gelesen wurde, können wir uns die MIME Typen der Shader angucken, um festzulegen, ob es ein Vertex-Shader (MIME Typ: "x-shader/x-vertex") oder ein Fragment-Shader (MIME Typ: "x-shader/x-fragment") ist. Danach werden dann die entsprechenden Shadertypen erstellt.
gl.shaderSource(shader, theSource); // Kompiliere das Shaderprogramm gl.compileShader(shader); // Überprüfe, ob die Kompilierung erfolgreich war if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert("Es ist ein Fehler beim Kompilieren der Shader aufgetaucht: " + gl.getShaderInfoLog(shader)); return null; } return shader; }
Schließlich wird der Shader kompiliert. Falls ein Fehler während der Kompilierung auftritt, zeigen wir die Fehlermeldung an und geben null zurück. Andernfalls wird der kompilierte Shader zurückgegeben.
Die Shader
Nun müssen wir die eigentlichen Shaderprogramme in unser HTML schreiben. Wie genau diese Shader arbeiten, übersteigt das Ziel dieses Tutorials, wir gehen daher nur auf das Wesentliche ein.
Fragment-Shader
Jeder Pixel in einem Vieleck wird Fragment in der GL-Fachsprache genannt. Die Aufgabe des Fragment-Shaders ist es, die Farbe für jeden Pixel bereitzustellen. In unserem Fall ordnen wir einfach jedem Pixel eine weiße Farbe zu.
gl_FragColor
ist eine eingebaute GL Variable, die für die Farbe des Fragments verwendet wird.
<script id="shader-fs" type="x-shader/x-fragment"> void main(void) { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } </script>
Vertex-Shader
Der Vertex-Shader definiert die Position und Form von jedem Punkt.
<script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); } </script>
Das Objekt erstellen
Bevor wir unser Quadrat rendern können, müssen wir einen Puffer erstellen, der unsere Punkte enthält. Das werden wir mittels einer Funktion machen, die wir initBuffers()
nennen. Wenn wir zu mehr fortgeschrittenen WebGL-Konzepten kommen, wird diese Routine vergrößert, um mehr - und komplexere - 3D-Objekte zu erstellen.
var horizAspect = 480.0/640.0; function initBuffers() { squareVerticesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer); var vertices = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); }
Diese Routine ist, durch die einfache Art der Szene in diesem Beispiel, sehr einfach gehalten. Es geht los mit dem Aufruf der createBuffer()
Methode, die einen Puffer erstellt in dem wir die Punkte speichern können. Der Puffer wird, durch Aufrufen der bindBuffer()
Methode, mit dem Kontext verbunden.
Wenn das erledigt ist, erstellen wir einen JavaScript Array, der die Koordinaten für jeden Punkt des Quadrats enthält. Dieser wird dann in einen WebGL FloatArray umgewandelt und durch die bufferData()
Methode werden die Punkte für das Objekt festgelegt.
Die Szene zeichnen
Jetzt sind die Shader aufgebaut und das Objekt ist erstellt. Wir können die Szene rendern lassen. Da wir in dieser Szene nichts animieren, ist unsere drawScene()
Funktion sehr einfach. Es werden einige nützliche Routinen verwendet, die wir uns kurz anschauen.
function drawScene() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); perspectiveMatrix = makePerspective(45, 640.0/480.0, 0.1, 100.0); loadIdentity(); mvTranslate([-0.0, 0.0, -6.0]); gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer); gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); setMatrixUniforms(); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }
Als Erstes wird der Kontext auf unsere Hintergrundfarbe gesetzt und die Kameraperspektive festgelegt. Wir definieren ein Blickfeld von 45°, mit einem Höhen-/Breitenverhältnis von 640 zu 480 (die Größe unseres Canvas). Außerdem legen wir fest, dass wir nur Objekte zwischen 0.1 und 100 Einheiten gerendert haben wollen.
Dann wird die Position des Quadrats, über das Laden der ursprünglichen Position und der Verschiebung um 6 Einheiten von der Kamera weg, ermittelt. Danach, verbinden wir den Puffer des Quadrats mit dem Kontext, konfigurieren es, und zeichnen das Objekt, in dem wir die drawArrays()
Methode aufrufen.
Das Ergebnis kann hier ausprobiert werden, wenn Sie einen Browser verwenden, der WebGL unterstützt.
Matrix Operationen
Matrix Operationen sind schon kompliziert genug. Keiner möchte wirklich den ganzen Code selbst schreiben, der benötigt wird um die Berechnungen selber durchzuführen. Glücklicherweise gibt es Sylvester, eine sehr handliche Bibliothek, die bestens mit Vektor und Matrix Operationen in JavaScript umgehen kann.
Die glUtils.js
Datei, die in dieser Demo benutzt wird, wird bei einer ganzen Reihe von WebGL-Demos, die Web zu finden sind, verwendet. Keiner scheint sich völlig sicher zu sein, woher diese Bibliothek ursprünglich herkommt, aber es vereinfacht den Gebrauch von Sylvester noch weiter, in dem Methoden hinzugefügt werden, die auch spezielle Matrizentypen ermöglichen und HTML ausgegeben werden kann, um die Matrizen anzeigen zu lassen.
Zusätzlich, definiert diese Demo ein paar hilfreiche Routinen, um an diese Bibliothek für spezielle Aufgaben anzukoppeln. Was genau gemacht wird, ist kein Teil dieses Artikels, aber es gibt einige gute Referenzen zu Matrizen, die online verfügbar sind. Siehe unter Siehe auch, um ein paar aufzulisten.
function loadIdentity() { mvMatrix = Matrix.I(4); } function multMatrix(m) { mvMatrix = mvMatrix.x(m); } function mvTranslate(v) { multMatrix(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4()); } function setMatrixUniforms() { var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten())); var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten())); }