Tutorial WebGL: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K (VertexBufferObjects)
(Vorwort)
 
(16 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
WebGL ist der neue Standard für OpenGL im Browser. Der Standard ermöglicht es hardwarebeschleunigte 3D-Grafik mit Shadern im Browser darzustellen, ohne dabei auf spezielle Plugins angewiesen zu sein.
+
==Vorwort==
  
In diesem Tutorial werden die wesentlichen Besonderheiten von WebGL gegenüber anderen OpenGL-Anwendungen vorgestellt. Kenntnisse in [http://de.wikipedia.org/wiki/JavaScript JavaScript] und [http://www.khronos.org/opengles/2_X/ OpenGL ES 2.0], also insbesondere [[GLSL]] und [[VBO]]s werden vorausgesetzt. Es sei darauf hingewiesen, dass WebGL im Augenblick noch hochgradig experimentell ist und bisher auch nicht wirklich dokumentiert ist. Neben diesem Tutorial existieren auch einige andere [[#Links|Tutorials]], aber ein Blick in den Quellcode der verfügbaren Demos kann nie schaden.
+
In diesem Tutorial werden die wesentlichen Besonderheiten von WebGL gegenüber anderen OpenGL-Anwendungen vorgestellt. Kenntnisse in [http://de.wikipedia.org/wiki/JavaScript JavaScript] und [http://www.khronos.org/opengles/ OpenGL ES], also insbesondere [[GLSL]] und [[VBO]]s werden vorausgesetzt. Neben diesem Tutorial existieren auch einige andere [[#Links|Tutorials]], aber ein Blick in den Quellcode der verfügbaren Demos kann nie schaden.
  
WebGL ist im wesentlichen ein JavaScript-Binding für OpenGL ES 2.0 und wird bisher von den aktuellen Entwickler-Versionen von Firefox, Google Chrome und Safari unterstützt. Weitere Browser auf Gecko- bzw. Webkit-Basis werden sicher bald folgen. Bisher wird der WebGL-Standard von den folgenden Browsern unterstützt:
+
Das neuere WebGL 2.0 lehnt an OpenGL ES 3.00 an und somit werden werden auch [[VAO]] unterstütz.<br>
(Stand Oktober 2009)
+
Siehe auch:
* Gecko-Engine
+
* [https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.6 WebGL 2.0 Specification]
** Mozilla Firefox 3.7a1pre ([http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-trunk/ Nightly Build], "Minefield")
 
*** WebGL muss über die Einstellung <tt>about:config</tt> -> <tt>webgl.enabled_for_all_sites</tt> aktiviert werden. Unter Windows kann es einige Probleme geben, wenn kein OpenGL verfügbar ist, in dem Fall muss [http://hacks.mozilla.org/2009/09/webgl-for-firefox/ in den Softwaremodus geschaltet] werden.
 
*** Es ist möglich verschiedene Firefox-Versionen parallel zu verwenden. Es ist aber ratsam dafür getrennte Profile anzulegen. Das Ausführen von <tt>firefox -help</tt> in der Konsole dürfte in diesem Zusammenhang hilfreich sein, insbesondere die Optionen <tt>-P</tt>, <tt>-ProfileManager</tt> und <tt>-no-remote</tt>.
 
* WebKit ([http://nightly.webkit.org/ Nightly Build])
 
** Google Chrome 4.0.221.8 (Nightly Build, [http://www.khronos.org/news/permalink/webgl-seen-in-chrome-browser/ siehe hier])
 
** Safari (Nightly Build, ab ca. 23.9.2009)
 
* <span style="color:grey;">...bitte ergänzen...</span>
 
  
 +
==Demos==
  
__TOC__
 
  
 +
Zunächst einmal ein paar Demos um die Funktionalität des eigenen Browsers zu testen.
 +
* [http://webglsamples.org/ ] - Ein paar Demos die zeigen, was WebGL leistet.
  
==Demos==
+
Noch 2 Bilder, zu denen es keine Source mehr gibt:
Zunächst einmal ein paar Demos um die Funktionalität des eigenen Browsers zu testen.
 
 
{|{{Prettytable_B1}}
 
{|{{Prettytable_B1}}
| [[Bild:webgl-sporeviewer.jpg|320px]]<br />[http://people.mozilla.com/~vladimir/webgl/spore/sporeview.html Spore Creature Viewer]
+
| [[Bild:webgl-sporeviewer.jpg|320px]]
| [[Bild:webgl-puls.jpg|320px]]<br />[http://wakaba.c3.cx/w/puls.html Puls]
+
| [[Bild:webgl-metatunnel.jpg|320px]]
|-
 
| [[Bild:webgl-escher-droste.jpg|320px]]<br />[http://wakaba.c3.cx/w/escher_droste.html Escher-Droste Effekt]
 
| [[Bild:webgl-metatunnel.jpg|320px]]<br />[http://cs.helsinki.fi/u/ilmarihe/metatunnel.html Metatunnel]
 
 
|}
 
|}
 
  
 
==WebGL==
 
==WebGL==
Zeile 67: Zeile 57:
  
 
// request rendering context from the canvas
 
// request rendering context from the canvas
try {
+
var names = [ "webgl", "experimental-webgl", "moz-webgl", "webkit-3d" ];
    // using Mozilla? (e.g. Firefox, ...)
+
for (var i=0; i<names.length; i++) {
    if (!gl) { gl = canvas.getContext("moz-webgl"); }
+
    try {  
} catch (e) { }
+
        gl = canvas.getContext(names[i]);
try {
+
        if (gl) { break; }
    // using Webkit? (e.g. Google Chrome, ...)
+
    } catch (e) { }
    if (!gl) { gl = canvas.getContext("webkit-3d"); }  
+
}
} catch (e) { }
 
 
 
 
if (!gl) {
 
if (!gl) {
 
     alert("No known OpenGL context detected! Is it enabled?");
 
     alert("No known OpenGL context detected! Is it enabled?");
 
     return;
 
     return;
 
}
 
}
 +
 +
// since WebGL is still experimental, we need some compatibility code
 +
compatibilityCode();
 
</source>
 
</source>
 +
 +
Da die verschiedenen Implementierungen noch nicht alle auf dem selben Stand sind ist im Augenblick noch etwas Kompatibilitätscode erforderlich. In Zukunft wird dies hoffentlich nicht mehr notwendig sein. Die Funktion <tt>compatibilityCode()</tt> findest du in der [[Tutorial_WebGL_Sample|Beispiel-Implementierung]] dieses Tutorials.
  
 
===Shader===
 
===Shader===
Zeile 91: Zeile 84:
 
     gl.shaderSource(shader, shaderSource);
 
     gl.shaderSource(shader, shaderSource);
 
     gl.compileShader(shader);
 
     gl.compileShader(shader);
   
+
 
     if (!gl.getShaderi(shader, gl.COMPILE_STATUS)) {
+
     if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 
         alert(gl.getShaderInfoLog(shader));
 
         alert(gl.getShaderInfoLog(shader));
 
         return null;
 
         return null;
 
     }     
 
     }     
   
+
 
 
     return shader;
 
     return shader;
 
}
 
}
Zeile 123: Zeile 116:
 
// linking
 
// linking
 
gl.linkProgram(program);
 
gl.linkProgram(program);
if (!gl.getProgrami(program, gl.LINK_STATUS)) {
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
 
     alert(gl.getProgramInfoLog(program));
 
     alert(gl.getProgramInfoLog(program));
 
}
 
}
Zeile 185: Zeile 178:
 
VertexArrays scheinen zumindest im Firefox bisher nicht implementiert zu sein. Das mag damit zusammenhängen, dass Arrays in JavaScript sehr langsam sind. Arrays müssen nämlich in JavaScript nicht zwangsläufig wirkliche zusammenhängende Arrays sein, sondern können auch als HashMap realisiert sein.
 
VertexArrays scheinen zumindest im Firefox bisher nicht implementiert zu sein. Das mag damit zusammenhängen, dass Arrays in JavaScript sehr langsam sind. Arrays müssen nämlich in JavaScript nicht zwangsläufig wirkliche zusammenhängende Arrays sein, sondern können auch als HashMap realisiert sein.
  
Um Geometrie zu rendern muss also zumindest im Augenblick auf VertexBufferObjects ([[VBO]]s) zurückgegriffen werden. Diese können im wesentlichen wie gewohnt benutzt werden. Es gibt eine spezielle Wrapper-Klasse <tt>CanvasFloatArray</tt> die ein JavaScript-Array mit Werten in ein richtiges float-Array umwandelt. Dieses <tt>CanvasFloatArray</tt> wird dann an OpenGL übergeben. Zusätzlich gibt es auch die Klassen <tt>CanvasByteArray</tt>, <tt>CanvasShortArray</tt> und <tt>CanvasIntArray</tt>. Außerdem existieren auch <tt>CanvasUnsignedByteArray</tt>, <tt>CanvasUnsignedShortArray</tt> und <tt>CanvasUnsignedIntArray</tt>.
+
Um Geometrie zu rendern muss also zumindest im Augenblick auf VertexBufferObjects ([[VBO]]s) zurückgegriffen werden. Diese können im wesentlichen wie gewohnt benutzt werden. Es gibt eine spezielle Wrapper-Klasse <tt>WebGLFloatArray</tt> die ein JavaScript-Array mit Werten in ein richtiges float-Array umwandelt. Dieses <tt>WebGLFloatArray</tt> wird dann an OpenGL übergeben. Zusätzlich gibt es auch die Klassen <tt>WebGLByteArray</tt>, <tt>WebGLShortArray</tt> und <tt>WebGLIntArray</tt>. Außerdem existieren auch <tt>WebGLUnsignedByteArray</tt>, <tt>WebGLUnsignedShortArray</tt> und <tt>WebGLUnsignedIntArray</tt>.
  
Leider existiert im Zusammenhang mit [[glVertexAttribPointer]] ein Bug in der aktuellen Firefox-Implementierung. Aus Sicherheitsgründen wird die mindestens notwendige Größe eines Vertexbuffers geprüft bevor darauf zugegriffen werden kann. Sofern wie im Beispiel ein Interleaved-VBO verwendet wird, wird diese [https://bugzilla.mozilla.org/show_bug.cgi?id=521667 Größe leider falsch berechnet], weshalb ein zusätzlicher Vertex notwendig ist.
+
Leider existiert im Zusammenhang mit [[glVertexAttribPointer]] ein Bug in der aktuellen Firefox-Implementierung. Aus Sicherheitsgründen wird die mindestens notwendige Größe eines Vertexbuffers geprüft bevor darauf zugegriffen werden kann. Sofern wie im Beispiel ein Interleaved-VBO verwendet wird, wird diese [https://bugzilla.mozilla.org/show_bug.cgi?id=521667 Größe leider falsch berechnet]. Um den Bug zu umgehen müssen im Augenblick Indices benutzt werden.
  
 
<source lang="javascript">
 
<source lang="javascript">
Zeile 193: Zeile 186:
 
var vertices = [
 
var vertices = [
 
     // position XYZ, normal XYZ, texcoord UV => 8 floats per vertex
 
     // position XYZ, normal XYZ, texcoord UV => 8 floats per vertex
    0.0,  0.5,  0.0,  0.6,  0.0,  0.8,  0.5,  0.0,
+
    -0.5,  0.5,  0.0,  0.6,  0.0,  0.8,  0.0,  0.0,
 +
    0.5,  0.5,  0.0,  0.6,  0.0,  0.8,  1.0,  0.0,
 
     -0.5, -0.5,  0.0,  0.0,  0.0,  1.0,  0.0,  1.0,
 
     -0.5, -0.5,  0.0,  0.0,  0.0,  1.0,  0.0,  1.0,
 
     0.5, -0.5,  0.0,  0.0,  0.6,  0.8,  1.0,  1.0,
 
     0.5, -0.5,  0.0,  0.0,  0.6,  0.8,  1.0,  1.0,
 
    // add an additional vertex, this is an workaround for this bug:
 
    // https://bugzilla.mozilla.org/show_bug.cgi?id=521667
 
    0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0,  0.0
 
 
];
 
];
 
+
// we need also some indices because of this annoying Firefox-Bug:
// create VBO
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=521667
 +
var indices = [
 +
  0, 1, 2, 2, 1, 3
 +
];
 +
// create VBO & IBO
 
var vbo = gl.createBuffer();
 
var vbo = gl.createBuffer();
 
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
 
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, new CanvasFloatArray(vertices), gl.STATIC_DRAW);
+
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);
 +
var ibo = gl.createBuffer();
 +
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
 +
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(indices), gl.STATIC_DRAW);
  
// setup vertex attributes for this interleaved VBO
+
// ...
 +
 
 +
// setup interleaved VBO and IBO
 +
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
 +
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
 
gl.enableVertexAttribArray(0);
 
gl.enableVertexAttribArray(0);
 
gl.enableVertexAttribArray(1);
 
gl.enableVertexAttribArray(1);
 
gl.enableVertexAttribArray(2);
 
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 4*8, 0); // position
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 8*4, 0*4); // position
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 4*8, 3); // normal
+
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 8*4, 3*4); // normal
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 4*8, 6); // texcoord
+
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8*4, 6*4); // texcoord
  
 
// draw the buffer
 
// draw the buffer
gl.drawArrays(gl.TRIANGLES, 0, 3);
+
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
 
</source>
 
</source>
  
Der schnellste Weg ein Mesh zu laden, dürfte sein dessen Vertex- und Indexbuffer direkt als JavaScript-Array anzugeben. Die Daten können so nativ vom Browser geladen werden. Bisher hat nach meinem Wissen noch niemand einen Benchmark versucht, aber das manuelle Parsen der Daten dauert bei einer JavaScript-Implementierung wahrscheinlich vergleichsweise lange. Außerdem dürfte es in JavaScript schwierig werden Binärdateien zu lesen. Folgende Loader bzw. Konverter sind bekannt:
+
Der schnellste Weg ein Mesh zu laden ist dessen Vertex- und Indexbuffer direkt als JavaScript-Array anzugeben. Die Daten können so nativ vom Browser geladen werden. Größere Meshes hat man natürlich ungern direkt im Quellcode und möchte diese auch vielleicht nicht direkt beim Start der Anwendung laden. Hier bietet sich eine eigene Datei an, die dann über ein [http://www.w3schools.com/XML/xml_http.asp XMLHttpRequest]-Objekt (Stichwort: [http://de.wikipedia.org/wiki/AJAX AJAX]) in JavaScript nachgeladen und anschließend dem Browser zum Parsen übergeben wird. So kann das Laden im Hintergrund realisiert werden.
 +
 
 +
Ein Problem ist natürlich das Dateiformat. Bisher hat nach meinem Wissen noch niemand einen Benchmark versucht, aber das manuelle Parsen der Daten dauert bei einer JavaScript-Implementierung wahrscheinlich vergleichsweise lange. Außerdem dürfte es in JavaScript schwierig werden Binärdaten zu lesen. Wenn man Daten aus einer externen Datei laden möchte, bietet sich das JSON-Format an. Dieses kann mit der JavaScript-Funktion <tt>eval()</tt> extrem schnell (da nativ) verarbeitet werden. Die folgenden Loader bzw. Konverter sind bekannt:
 
* Der [http://people.mozilla.com/~vladimir/webgl/spore/sporeview.html Spore Creature Viewer] lädt aus dem Spiel [http://www.spore.com/ Spore] exportierte Meshes direkt mit JavaScript. Spore benutzt das [http://www.collada.org/ COLLADA]-Format.
 
* Der [http://people.mozilla.com/~vladimir/webgl/spore/sporeview.html Spore Creature Viewer] lädt aus dem Spiel [http://www.spore.com/ Spore] exportierte Meshes direkt mit JavaScript. Spore benutzt das [http://www.collada.org/ COLLADA]-Format.
 
* Konverter für [http://en.wikipedia.org/wiki/Obj Wavefront OBJ] zu JavaScript-Arrays: [http://www.c3dl.org/index.php/c3dl-dev/wavefront-obj-file-to-javascript-arrays/ Beschreibung], [http://www.c3dl.org/index.php/download/ Download]
 
* Konverter für [http://en.wikipedia.org/wiki/Obj Wavefront OBJ] zu JavaScript-Arrays: [http://www.c3dl.org/index.php/c3dl-dev/wavefront-obj-file-to-javascript-arrays/ Beschreibung], [http://www.c3dl.org/index.php/download/ Download]
 
* Maxscript ([http://de.wikipedia.org/wiki/3ds_Max Autodesk 3ds Max]) welches JavaScript-Arrays exportiert: [http://www.c3dl.org/index.php/download/ Download]
 
* Maxscript ([http://de.wikipedia.org/wiki/3ds_Max Autodesk 3ds Max]) welches JavaScript-Arrays exportiert: [http://www.c3dl.org/index.php/download/ Download]
 +
* [http://www.khronos.org/message_boards/viewtopic.php?f=44&t=2171 Thread im Khronos-Forum] der sich mit dem effizienten Laden von Geometriedaten aus XML- und JSON-Formaten beschäftigt.
  
Größere Meshes hat man natürlich ungern direkt im Quellcode und möchte diese auch vielleicht nicht direkt beim Start der Anwendung laden. Hier bietet sich eine eigene HTML-Datei an, die dann über ein [http://www.w3schools.com/XML/xml_http.asp XMLHttpRequest]-Objekt (Stichwort: [http://de.wikipedia.org/wiki/AJAX AJAX]) in JavaScript nachgeladen und anschließend dem Browser zum Parsen übergeben wird. So kann das Laden im Hintergrund realisiert werden.
+
Es lohnt sich übrigens diese großen JavaScript-Arrays so kompakt wie möglich zu schreiben. Alleine durch das Weglassen von unnötigen Leerzeichen spart man schnell einige 10 oder 100 kB bei der Dateigröße. Des weiteren erlaubt das HTTP-Protokoll auch die Übertragung mit GZIP-komprimierter Daten. Aktuelle Browser sollten dies nativ unterstützen, so dass keine eigene Implementierung der Komprimierung erforderlich ist. Allerdings muss der HTTP-Server entsprechend konfiguriert sein.
 
 
Es lohnt sich übrigens diese großen JavaScript-Arrays so kompakt wie möglich zu schreiben. Alleine durch das Weglassen von unnötigen Leerzeichen spart man schnell einige 10 oder 100 kB bei der Dateigröße.
 
  
 
===Texturen===
 
===Texturen===
Zeile 264: Zeile 266:
  
 
Genau wie OpenGL 3.1 besitzt auch OpenGL ES 2.0 keine eigenen Mathematikfunktionen mehr. Einen guten Ansatz bietet hierbei das [http://wiki.delphigl.com/index.php/Tutorial_OpenGL3_Lineare_Algebra Lineare Algebra Tutorial von TAK2004]. Beliebt im Zusammenhang mit WebGL ist im Augenblick die [http://sylvester.jcoglan.com/ JavaScript-Bibliothek Sylvester] die grundlegende Vektor und Matrix Operationen bereitstellt. Die [http://bjartr.blogspot.com/2009/10/webgl-what-is-it-and-how-can-i-use-it_04.html WebGLU Library] versucht unter Rückgriff auf Sylvester die aus OpenGL 2.0 (und älter) bekannten Funktionen wie den Matrix-Stack zurückzubringen.
 
Genau wie OpenGL 3.1 besitzt auch OpenGL ES 2.0 keine eigenen Mathematikfunktionen mehr. Einen guten Ansatz bietet hierbei das [http://wiki.delphigl.com/index.php/Tutorial_OpenGL3_Lineare_Algebra Lineare Algebra Tutorial von TAK2004]. Beliebt im Zusammenhang mit WebGL ist im Augenblick die [http://sylvester.jcoglan.com/ JavaScript-Bibliothek Sylvester] die grundlegende Vektor und Matrix Operationen bereitstellt. Die [http://bjartr.blogspot.com/2009/10/webgl-what-is-it-and-how-can-i-use-it_04.html WebGLU Library] versucht unter Rückgriff auf Sylvester die aus OpenGL 2.0 (und älter) bekannten Funktionen wie den Matrix-Stack zurückzubringen.
 +
 +
Soweit von mir, im Anschluss findet ihr noch ein Beispiel sowie eine Liste von Links die nach Möglichkeit aktuell gehalten wird.
 +
 +
'''Coolcat'''
 +
{{TUTORIAL_NAVIGATION|[[Tutorial_GLScene]]|-}}
 +
 +
[[Kategorie:Tutorial|WebGL]]
  
 
==Beispiel==
 
==Beispiel==
Zeile 271: Zeile 280:
  
 
==Links==
 
==Links==
* Offizielle Dokumentation
+
* [[WebGL#Links|Hauptseite WebGL]]
** [http://www.khronos.org/opengles/2_X/ OpenGL ES 2.X and the OpenGL ES Shading Language]
 
** [http://www.khronos.org/registry/gles/ Khronos OpenGL ES API Registry]
 
** [http://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.24.pdf OpenGL ES 2.0.24  Full Specification]
 
** [http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf OpenGL ES Shading Language 1.0.17]
 
** [http://www.khronos.org/developers/resources/webgl/ WebGL Implementations, Tutorials and Sample Code]
 
** [http://www.khronos.org/message_boards/viewforum.php?f=35 Offizielles WebGL Forum]
 
* Bibliotheken
 
** [http://bjartr.blogspot.com/2009/10/webgl-what-is-it-and-how-can-i-use-it_04.html WebGL Tutorial and WebGLU Library]
 
** [http://sylvester.jcoglan.com/ Sylvester - Vector and Matrix math for JavaScript]
 
* Portierung der NeHe OpenGL Tutorials zu WebGL:
 
** [http://learningwebgl.com/blog/?p=11 Lesson 0: Getting started]
 
** [http://learningwebgl.com/blog/?p=28 Lesson 1: A triangle and a square]
 
** [http://learningwebgl.com/blog/?p=134 Lesson 2: Adding colour]
 
** [http://learningwebgl.com/blog/?p=239 Lesson 3: A bit of movement]
 
* Demos
 
** [http://www.cs.rit.edu/~bpd9116/webgl/OneSpinningTriangle.html OneSpinningTriangle], minimale Demo
 
** [http://people.mozilla.com/~vladimir/webgl/spore/sporeview.html Spore Creature Viewer], ein Viewer für [http://www.collada.org/ COLLADA]-Meshes, allerdings nur für aus dem Spiel [http://www.spore.com/ Spore] exportierte Meshes.
 
** [http://wakaba.c3.cx/w/puls.html Puls]
 
** [http://wakaba.c3.cx/w/escher_droste.html Escher-Droste Effekt]
 
** [http://cs.helsinki.fi/u/ilmarihe/metatunnel.html Metatunnel]
 
** [http://code.google.com/p/chromium/source/browse/trunk/samples/webgl Beispiele für Chromium], funktionieren scheinbar nicht mit Firefox
 
** [http://learningwebgl.com/blog/?p=205 Zooming into the Mandelbrot set in a WebGL fragment shader]
 
** [http://bjartr.blogspot.com/2009/10/interactive-shader-editing-with-webgl.html Interactive Shader Editing with WebGL and the WebGLU Library]
 
* Sonstiges
 
** [http://www.planet-webgl.org/ Planet WebGL], eine Aggregation verschiedener Blogs über WebGL
 

Aktuelle Version vom 17. Juli 2018, 16:41 Uhr

Vorwort

In diesem Tutorial werden die wesentlichen Besonderheiten von WebGL gegenüber anderen OpenGL-Anwendungen vorgestellt. Kenntnisse in JavaScript und OpenGL ES, also insbesondere GLSL und VBOs werden vorausgesetzt. Neben diesem Tutorial existieren auch einige andere Tutorials, aber ein Blick in den Quellcode der verfügbaren Demos kann nie schaden.

Das neuere WebGL 2.0 lehnt an OpenGL ES 3.00 an und somit werden werden auch VAO unterstütz.
Siehe auch:

Demos

Zunächst einmal ein paar Demos um die Funktionalität des eigenen Browsers zu testen.

  • [1] - Ein paar Demos die zeigen, was WebGL leistet.

Noch 2 Bilder, zu denen es keine Source mehr gibt:

webgl-sporeviewer.jpg webgl-metatunnel.jpg

WebGL

Context

Um WebGL nutzen zu können benötigt man zunächst einmal ein HTML5 Canvas-Element, welchem wir passender weise die ID "canvas" geben. Wir können die Anwendung beispielsweise über das onload-Event des body-Elements startet. Genauso gut kann man natürlich auch einen Link oder Button verwenden.

<html>
    <head>
        <script type="text/javascript">
	<!--
            function init() {

                // ...

            }
	// -->
        </script> 
    </head>
    <body onload="init()">
        <div style="text-align: center">
            <canvas id="canvas" width="640" height="480"></canvas>
        </div>
    </body>
</html>

Von unserem Canvas erhalten wir zum einen die spätere Viewportgröße und zum anderen einen sogenannten Context. Das Context-Objekt erlaubt uns sämtliche OpenGL ES 2.0 Funktionen aufzurufen.

// our WebGL rendering context, it might be useful to use a global variable for this
var gl = null;

// grab the canvas object and its dimensions
var canvas = document.getElementById("canvas");
var viewportWidth = canvas.width;
var viewportHeight = canvas.height;

// request rendering context from the canvas
var names = [ "webgl", "experimental-webgl", "moz-webgl", "webkit-3d" ];
for (var i=0; i<names.length; i++) {
    try { 
        gl = canvas.getContext(names[i]);
        if (gl) { break; }
    } catch (e) { }
}
if (!gl) {
    alert("No known OpenGL context detected! Is it enabled?");
    return;
}

// since WebGL is still experimental, we need some compatibility code
compatibilityCode();

Da die verschiedenen Implementierungen noch nicht alle auf dem selben Stand sind ist im Augenblick noch etwas Kompatibilitätscode erforderlich. In Zukunft wird dies hoffentlich nicht mehr notwendig sein. Die Funktion compatibilityCode() findest du in der Beispiel-Implementierung dieses Tutorials.

Shader

In OpenGL ES 2.0 herrscht Shader-Zwang, eine feste Funktionspipeline existiert nicht. Stehen der verfügbaren Grafikhardware keine GLSL-Shader zur Verfügung, muss OpenGL ES in den Softwaremodus geschaltet werden und wird entsprechend langsam. Das Laden von GLSL-Shadern geschieht wie gewohnt. Zuerst werden Vertex- und Fragmentshader geladen und compiliert. Beide Shader werden dann an ein Program-Objekt angehängt und gelinkt.

function loadShader(shaderType, shaderSource) {
    var shader = gl.createShader(shaderType);
    if (!shader) { return null; }    
    gl.shaderSource(shader, shaderSource);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }    

    return shader;
}

var vertexShaderSource = "...Vertexshader als String...";
var fragmentShaderSource = "...Fragmentshader als String...";

var vertexShader = loadShader(gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = loadShader(gl.FRAGMENT_SHADER, fragmentShaderSource);
if (!vertexShader || !fragmentShader) {
    alert("Shader problem");
}

// create program object
var program = gl.createProgram();

// attach our two shaders to the program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);

// setup attributes (optional)
gl.bindAttribLocation(program, 0, "aPosition");
gl.bindAttribLocation(program, 1, "aNormal");
gl.bindAttribLocation(program, 2, "aTexCoord");

// linking
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    alert(gl.getProgramInfoLog(program));
}

// setup uniforms (optional)
gl.useProgram(program);
gl.uniform1i(gl.getUniformLocation(program, "uTexture"), 0);

Im obigen Beispiel wird der Shader-Quellcode fest als String in den JavaScript-Code integriert. Das ist natürlich ziemlich unübersichtlich. Vom Prinzip spielt es keine Rolle wo der String herkommt. Beispielsweise kann man ihn mit einem HTTP-Request in einer Shader-Datei vom Server laden. Es ist aber auch möglich den Shader-Code als spezielles Script-Element in die HTML-Datei einzubinden.

<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aPosition;
attribute vec3 aNormal;
attribute vec2 aTexCoord;
varying vec3 vNormal;
varying vec2 vTexCoord; 
void main() {
    gl_Position = vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
    vNormal = aNormal;
}
</script>

<script id="shader-fs" type="x-shader/x-fragment">
varying vec3 vNormal;
varying vec2 vTexCoord;
uniform sampler2D uTexture;
void main() {
    gl_FragColor = texture2D(uTexture, vTexCoord);
}
</script>

Der Browser hat vom Script-Typ "x-shader/x-vertex" natürlich noch nie etwas gehört und wird das Element entsprechend einfach ignorieren. Mit einer einfachen JavaScript-Funktion kann man aber trotzdem an den Inhalt gelangen.

function getShaderSource(id) {
    var script = document.getElementById(id);
    if (!script) { return null; }
    
    var source = "";
    var child = script.firstChild;
    while (child) {
        // node is a "textnode" ?
        // see: http://de.selfhtml.org/javascript/objekte/node.htm#node_type
        if (child.nodeType == 3) {
            source += child.textContent;
        }
        child = child.nextSibling;
    }
    return source;
}

var vertexShaderSource = getShaderSource("shader-vs");
var fragmentShaderSource = getShaderSource("shader-fs");

VertexBufferObjects

VertexArrays scheinen zumindest im Firefox bisher nicht implementiert zu sein. Das mag damit zusammenhängen, dass Arrays in JavaScript sehr langsam sind. Arrays müssen nämlich in JavaScript nicht zwangsläufig wirkliche zusammenhängende Arrays sein, sondern können auch als HashMap realisiert sein.

Um Geometrie zu rendern muss also zumindest im Augenblick auf VertexBufferObjects (VBOs) zurückgegriffen werden. Diese können im wesentlichen wie gewohnt benutzt werden. Es gibt eine spezielle Wrapper-Klasse WebGLFloatArray die ein JavaScript-Array mit Werten in ein richtiges float-Array umwandelt. Dieses WebGLFloatArray wird dann an OpenGL übergeben. Zusätzlich gibt es auch die Klassen WebGLByteArray, WebGLShortArray und WebGLIntArray. Außerdem existieren auch WebGLUnsignedByteArray, WebGLUnsignedShortArray und WebGLUnsignedIntArray.

Leider existiert im Zusammenhang mit glVertexAttribPointer ein Bug in der aktuellen Firefox-Implementierung. Aus Sicherheitsgründen wird die mindestens notwendige Größe eines Vertexbuffers geprüft bevor darauf zugegriffen werden kann. Sofern wie im Beispiel ein Interleaved-VBO verwendet wird, wird diese Größe leider falsch berechnet. Um den Bug zu umgehen müssen im Augenblick Indices benutzt werden.

// define some vertexdata
var vertices = [
    // position XYZ, normal XYZ, texcoord UV => 8 floats per vertex
    -0.5,  0.5,  0.0,  0.6,  0.0,  0.8,  0.0,  0.0,
     0.5,  0.5,  0.0,  0.6,  0.0,  0.8,  1.0,  0.0,
    -0.5, -0.5,  0.0,  0.0,  0.0,  1.0,  0.0,  1.0,
     0.5, -0.5,  0.0,  0.0,  0.6,  0.8,  1.0,  1.0,
];
// we need also some indices because of this annoying Firefox-Bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=521667
var indices = [
   0, 1, 2, 2, 1, 3
];
// create VBO & IBO
var vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);
var ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(indices), gl.STATIC_DRAW);

// ...

// setup interleaved VBO and IBO
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
gl.enableVertexAttribArray(2);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 8*4, 0*4); // position
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 8*4, 3*4); // normal
gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8*4, 6*4); // texcoord

// draw the buffer
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

Der schnellste Weg ein Mesh zu laden ist dessen Vertex- und Indexbuffer direkt als JavaScript-Array anzugeben. Die Daten können so nativ vom Browser geladen werden. Größere Meshes hat man natürlich ungern direkt im Quellcode und möchte diese auch vielleicht nicht direkt beim Start der Anwendung laden. Hier bietet sich eine eigene Datei an, die dann über ein XMLHttpRequest-Objekt (Stichwort: AJAX) in JavaScript nachgeladen und anschließend dem Browser zum Parsen übergeben wird. So kann das Laden im Hintergrund realisiert werden.

Ein Problem ist natürlich das Dateiformat. Bisher hat nach meinem Wissen noch niemand einen Benchmark versucht, aber das manuelle Parsen der Daten dauert bei einer JavaScript-Implementierung wahrscheinlich vergleichsweise lange. Außerdem dürfte es in JavaScript schwierig werden Binärdaten zu lesen. Wenn man Daten aus einer externen Datei laden möchte, bietet sich das JSON-Format an. Dieses kann mit der JavaScript-Funktion eval() extrem schnell (da nativ) verarbeitet werden. Die folgenden Loader bzw. Konverter sind bekannt:

Es lohnt sich übrigens diese großen JavaScript-Arrays so kompakt wie möglich zu schreiben. Alleine durch das Weglassen von unnötigen Leerzeichen spart man schnell einige 10 oder 100 kB bei der Dateigröße. Des weiteren erlaubt das HTTP-Protokoll auch die Übertragung mit GZIP-komprimierter Daten. Aktuelle Browser sollten dies nativ unterstützen, so dass keine eigene Implementierung der Komprimierung erforderlich ist. Allerdings muss der HTTP-Server entsprechend konfiguriert sein.

Texturen

Das Laden von Texturen ist relativ einfach, da der Browser bereits über die nötige Infrastruktur zum Laden von Bildern in vielen Formaten bereitstellt. Etwas ungewohnt ist der asynchrone Ladevorgang: Ein Bild steht nicht sofort zur Verfügung, da dieses ja möglicherweise zuerst vom Server geladen werden muss. Sobald aber das Bild verfügbar ist wird das onload-Event ausgelöst. Was im Fehlerfall geschehen soll wird über das onerror-Event festgelegt.

function loadTexture(filename) {
    // preparations
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    var image = new Image();

    // register event handlers
    image.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, image);
        gl.generateMipmap(gl.TEXTURE_2D);
        draw(); // texture now available, we can redraw the scene
    }
    image.onerror = function() {
        alert("error while loading image '"+filename+"'.");
    }

    // request image from server
    image.src = filename; 

    // return texture object (asynchronous loading, texture NOT available yet!)
    return texture;
}

Ressourcen und Mathematik

Dadurch das Ressourcen (Meshes, Texturen, Shader, ...) im Browser asynchron geladen werden, wird entsprechendes Management dieser Ressourcen erforderlich. Eine Ressource darf erst verwendet werden, wenn sie auch wirklich initialisiert wurde. Entsprechend müssen die zugehörigen Callbacks abgewartet werden.

Eine einfache Lösung für dieses Problem ist ein Ressourcenmanager der alle Daten lädt oder zumindest weiß welche Ressourcen noch geladen werden müssen. Zu Beginn wird einfach nur der Text "Loading..." angezeigt. Jede Ressource meldet sich beim Manager sobald sie verfügbar ist. Sobald alles korrekt initialisiert wurde kann der Ressourcenmanager die eigentliche Anwendung starten.

Genau wie OpenGL 3.1 besitzt auch OpenGL ES 2.0 keine eigenen Mathematikfunktionen mehr. Einen guten Ansatz bietet hierbei das Lineare Algebra Tutorial von TAK2004. Beliebt im Zusammenhang mit WebGL ist im Augenblick die JavaScript-Bibliothek Sylvester die grundlegende Vektor und Matrix Operationen bereitstellt. Die WebGLU Library versucht unter Rückgriff auf Sylvester die aus OpenGL 2.0 (und älter) bekannten Funktionen wie den Matrix-Stack zurückzubringen.

Soweit von mir, im Anschluss findet ihr noch ein Beispiel sowie eine Liste von Links die nach Möglichkeit aktuell gehalten wird.

Coolcat


Vorhergehendes Tutorial:
Tutorial_GLScene
Nächstes Tutorial:
-

Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com.
Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen.

Beispiel

Eine Beispielanwendung die die hier vorgestellten Code-Schnippsel zusammenfügt ist hier verfügbar.

webgl-sample.jpg

Links