Home
WebGL Api Spickzettel
WebGL Sicherheit
Tutorial
0 : WebGL Browser
1 : Das erste Dreieck
2 : 3D-Mathematik
3 : Farbe
4 : Animation
5 : Interaktion I
6 : Texturen
7 : Beleuchtung I
8 : Interaktion II
Links
WebGL Beispiele
WebGL Frameworks
ext. WebGL Tutorials
Kontakt / Impressum
webgl ([ät)] peter-strohm Punkt de
|
1 Das erste Dreieck
>>>> Direkt zum Beispiel <<<<
>>>Quellcode zum Beispiel <<<
Ich gehe jetzt davon aus, dass du schon WebGL-Beispiele die andere Leute
programmiert haben in deinem Browser gesehen hast und damit die
Grundvoraussetzung zum Start in die eigene Entwicklung gegeben ist. Die
Softwareanforderungen sind damit auch schon erfüllt. Zum Erstellen von
WebGL-Seiten benötigst du außer einem Browser
(get.webgl.org
gibt Auskunft über geeignete Browser) nur noch einen Texteditor wie z.B.
Notepad++.
Da WebGL im Internet-Browser dargestellt wird, muss es in HTML-Dateien
eingebettet werden. Wenn du nicht weißt was HTML ist, bis du hier falsch.
HTML ist zwar nicht schwierig zu erlernen und man muss auch kein Experte sein
um in WebGL einzusteigen, aber ein paar Grundkenntnisse sollten schon da sein.
Das gleiche gilt für JavaScript und allgemeine Grundlagen der
Programmierung.
1.1 Was ist WebGL und was ist
es nicht ?
WebGL ist die Integration von OpenGL (genaugenommen OpenGL ES 2.0) in JavaScript.
Somit bietet WebGL eine Möglichkeit, rechenintensive Grafiken
(i.d.R. 3D-Szenen) innerhalb von Webseiten zu verwenden.
WebGL ist NICHT eine beschreibende Sprache, in der du so etwas wie
<3DWuerfel x=1.0 y=1.0 z=1.0>
schreiben kannst und dann einen 3dimensionalen Würfel siehst. Zumindest
nicht ohne Hilfsmittel, aber dazu später mehr.
Ich möchte damit ausdrücken, dass WebGL ziemlich weit unten
anfängt. Wer WebGL programmieren will, sollte wissen was eine Matrix ist
(in der Mathematik, nicht im Kino!), wie man in der räumlichen Geometrie
mit Vektoren rechnet und Trigonometrische Funktionen anwendet.
Hallo ? Ist da noch jemand ?
Gut, ich werde natürlich auf die mathematischen Aspekte eingehen, aber
mit pMathe = 0x0000000 wird's sehr viel Copy und Paste für dich.
1.2 Auf ins kalte Wasser
Als allererstes Beispiel wird hier die kleinste "halbwegs sinnvolle"
WebGL-Seite vorgestellt. Da auch die komplexesten 3D-Szenen letztlich aus
einzelnen Dreiecken zusammengesetzt sind, stellt diese erste Anwendung ein
einzelnes weißes Dreieck dar.
Das erste Dreieck mit WebGL
Abbildung 1.1 : Screenshot des ersten Dreiecks
Der Quelltext ist so
angelegt, dass er komplett ohne zusätzliche JavaScript-Biblotheken oder
sonstige externe Resourcen auskommt. Alles passiert nacheinander innerhalb
einer Datei. Üblicherweise und in den nachfolgenden Kapiteln werden die
Standardoperationen wie die Initialisierung in eigene .js-Dateien ausgelagert,
was jedoch gerade für den Einstieg die übersicht erschwert.
Jetzt springen wir ans Ende der Quellcodedatei in Zeile 110. Dort sind wir
mitten im HTML-Umfeld das Dir einigermaßen bekannt sein sollte.
110 | <canvas id="meineWebGLCanvas" width="500" height="500"></canvas>
|
Diese Zeile platziert eine Zeichenfläche ("Canvas") auf der HTML-Seite.
Mit minimalen Englischkenntnissen ist es nicht schwer zu erahnen, wie hoch und
breit diese Zeichnungsfläches sein wird... Innerhalb dieser Fläche
wird die WebGL-Szene dargestellt.
Die id="meineWebGLCanvas" benötigen wir später noch, um Javascript
mitzuteilen, wo das WebGL-Geraffel dargestellt werden soll.
Bevor wir mit der Theorie weitermachen (bzw. richtig anfangen) verrate ich dir
die Stellen, an denen du schon jetzt direkt Einfluss auf das dargestellte
Dreieck nehmen kannst:
In Zeilen 88-90 stehen sechs Zahlen. Diese geben die Eckpunkte des Dreiecks in
karthesischen Koordinaten an (siehe Kommentare im Quellcode).
Da in diesem rudimentär-Beispiel noch keine echte 3D-Projektion verwendet
wird, können die Z-Koordinaten alle auf 0.0 gesetzt sein. Später
wird die Z-Koordinate die Tiefe im Raum angeben, wobei die negative z-Achse
"in den Bildschirm hinein" zeigt. (Du brauchst nicht zu versuchen, in diesem
Beispiel die Z-Werte zu ändern; da passiert noch nichts!)
Jetzt kannst du es wagen: Falls noch nicht geschehen, lade die Datei
"kapitel1.html" in ein lokales Verzeichnis auf
deinem Rechner und experimentiere mit den Koordinaten. Setze das Dreieck nach
links oder rechts, mache es spitzer oder stumpfer.
Fertig? Ich hoffe du hast nun ein Gefühl für den Zusammenhang
zwischen der Canvas-Zeichenfläche und den drei Dreieckspunkten.
Die zweite Stelle im Quellcode, an der du jetzt schon Einfluss nehmen kannst
ist in Zeile 65 der Datei:
65 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n\
|
Hier kannst du die Farbe des Dreiecks ändern. Die vier 1.0-Werte stehen
für RGBA (Rot,Grün,Blau,Alpha). Es würde zu weit (zurück)
führen hier dieses Farbschema zu erklären. Alpha beschreibt
Opazität (Gegenteil von Transparenz); Alpha=0 --> Dreieck ist
unsichtbar.
1.3 Initialisierung und
Struktur
In diesem Beispiel beziehe ich mich nur auf die Browser FireFox ab Version 4.0
und Google Chrome ab Version 9.0. Zukünftig sollte es auch für
weitere Browser funktionieren.
Wir wollen uns nun anschauen, was im Quellcode passiert bevor das Dreieck auf
dem Bildschirm erscheint.
Die Zeichenfläche ("Canvas") habe ich ja im letzten Absatz bereits
erwähnt. Sie ist im Body-Teil des HTML Dokuments eingebettet.
Nachdem die Datei vom Browser geladen wurde, wird die Javascript-Funktion
meinWebGLStart() aufgerufen:
103 | window.onload = function () {
| 104 | meinWebGLStart();
| 105 | };
|
meinWebGLStart() ist ein paar Zeilen darüber implementiert. Die
Funktion ist in diesem Beispiel das Ein und Alles. Alle Schritte von der
Initialisierung von WebGL bis zum Zeichnen des Dreiecks werden nacheinander
ausgeführt:
Ich fange vorne an:
26 | canvas = window.document.getElementById("meineWebGLCanvas");
| 27 |
| 28 | try {
| 29 | // Falls der Browser es unterstuetzt, wird hier WebGL
| 30 | // erstmalig angesprochen und der "WebGL-Context" in
| 31 | // dem Objekt gl gespeichert.
| 32 | gl = canvas.getContext("experimental-webgl");
| 33 | } catch (e) {}
| 34 | if (!gl) {
| 35 | window.alert("Fehler: WebGL-Context nicht gefunden");
| 36 | }
|
In der lokalen Variable canvas wird zunächst über das globale
document-Objekt von Javascript das Element mit dem Namen
"meineWebGLCanvas" gesucht und damit lokal (in dieser Funktion
verfügbar).
Danach wird die Objektmethode getContext("experimental-webgl")
aufgerufen und der Rückgabewert dem ebenfalls lokalen Objekt gl
zugewiesen.
Mit dieser Zeile wird geprüft, ob der verwendete Browser den
"experimental-webgl" -Context unterstützt und falls dies so ist, steht
das Objekt gl ab sofort für alle WebGL-Befehle bereit. Die
Verknüpfung zwischen der oben beschriebenen Canvas und WebGL ist damit
ebenfalls hergestellt. Die Initialisierung ist in einen try-catch-Block
eingeschlossen um alle Arten von Fehler abzufangen, die auftreten falls der
verwendete Browser WebGL (noch) nicht unterstützt.
Direkt danach finden noch eine überprüfung statt, ob die
Initialisierung geklappt hat. Wenn kein WebGL-context gefunden wurde, wird mit
dem alert-Befehl eine Fehlermeldung im Browser angezeigt.
In den darauffolgenden Codezeilen wird ein WebGL-Program-Objekt mit zwei
Shadern erzeugt:
40 | webGLProgramObject = gl.createProgram();
| 41 |
| 42 | // Der folgende String enthaelt den kompletten Quellcode
| 43 | // fuer einen minimalistischen Vertex-Shader:
| 44 | vShaderQuellcode =
| 45 | 'attribute vec4 vPosition; \n\
| 46 | void main() \n\
| 47 | { \n\
| 48 | gl_Position = vPosition; \n\
| 49 | } \n';
| 50 | // Das Vertex-Shader-Objekt wird angelegt:
| 51 | vShader = gl.createShader(gl.VERTEX_SHADER);
| 52 | // - mit seinem Quelltext verknuepft:
| 53 | gl.shaderSource(vShader, vShaderQuellcode);
| 54 | // - kompiliert:
| 55 | gl.compileShader(vShader);
| 56 | // - dem Shader-Program-Objekt hinzugefuegt:
| 57 | gl.attachShader(webGLProgramObject, vShader);
| 58 |
| 59 | // Nochmal das gleiche Vorgehen wie fuer den Vertex-
| 60 | // Shader; analog fuer den Fragment-Shader:
| 61 | fShaderQuellcode =
| 62 | 'precision mediump float;\n\
| 63 | void main() \n\
| 64 | { \n\
| 65 | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n\
| 66 | } \n';
| 67 | fShader = gl.createShader(gl.FRAGMENT_SHADER);
| 68 | gl.shaderSource(fShader, fShaderQuellcode);
| 69 | gl.compileShader(fShader);
| 70 | gl.attachShader(webGLProgramObject, fShader);
| 71 | // Das Shader-Program-Objekt ist vollstaendig und muss
| 72 | // gelinkt werden.
| 73 | gl.linkProgram(webGLProgramObject);
| 74 | // Da theoretisch mehrere Shader-Program-Objekte moeglich
| 75 | // sind, muss angegeben werden, welches benutzt werden soll.
| 76 | gl.useProgram(webGLProgramObject);
| Shaders sind ein Kapitel für sich. In der OpenGL Literatur tauchen sie
üblicherweise erst in den hinteren Kapiteln auf. In WebGL (bzw. OpenGL ES
2.0) kommt man jedoch von Anfang an mit Shaders in Berührung.
Für diese Kapitel 1 würde es zu weit führen, auf die Shader
detailiert einzugehen und es ist auch erstmal nicht notwendig. Das
Grundkonzept will ich trotzdem versuchen mit einfachen Worten zu
erklären.
Was WebGL von herkömmlichen Javascript (2D) Grafikfunktionen
unterscheidet ist, dass ein Großteil der erforderlichen Berechnungen auf der
Grafikarte statt im Hauptprozessor ausgeführt werden (GPU statt CPU). Die
GPU (Graphics Processing Unit) ist für grafische Berechnungen ausgelegt
(wer hätte das gedacht) und hat v.A. gegenüber der CPU den Vorteil
dass sie viele Rechenoperationen GLEICHZEITIG berechnen kann. Wenn wir z.B.
ein 2D-Bild (Bildschirm) aus einer 3D-Szene (WebGL) berechnen wollen, muss
für jeden Pixel ein eingener Farbwert berechnet werden. D.h. jede Menge
"kleine" Berechnungen die voneinander unabhängig sind und damit ideal auf
der GPU berechnet werden können.
SHADER sind Mini-Programme, die auf der GPU vielfach parallel laufen.
(diese Definition sollte zumindest für Kapitel 1 ausreichen)
Der Quellcode der beiden Shader (es gibt immer diese zwei Shader in jeder
WebGL-Anwendung) ist als gewöhnlicher String in den JavaScript Quellcode
eingebettet. Den Vertex-Shader findest du in Zeile 45-49, den Fragment-Shader
in Zeile 62-66.
Möglicherweise hast du wie ich weiter oben vorgeschlagen habe, den
Fragment-Shader bereits bearbeitet, wenn du die Farbe des Dreiecks
geändert hast.
Die beiden Shader-Quellcode-Strings werden jeweils mit
gl.compileShader(..) übersetzt (Kompilieren kennst du hoffentlich
von anderen Programmiererfahrungen...) und mit attachShader(..) dem
WebGL-Program-Objekt hinzugefügt. Sobald dem Program-Objekt beide Shader
übergeben wurden, kann es gelinkt werden (Compiler + Linker ähnlich
wie in C, Pascal,...etc.).
Das Abschließende useProgram(..) "aktiviert" das gerade erstellte
Program-Objekt. Theoretisch können mehrere Programm-Objekte verwendet
werden, die mit useProgram jeweils ausgetauscht werden.
Der Datenfluss geht üblicherweise folgendermaßen:
1. im JavaScriptcode wird ein Rohdatenpuffer erzeugt (Raumkoordinaten, Farben,
etc.)
2. im Vertex-Shader werden die punktbezogenen Werte berechnet
(Transformationen, Normalenvektoren, Texturkoordinaten,...) und die
vordefinierte Variable $gl_Position$ belegt.
3. der Fragment-Shader berechnet die pixelbezogenen Werte zwischen den
Vertices (interpoliert z.B. Farben zwischen Vertices) und belegt die
vordefinierte Variable $gl_FragColor$.
Die folgenden zwei Zeilen sind leicht zu verstehen:
78 | gl.clearColor(0.0, 0.0, 0.0, 1.0);
| 79 | // Hintergrund loeschen
| 80 | gl.clear(gl.COLOR_BUFFER_BIT);
|
Die beiden Funktion legen die Hintergrundfarbe der 3D-Szene als RGB-Wert fest
und löschen die komplette Szene in dieser Farbe.
Jetzt kommt die Antwort auf die spannende Frage, wie Daten zwischen dem
"normalen" JavaScript-Code und den WebGL-Shadern ausgetauscht werden.
84 | vertexAttribLoc = gl.getAttribLocation(webGLProgramObject, "vPosition");
|
getAttribLocation(..) ist eine WebGL-Api-Funktion, die hier anhand des
Variablennamens vPosition eine Zugriffsmöglichkeit auf eine
Eingangsvariable des Vertex-Shaders liefert.
Dass in Zeile 88-90 die Dreieckskoordinaten angelegt werden, ist leicht zu
erahnen. Datentyp ist das WebGL-spezifische Float32Array.
Um die Berechnungen auf die GPU zu verlagern, müssen diese Daten in den
Grafikspeicher übertragen werden. Hierzu wird mit createBuffer ein
neuer Speicherbereich angelegt, der dann mit bindBuffer aktiviert wird
und zuletzt mit bufferData befüllt wird. Der Inhalt von
vVertices wird hier in den Grafikspeicher kopiert.
Nun müssen wir WebGL noch mitteilen, wofür der gerade aktivierte und
befüllte Puffer verwendet werden soll:
97 | gl.vertexAttribPointer(vertexAttribLoc, 3, gl.FLOAT, false, 0, 0);
| 98 | gl.enableVertexAttribArray(vertexAttribLoc);
|
Der erste Parameter vertexAttribLoc verweist auf die Eingangsvariable
des Vertex-Shaders, "3" ist die Anzahl der Werte pro Element (3
Koordinatenwerte pro Punkt) und "gl.Float" ist der Datentyp.
enableVertexAttribArray(..) aktiviert die Verwendung des Puffers
für folgende Zeichenfunktionen.
Der letzte Befehl bringt nun alles "auf den Schirm" um mit Cpt. Picard zu
sprechen:
100 | gl.drawArrays(gl.TRIANGLES, 0, 3);
|
Hiermit werden aus den zuletzt aktivierten Pufferdaten Dreiecke generiert (in
diesem Fall nur eines). Den ersten Parameter gl.TRIANGLES kannst du
z.B. auch in gl.POINTS oder
gl.LINE_STRIP
ändern und dich überraschen lassen, wie sich die Darstellung
verändert.
Der zweite und dritte Parameter geben Start- und Anzahl der Array-Elemente an,
die gezeichnet werden sollen.
Das soll es jetzt erstmal gewesen sein für Kapitel 1 meines
WebGL-Tutorials. Ich hoffe es ist einigermaßen verständlich. Kommentare
nehme ich gerne unter der u.A. Emailadresse entgegen.
Im Kapitel zwei werden wir die räumliche Projektion einführen und
ausführlich auf die Modelview- und Perspektivmatrizzen eingehen.
|