|
|
(22 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) |
Zeile 1: |
Zeile 1: |
− | {{Warnung|Dieser Artikel befindet sich noch im Aufbau!}}
| + | #REDIRECT [[Tutorial WebGL]] |
− | WebGL ist der neue Standard für OpenGL im Browser. Der Standard ermöglicht es hardwarebeschleunigte 3D-Grafik im Browser darzustellen, ohne dabei auf spezielle Plugins angewiesen zu sein.
| |
− | | |
− | Dieses Tutorial ist ein Quick&Dirty Tutorial. Es werden nur 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 insb. [[GLSL]], [[VBO]]s und der notwendigen [[Tutorial_OpenGL3_Lineare_Algebra|linearen Algebra]] werden vorausgesetzt. Es sei darauf hingewiesen das WebGL im Augenblick noch hochgradig experimentell ist und es 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 nicht schaden sein.
| |
− | | |
− | Bisher wird der WebGL-Standard von den folgenden Browsern unterstützt:
| |
− | * 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.
| |
− | * 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>
| |
− | | |
− | | |
− | __TOC__
| |
− | | |
− | | |
− | ==Demos==
| |
− | Zunächst einmal ein paar Demos um die Funktionalität des eigenen Browsers zu testen.
| |
− | {|{{Prettytable_B1}}
| |
− | | [[Bild:webgl-sporeviewer.jpg|320px]]<br />[http://people.mozilla.com/~vladimir/webgl/spore/sporeview.html Spore Creature Viewer]
| |
− | | [[Bild:webgl-puls.jpg|320px]]<br />[http://wakaba.c3.cx/w/puls.html Puls]
| |
− | |-
| |
− | | [[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==
| |
− | | |
− | ===Context===
| |
− | Um WebGL nutzen zu können benötigt man zunächst einmal ein HTML5 Canvas-Element, welchem wir passenderweise die ID "canvas" geben.
| |
− | | |
− | <source lang="html4strict">
| |
− | <html>
| |
− | <head>
| |
− | | |
− | <!-- ... -->
| |
− | | |
− | </head>
| |
− | <body>
| |
− | <div style="text-align: center">
| |
− | <canvas id="canvas" width="640" height="480"></canvas>
| |
− | </div>
| |
− | </body>
| |
− | </html>
| |
− | </source>
| |
− | | |
− | Von diesem 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.
| |
− | | |
− | <source lang="javascript">
| |
− | // 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
| |
− | try {
| |
− | // using Mozilla? (e.g. Firefox, ...)
| |
− | if (!gl) { gl = canvas.getContext("moz-webgl"); }
| |
− | } catch (e) { }
| |
− | try {
| |
− | // using Webkit? (e.g. Google Chrome, ...)
| |
− | if (!gl) { gl = canvas.getContext("webkit-3d"); }
| |
− | } catch (e) { }
| |
− | | |
− | if (!gl) {
| |
− | alert("No known OpenGL context detected! Is it enabled?");
| |
− | return;
| |
− | }
| |
− | </source>
| |
− | | |
− | ===Shader===
| |
− | In OpenGL ES 2.0 herrscht Shader-Zwang, eine [[Feste Funktionspipeline|feste Funktionspipeline]] existiert nicht. 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.
| |
− | | |
− | <source lang="javascript">
| |
− | function loadShader(shaderType, shaderSource) {
| |
− | var shader = gl.createShader(shaderType);
| |
− | if (!shader) { return null; }
| |
− | gl.shaderSource(shader, shaderSource);
| |
− | gl.compileShader(shader);
| |
− |
| |
− | if (!gl.getShaderi(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 and uniforms (optional)
| |
− | gl.bindAttribLocation(program, 0, "aPosition");
| |
− | gl.bindAttribLocation(program, 1, "aNormal");
| |
− | gl.bindAttribLocation(program, 2, "aTexCoord");
| |
− | gl.uniform1i(gl.getUniformLocation(program, "uTexture"), 0);
| |
− | | |
− | // linking
| |
− | gl.linkProgram(program);
| |
− | if (!gl.getProgrami(program, gl.LINK_STATUS)) {
| |
− | alert(gl.getProgramInfoLog(program));
| |
− | }
| |
− | </source>
| |
− | | |
− | 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.
| |
− | | |
− | <source lang="html4strict">
| |
− | <script id="shader-vs" type="x-shader/x-vertex">
| |
− | attribute vec3 aPosition;
| |
− | attribute vec3 aNormal;
| |
− | attribute vec3 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>
| |
− | </source>
| |
− | | |
− | Der Browser hat vom Script-Typ <tt>"x-shader/x-vertex"</tt> 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.
| |
− | | |
− | <source lang="javascript">
| |
− | function getShaderSource(id) {
| |
− | var script = document.getElementById(id);
| |
− | if (!script) { return null; }
| |
− |
| |
− | var source = "";
| |
− | var child = script.firstChild;
| |
− | while (child) {
| |
− | if (child.nodeType == 3) {
| |
− | source += child.textContent;
| |
− | }
| |
− | child = child.nextSibling;
| |
− | }
| |
− | return source;
| |
− | }
| |
− | | |
− | var vertexShaderSource = getShaderSource("shader-vs");
| |
− | var fragmentShaderSource = getShaderSource("shader-fs");
| |
− | </source>
| |
− | | |
− | ===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 ([[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>.
| |
− | | |
− | 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.
| |
− | | |
− | <source lang="javascript">
| |
− | // define some vertexdata
| |
− | var vertices = [
| |
− | // 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.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,
| |
− | | |
− | // 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
| |
− | ];
| |
− | | |
− | // create VBO
| |
− | var vbo = gl.createBuffer();
| |
− | gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
| |
− | gl.bufferData(gl.ARRAY_BUFFER, new CanvasFloatArray(vertices), gl.STATIC_DRAW);
| |
− | | |
− | // setup vertex attributes for this interleaved VBO
| |
− | gl.enableVertexAttribArray(0);
| |
− | gl.enableVertexAttribArray(1);
| |
− | gl.enableVertexAttribArray(2);
| |
− | gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 8, 0); // position
| |
− | gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 8, 3); // normal
| |
− | gl.vertexAttribPointer(2, 2, gl.FLOAT, false, 8, 6); // texcoord
| |
− | | |
− | // draw the buffer
| |
− | gl.drawArrays(gl.TRIANGLES, 0, 3);
| |
− | </source>
| |
− | | |
− | ===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 <tt>onload</tt>-Event ausgelöst.
| |
− | <source lang="javascript">
| |
− | function loadTexture(filename) {
| |
− | 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();
| |
− | 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+"'.");
| |
− | }
| |
− | image.src = filename;
| |
− | return texture;
| |
− | }
| |
− | </source>
| |
− | | |
− | ===Ressourcenmanagement===
| |
− | 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 <tt>"Loading..."</tt> angezeigt. Jede Ressource meldet sich beim Manager sobald sie verfügbar ist. Sobald alles korrekt initialisiert wurde kann der Ressourcenmanager die eigentliche Anwendung starten.
| |
− | | |
− | ==Links==
| |
− | * [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]
| |
− | * Portierung der NeHe OpenGL Tutorials zu WebGL:
| |
− | ** [http://learningwebgl.com/blog/?p=28 Lesson 1 – A triangle and a square]
| |
− | ** [http://learningwebgl.com/blog/?p=134 Lesson 2 – Adding colour]
| |
− | * Demos
| |
− | ** [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]
| |