senäh

17senäh und so…

WebGL Thumb

HTML/CSS/JS
14. Mrz 2012
Kommentare: 6

Einstieg in WebGL mit three.js

Kategorien: HTML/CSS/JS | 14. Mrz 2012 | Kommentare: 6

Vor einigen Tagen stand ich vor der Entscheidung über welches Thema ich als nächstes schreiben sollte. Über die HTML5 Game Engine, die ich schon länger plane? Einen Teil davon? Oder was ganz anderes? Am Ende habe ich mich für ein Thema entschieden, in welchem ich mich selbst überhaupt noch nicht auskenne und bei dem es verwunderlich ist, dass ich mit diesem noch nicht beschäftigt habe: WebGL! Warum habe ich mich noch nie mit WebGL beschäftigt?! Ich stehe auf Browserspiele, ich interessiere mich für 3D-Grafik – warum habe ich dann bitte schön noch nie etwas mit WebGL gemacht?! Höchste Zeit etwas nachzuholen.

Was ist WebGL?

Okay, ich habe etwas geflunkert. Ich habe WebGL zwar noch nie praktisch verwendet, aber dass ich mich nie damit beschäftigt hätte, wäre gelogen. Darum will ich mal kurz zusammenfassen, was ich alles über WebGL weiß: WebGL ist eine Grafik-API zur hardwarebeschleunigten Darstellung von 2D/3D-Grafiken im Browser, welche auf OpenGL ES 2.0 basiert und von der Khronos Group in eigenen Spezifikation definiert wird. WebGL ist damit kein Teil von HTML5, jedoch erfolgt der Zugriff auf die WebGL API über einen speziellen WebGL-Context eines Canvas Elements: canvas.getContext("webgl"). Grafiken, welche ich mittels der WebGL API zeichne, werden also ganz konventionell über ein Canvas Element dargestellt. WebGL wird von allen Browsern unterstützt. Wirklich allen? Nein, ein kleiner Browser namens IE weigert sich WebGL (ohne Plugins) darzustellen. Außerdem kann es sein, dass Nutzer mit WebGL-fähigen Browser trotzdem kein WebGL darstellen können, weil ihre Grafikkarten(treiber) nicht darauf ausgelegt sind. Chrome bietet deswegen als einziger Browser zusätzlich einen Softwarerenderer namens SwiftShader für WebGL an. Ob euer Browser (und eure Grafikkarte) am Ende WebGL unterstützen, könnt ihr mit dem WebGL Report testen. Ein Wort zu mobilen Browsern: hier sah es lange Zeit sehr mau aus. Mittlerweile haben zumindest Firefox for Mobile und Opera Mobile sowie ein paar Spezialfälle wie das BlackBerry PlayBook WebGL implementiert. Die derzeitige Chrome Beta für Android unterstützt noch kein WebGL, aber das dürfte sich in absehbarer Zeit ändern. Nach meinen Informationen ist iOS (bzw. Safari Mobile) prinzipiell in der Lage WebGL darzustellen, jedoch ist es nicht freigeschalten. Dumm -.-

Ursprünglich wollte ich mich in diesem Artikel mit absolut reinem WebGL beschäftigen. Ich wollte dazu ein Dreieck mit WebGL darstellen und es rotieren lassen. Ganz ähnlich wie bei meinem alten Molehill Beispiel. Ähnlich wie Molehill von Flash ist WebGL sehr low-level (und damit äußerst komplex für einfache Dinge, aber flexibel für komplexe Aufgaben). Bei meinen Vorbereitungen auf diesen Artikel bin ich jedoch in das überaus gefürchtete #Spechaos geraten (ja, das habe ich getaggt). Ich wurde durch ein Chaos an veralteten Specifications behindert. (Specification+Chaos=Spechaos. Eine eigene Erfindung ;))

Anstatt euch deswegen mit Halbwahrheiten über WebGL zu überschütten, muss ich auf ein Framework zurückgreifen. Glücklicherweise gibt es mit three.js einen de facto Standard für diese Aufgabe. Sollte ich mich später einmal richtig in WebGL eingelesen haben (und das habe ich definitiv vor), kann ich euch dieses Beispiel gerne noch einmal in Vanilla-WebGL zeigen. Um den Theorieteil abzuschließen möchte ich schlussendlich auf zwei Lernressourcen verweisen, welche perfekt für diejenigen sind, die noch tiefer in die Materie eintauchen möchten:

Falls ihr mittels WebGL nur 2D-Grafik anzeigen möchtet, könnte außerdem folgender HTML5Rocks Artikel interessant sein. Da er sich auf 2 Dimensionen beschränkt, ist er vielleicht auch etwas einsteigerfreundlicher.

Wie stelle ich einen Würfel mit three.js dar?

Genug palavert, jetzt machen wir Eier. Anstatt einem einfachem Dreieck wie im Molehill Beispiel werden ich einen Würfel mit three.js erzeugen. Dies geht relativ flott. Zuerst benötigen wir natürlich ein Canvas Element, welches die ID "webglCanvas" erhält:

1
<canvas id="webglCanvas" width="400" height="400"></canvas>

Das Canvas Element repräsentiert natürlich den Bereich, innerhalb dessen wir 3D-Grafiken mit WebGL/three.js darstellen können. Das Canvas Element könnt ihr irgendwo platzieren. Ich habe es innerhalb eines leeren <body> Tags erstellt. Danach können wir schon in den JavaScript-Code springen.

Falls es in Zukunft Probleme mit dem Code geben sollte: In diesem Beispiel verwende ich die Version r48 von three.js. Beginnen wir damit das eben erstellte Canvas Element in JavaScript zu referenzieren:

1
var canvas = document.getElementById("webglCanvas");

Spitze! Ihr macht das gut ;) So ein bisschen Lob gefällt euch, nicht war? :D Dann freut euch jetzt auf das erste three.js Objekt: THREE.Scene! Wie ihr seht erreicht ihr alle Objekte von three.js über das globale Objekt THREE. Man bezeichnet so etwas auch als Namespace. Das Scene-Objekt ist eine Art Container, welche alle für einen 3D-Raum relevanten Objekte beinhaltet. Dazu gehören Kamera, Lichtquellen, 3D-Modelle usw. Zunächst benötigen wir nur eine neue Instanz von Scene ohne weitere Konfiguration:

1
var scene = new THREE.Scene();

Kommen wir zum nächsten wichtigen Objekt: THREE.PerspectiveCamera. Eine perspektivische Kamera stellt Objekte perspektivisch dar. Simpel oder? ;) Anders formuliert: Objekt in der Ferne sehen kleiner aus als in der Nähe. Der Compagnon einer perspektivischen Kamera ist die orthografische Kamera (THREE.OrthographicCamera). Ich verwende hier die perspektivische Kamera, weil sie dem menschlichem Sehen entspricht. Und damit wisst ihr auch gleich, wozu die Kamera dient. Die Kamera hat die gleiche Funktion wie unsere Augen. Wir sehen mit ihr die Welt, also scene. Eine Kamera benötigt im Wesentlichen eine Position und eine Blickrichtung. Die Position verändert ihr mit den Eigenschaften .x, .y und .z und die Blickrichtung mit der Methode .lookAt(new THREE.Vector3(x, y, z)). Der Konstruktor der perspektivischen Kamera benötigt zusätzlich noch vier andere Parameter: FOV/Bildwinkel in Grad, das Bildverhältnis, die nahe und die ferne Clipping Begrenzung. Als Bildwinkel nehmen wir 50° (was laut Wikipedia dem natürlichen Sehen entsprechen soll), das Bildverhältnis ist canvas.width / canvas.height und als nahe bzw. ferne Clipping Begrenzung nehmen wir 1 bzw. 1000. Was aber ist überhaupt Clipping?! Es handelt sich dabei einfach um ein Optimierungsverfahren. Die Kamera “sieht” dabei nur Objekt innerhalb der Clipping Begrenzung. Nähere oder fernere Objete werden ignoriert und müssen so nicht berechnet werden. Da wir aber nur einen einzigen Würfel darstellen wollen, ist uns das erst einmal egal. Anschließend positionieren wir die Kamera etwas und fügen sie der scene hinzu.

1
2
3
4
var camera = new THREE.PerspectiveCamera(
   50, canvas.width / canvas.height, 1, 1000);
camera.position.z = 500;
scene.add(camera);

Danach erstellen wir eine neue Instanz des Objektes THREE.WebGLRenderer. Ein Renderer berechnet die Darstellung eines Ausschnitts der 3D-Welt, also was camera nun eigentlich in scene sieht, und zeichnet sie auf ein Canvas Element. three.js liefert verschiedene Renderer mit, welche auch 3D-Darstellung komplett ohne WebGL berechnen können. Da WebGL aber die performanteste Variante darstellt (Stichwort: Hardwarebeschleunigung und Verwendung der Grafikkarte), verwenden wir auch THREE.WebGLRenderer. Dieser benötigt als Parameter unser anfangs erstelltes Canvas Element sowie dessen Dimensionen:

1
2
var renderer = new THREE.WebGLRenderer({canvas:canvas});
renderer.setSize(canvas.width, canvas.height);

Ein 3D-Objekt besteht im wesentlichen aus Polygonen und ihrer Oberfläche/Textur. Mit Polygonen bezeichne ich hier konkret Polygon mit drei Eckpunkten (alle Polygone eines 3D-Objektes bezeichnet man auch als Mesh) und mit Oberfläche/Textur innerhalb von three.js Materialien. Unser 3D-Objekt soll ein Würfel werden. Wir müssen also irgendwie die Eckpunkte des Würfels und seine sechs Seiten beschreiben. Fange wir mit den sechs Seiten an. Dazu erstellen wir sechs verschiedene Materialien (genauer: sechs Instanzen von THREE.MeshBasicMaterial), denen wir zufällig eine Farbe zuweisen. Alle Materialien speichern wir in einem Array:

1
2
3
4
5
6
var materials = [];
for (var i = 0; i < 6; i ++) {
   materials.push(
      new THREE.MeshBasicMaterial(
         {color: Math.random() * 0xffffff }));
}

Nett oder? So ein bisschen Hexadezimal-Farben-Voodoo wärmt einen immer das Herz. Erstellen wir nur die Polygone und weisen ihnen die Materialien zu. Wir bedienen uns dabei einiger in three.js vorgefertigter Objekte. Konkret erstellen wir eine Würfelgeometrie (THREE.CubeGeometry), welche 7 Parameter erhält (huh!). Das wären Breite, Höhe, Tiefe (alle drei 200), Breitensegmentierung, Höhensegmentierung, Tiefensegmentierung (alle drei 1) und unsere materials. Segmentierung heißt in diesem Fall, dass wir keine der Würfelseiten unterteilt haben möchten. (Ein Zauberwürfel wäre bspw. mehrfach an den Seiten unterteilt.) Diesen Würfel übergeben wir einer neuen Instanz von THREE.Mesh, unserem eigentlichen 3D-Objekt. Als zweiten Konstruktorparameter erhält sie zusätzlich eine Instanz von THREE.MeshFaceMaterial(), welche nur sagt “Ja, unser Würfel hat sechs einfarbige Seite. Bitte zeige sie uns auch.” Die eigentliche Mesh Instanz könnt ihr dann frei im Raum bewegen, drehen, etc.

1
2
3
4
var cube = new THREE.Mesh(
   new THREE.CubeGeometry(200, 200, 200, 1, 1, 1, materials),
   new THREE.MeshFaceMaterial());
scene.add(cube);

Achja: Zur scene muss der Würfel natürlich hinzugefügt werden ;) Anschließend müssen wir dem Renderer nur noch sagen, welche Scene Instanz und Kamera berechnet werden soll und schon sehen wir auf dem Canvas einen Würfel.

1
renderer.render(scene, camera);

Und so sieht das Ergebnis aus:

Hmm… sieht eigentlich nur nach einem einfachen Viereck aus oder? Damit wir wirklich sehen, dass es sich um einen Würfel handelt, können wir ihn ganz einfach animieren. Ersetzt den render() Aufruf einfach mit folgendem Code:

1
2
3
4
5
6
7
8
9
10
11
function render() {
   cube.rotation.x += 0.009;
   cube.rotation.y += 0.006;
   cube.rotation.z += 0.003;
   renderer.render(scene, camera);
}

(function animate() {
   render();
   requestAnimationFrame(animate);
})();

(Mehr Infos über requestAnimationFrame.)

Ahjaaaaa! DAS ist ein Würfel!

Damit bin ich mit meiner Einführung in three.js fertig. Vielleicht gibt es wann anders noch einmal ein schwierigeres Beispiel :)

(Hinweis: Solltet ihr das Beispiel nicht sehen können, gibt es wahrscheinlich ein temporäres Problem mit JsFiddle bzw. der Verlinkung zu three.js. Zu einem späteren Zeitpunkt sollte es wieder funktionieren.)

 

Autor: Pipo

...kommt ursprünglich aus der Designerecke, ist aber im Laufe seines Studiums in die Webentwicklung gestolpert. Kann sich seit dem nicht so richtig entscheiden wo er hingehört und wagt deswegen vielleicht die Flucht nach vorne in ein komplett neues Gebiet. Vielleicht Management? Niemand weiß es. Auch er nicht.

Kommentare (6)

  1. Pingback: dario. domi. de. » HTML5 Canvas Examples & Demos

  2. Danke für deine schöne Erklärung!

    Ich arbeite seit ein paar Wochen auch mit three.js und suche im Netz eine Erklärung wie ich ein Collada-Objekt so in die Szene unterbringe, so das ich es auch selektieren kann und verschieben. Ich arbeite ohne WebGL und nehme nur den Canvas renderer. Das soll auch erstmal so bleiben! :)

    Hast du eine Idee mit dem Objekt dann melde dich bitte.

    Grüße, Marc 

  3. Hi Marc :)

    Hast du das Collada-Modell schon geladen? Wenn nicht, bietet die ThreeJS einen entsprechenden Loader. Das sieht ungefähr so aus:
    var loader = new THREE.ColladaLoader();
    var dae;
    loader.load( PATH_TO_COLLADA, function (collada) { dae = collada.scene; };)
    (Hier eine ausführlichere Erklärung.) 

    Anschließend kannst du die  ”dae” Instanz einfach zur “scene” hinzufügen: scene.add(dae); 
    Oder hast du Probleme beim Selektieren? Wenn ja, meinst du, dass du das Objekt mit der Maus anklicken und anschließend bewegen möchtest?

    Viele Grüße aus Leipsch,
    Pipo :) 

  4. Pingback: dario. domi. de. » WebGL 3D-Development

  5. Hi,

    der Code funktioniert wieder, wenn die Zeile 65 gegen dies hier ausgetauscht wird:

    var geometry = new THREE.CubeGeometry(200, 200, 200, 1, 1, 1);
    var cube = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));

    Gruß
    Pepino

    PS: Zeile 1-30 ist jetzt schon in Three.js integriert und muss auch nicht mehr explizit eingefügt werden. 

  6. Lang ist’s her, dass ich mal mit three.js arbeiten durfte. Würde mich echt gerne mal wieder mit einer neuen Version auseinander setzen, aber bis kam es nicht wieder dazu.
    Vielen Dank für die Updates :) 

Hinterlasse einen Kommentarsenäh

Pflichtfelder sind mit * markiert.