Auf PCs und Macs haben anspruchsvolle 3D-Grafiken schon vor einer ganzen Weile Einzug gehalten. In Form von Fotogalerien, Produktkonfiguratoren und -ansichten und natürlich in Form von Spielen. Auch der mobile Bereich ist technisch mittlerweile auf dem Stand, aufwendige 3D-Grafiken berechnen zu können.
Nun stellt sich bei der Planung einer entsprechenden Anwendung oder eines Spieles aber die Frage, auf welche Programmiersprachen und Frameworks gesetzt werden sollte. Insbesondere, wenn das Produkt dann auf mehreren Plattformen erscheinen soll.
Mit diesem Artikel möchte ich eine Einführung in das JavaScript-Framework ThreeJS geben und zeigen, wie einfach es sein kann, mit vertrauten Web-Werkzeugen und dem richtigen Framework durchaus auch anspruchsvollere 3D-Anwendungen zu schreiben. Am Beispiel von CocoonJS werde ich kurz erklären, wie diese dann nicht nur auf Desktop-Browsern, sondern auch auf mobilen Endgeräten genutzt und in die Stores gestellt werden können.
Warum JavaScript?
Weil es (so ziemlich als einziges) wirklich Cross-Plattform ist. Weil es schnell (genug) ist, weil es relativ einfach zu erlernen ist, weil man sehr sehr gut damit prototypen kann, und weil es eine große Fangemeinde besitzt. Außerdem bietet die Event-getriebene Architektur von JavaScript viele Vorteile für die Entwicklung von Spielen und anderen Anwendungen.
Warum ThreeJS?
ThreeJS ist eine Framework für WebGL-Anwendungen, das bemüht ist, seinen Benutzern die anstrengenden Details der 3D-Programmierung abzunehmen und trotzdem hochkomplexe Grafiken zu ermöglichen.
ThreeJS ist bereits seit April 2010 auf dem Markt und wurde seitdem stetig weiterentwickelt. Es erfreut sich größter Beliebtheit (fast 14.000 Github Favs) und kommt mit vielen Beispielen (siehe threejs.org).
Browser-Support
Bislang war es der größte wunde Punkt bei der Verwendung von WebGL. Microsoft hat lange einen Bogen um die Technik gemacht und die Unterstützung im Internet Explorer strikt verweigert. Da der Internet Explorer jedoch nach wie vor einen hohen Marktanteil hat, war der Cross-Plattform-Einsatz damit eigentlich schon fast wieder erschlagen. Auch auf den mobilen Browsern, wie dem iOS Safari und dem Android Browser, fehlt bislang jede Unterstützung.
Doch Zeiten ändern sich! Microsoft unterstützt ab der Version 11 des Internet Explorers WebGL, und Dank CocoonJS können WebGL-Anwendungen einfach als App direkt auf iOS und Android installiert werden – und sind nicht mehr auf zusätzliche Browser mit WebGL-Unterstützung angewiesen.
Alle unterstützten Browser im Detail findet man hier: caniuse.com/webgl
[smartblock id=6342]
Ein einfaches Beispiel
Bevor es losgeht: der Renderer
Bevor es losgehen kann, muss der ThreeJS Renderer initialisiert werden. Das geschieht wie folgt:
var canvas = document.createElement('canvas'); var renderer = new THREE.WebGLRenderer({canvas: canvas}); renderer.setSize(window.innerWidth, window.innerHeight); document.getElementById('render-container').appendChild(renderer);
Zunächst wird ein Canvas-Element angelegt, auf dem anschließend der ThreeJS Renderer erzeugt wird. Danach wird noch die gewünschte Breite und Höhe des Canvas (hier volle Fensterbreite und -höhe) gesetzt und das erzeugte Canvas-Element entsprechend im DOM eingefügt.
Neben dem WebGLRenderer unterstützt ThreeJS auch einen CanvasRenderer, welcher versucht in einem 2D-Canvas die 3D-Grafiken zu simulieren, bzw. zu berechnen. Natürlich ist das Ergebnis nicht vergleichbar mit dem WebGLRenderer, aber in Fällen wo kein 3D-Context zur Verfügung steht, ist dies unter Umständen ein akzeptables Fallback.
Ältere Versionen von ThreeJS unterstützen hier zudem noch ein SVG Fallback, das aber in aktuellen Versionen entfallen ist.
Dort wo alles geschieht: Die Szene
Sobald der Renderer erstellt wurde, kann mit ThreeJS gearbeitet werden. Hierfür steht ein komplexer Szenengraph zur Verfügung, über den sich bestimmen lässt, was letztendlich angezeigt werden soll.
So ein Szenengraph ist schnell erstellt:
var scene = new THREE.Scene();
Dem Szenengraphen werden nun nach und nach alle zu zeichnenden Objekte (inklusive deren Position, Orientierung und Materialien) hinzugefügt, ebenso wie ein Kamera-Objekt (aus dessen Sicht die Szene dann „gefilmt“ wird) und gegebenenfalls Lichtquellen für die Schattenberechnung, bzw. Ausleuchtung der Szene. Vereinfacht kann man sich das tatsächlich wie an einem Hollywood-Filmset vorstellen, und es ist erheblich intuitiver, Szenen in dieser Form zu arrangieren, als in reinem WebGL entsprechend zu programmieren.
Allen Objekten im Szenengraphen sind ein paar Dinge gemeinsam: Sie haben eine Position, eine Rotation und eine Skalierung. Diese Werte können am Objekt selbst verändert werden (direkt über die Attribute position
, rotation
und scale
), was sich dann sofort auf den Szenengraphen auswirkt. Ferner können Objekte verschachtelt und gruppiert werden.
Der Einsatz der Kamera
var camera = new THREE.PerspectiveCamera(75, renderer.context.canvas.width / renderer.context.canvas.height, 0.1, 1000);
Es werden zwei Arten von Kameras unterstützt, einmal die OrthoGraphicCamera und die hier verwendete PerspectiveCamera. Eine perspektivische Kamera funktioniert so, wie man das aus der Realität kennt: Objekte in der Ferne werden kleiner dargestellt.
Im Code-Ausschnitt sehen wir die Initialisierung einer typischen perspektivischen Ansicht. Der erste Parameter definiert das „field of view“ (fovy) und gibt an, wie groß der sichtbare Winkel sein soll, mit dem die Kamera die Welt sieht. Werte zwischen 50 und 75 sind so in etwa das, was man aus Spielen und Filmen gewohnt ist, bei höheren Werten erhält man eine Art „Panorama“, bzw. „Fischaugen“-Effekt. Der zweite Parameter bestimmt das Verhältnis von Breite zu Höhe und sollte meist auf das Seitenverhältnis des entsprechend verwendeten Canvas gesetzt werden. Die beiden letzten Werte beschreiben eine „near“ und „far“ Plane. Diese kann man sich als Ebene vorstellen, die senkrecht zur Kamera steht. Alles was sich vor der „near“-Ebene befindet wird von der Kamera ignoriert, ebenso alles was sich hinter der „far“-Ebene befindet.
Bei einer orthographischen Ansicht hingegen ist es eher so, wie man damals in der Schulmathematik die 3D-Objekte gezeichnet hat. Egal wie weit entfernt ein Objekt von der Kamera ist, es ist immer gleich groß. Orthografische Kameras sind zum Beispiel sehr beliebt, wenn man 2D-Inhalte anzeigen möchte, da hier die Tiefe in dem Sinne nur für die Sortierung der verschiedenen Grafik-Ebenen zuständig ist. Ein „field of view“ gibt es entsprechend für diese Ansicht auch nicht.
Anmerkung: Wer sich fragt, warum man 2D-Inhalte mit Hilfe von WebGL darstellen will, anstelle direkt den 2D-Canvas zu verwenden: Mit WebGL ist, zum Beispiel durch den Einsatz von Shadern, erheblich mehr möglich, als mit dem 2D-Canvas. Und auch diverse Effekte lassen sich (entsprechende Hardware-Unterstützung vorausgesetzt) erheblich performanter erzielen.
Es werde Licht!
In ThreeJS werden diverse gängigen Lichttypen unterstützt, als da wären:
- AmbientLight. Umgebungslicht, dass keine feste Position hat und alle Objekte gleichmäßig erhellt.
- DirectionalLight. Licht ohne Position, aber mit Einfallwinkel. Vergleichbar mit einer Lichtquelle in sehr großer Entfernung, zum Beispiel Sonnenlicht.
- AreaLight. Scheint in alle Richtungen aus einem Rechteck heraus. Vielleicht am ehesten vergleichbar mit einer großen rechteckigen Deckenlampe?
- HemisphereLight. Imitiert realistisches Sonnenlicht (inklusive Reflektion vom Boden).
- PointLight. Lichtpunkt, von dem Licht in alle Richtungen abstrahlt. Mit einer Glühbirne vergleichbar.
- SpotLight. Lichtkegel, ähnlich dem von einer Taschenlampe.
Jedes Licht hat eine Farbe und die meisten Lichtarten haben auch eine Intensität, welche die Leuchtstärke angibt. Im Folgenden werden Beispielhaft ein AmbientLight und ein DirectionalLight erstellt:
var light = new THREE.AmbientLight( 0x404040 ); scene.add( light ); var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.9 ); scene.add( directionalLight );
Damit man auch was sieht: Objekte / Meshes
Wir haben den Renderer, die Szene, die Kamera und ein paar Lichter, aber noch immer ist nichts zu sehen. Was fehlt sind die 3D-Objekte, die sogenannten Meshes.
Ein Mesh besteht im einfachsten Falle aus einer Geometrie und einem Material. Die Geometrie beschreibt die Form des Objektes, das Material bestimmt zum Beispiel, welche Farbe das Objekt hat, oder wie es auf Licht/Schatten, etc. reagiert.
var sphere = new THREE.Mesh(new THREE.SphereGeometry(10, 10, 10), new THREE.MeshNormalMaterial()); scene.add(sphere);
In dem Beispiel wird eine einfache Kugel erzeugt und der Szene hinzugefügt. ThreeJS bietet eine Reihe von einfachen Geometrien, die direkt erzeugt und genutzt werden können. Am besten klickt man sich einmal durch die Liste auf Github oder in der Doku (unter Extras/ Geometries). Neben den vordefinierten Geometrien kann man aber auch eigene Models laden, die zum Beispiel in 3D-Modellierungsprogrammen wie Blender, Wings3D, Maya oder 3D Studio Max erstellt wurden.
Mit Hilfe entsprechender Exporter oder Converter können die Formate (ctm, fbx, obj, blender, maya, 3d-studio) in ein ThreeJS eigenes JSON-Format konvertiert werden. Sobald die Daten dann als JSON vorliegen, können sie sehr einfach in ThreeJS geladen werden:
var loader = new THREE.JSONLoader(); loader.load( "3dmodel.js", function( geo, mat) { var mesh = new THREE.Mesh(geo, mat); scene.add(mesh); }
Anmerkung: 3D-Daten als .json? Das ist doch völlig ineffizient und viel zu groß, mag man zunächst annehmen. Man muss jedoch bedenken, dass die Dateien normalerweise komprimiert vom Webserver ausgeliefert werden, und jeder Browser diese in Windeseile und völlig automatisch wieder dekomprimiert. Sich selbst eine Komprimierung in JavaScript zu entwickeln und sich mit einem proprietären binären Format rumzuärgern, macht absolut keinen Sinn!
Noch ein Wort zu den Materialien
Wie bereits erwähnt bestehen die Objekte aus verschiedenen Materialien, die direkt beeinflussen, wie das Objekt dann im Endeffekt aussieht. ThreeJS kommt mit einer Vielzahl vorgefertigter Materialien, die im Normalfall auch ausreichen müssten. Siehe Doku oder Github.
Am spannendsten ist hier wohl das sogenannte ShaderMaterial. Es ermöglicht das Schreiben eigener Shader. Das sind, ganz vereinfacht dargestellt, Programme, die direkt auf dem Grafikchip ausgeführt werden und letztendlich dafür verantwortlich sind, was wie angezeigt wird. Hierbei sind den Möglichkeiten kaum Grenzen gesetzt, im Rahmen dieses Artikels möchte ich das jedoch nicht näher betrachten.
Vollständiges Beispiel:
Nachdem die Szene wie gewünscht erzeugt wurde, ist nur noch dem Renderer mitzuteilen, dass er die Szene berechnen soll:
renderer.render(scene, camera);
Hier das vollständige Beispiel als Fiddle. Wenn Ihr Browser WebGL unterstützt, müsste eine bunt gefärbte, sich drehende Kugel zu sehen sein (Link zum Fiddle). (Anmerkung: Auf der HTTPS-Variante dieses Artikel funktioniert das Fiddle leider manchmal nicht, hier einfach dem Link folgen, oder auf die non-https Variante wechseln).
Eine bunte Kugel wirkt jetzt natürlich etwas unspektakulär, aber für die paar Zeilen Code ist das schon ziemlich beachtlich. Der entsprechend benötigte Code für den Fall, dass man ThreeJS nicht verwendet, ist erheblich länger und komplexer. Und das wirklich spannende kommt ja jetzt erst: Es können nach belieben weitere Objekte zur Szene hinzugefügt werden, Kamera und Objekte können bewegt, und Texturen über die Objekte gelegt werden, usw. Und Dank ThreeJS sehr sehr einfach.
Komplexere Beispiele
Es lohnt sich sehr, durch andere Projekte und Beispiele zu stöbern, um einen Eindruck davon zu bekommen, was mit ThreeJS tatsächlich möglich ist:
- http://threejs.org/examples Sehr viele Beispiele zu einzelnen Techniken.
- http://stemkoski.github.io/Three.js/ weitereBeispiele zu einzelnen Techniken
- http://threejs.org/ komplexere Projekte
Mobile? CocoonJS!
Da weder der Android Browser noch der iOS Safari WebGL bislang unterstützen, war auf Mobile immer ein Umweg von Nöten. Entweder als App verpackt (zum Beispiel via PhoneGap oder dem Intel XDK) oder über nachinstallierte Browser, wie Firefox oder Chrome.
Für 2D-Canvas war die Geschwindigkeit hier meist noch ausreichend, die 3D-Performance verbessert sich zwar laufend, kann aber bestenfalls noch als mangelhaft bezeichnet werden.
Die Jungs von ludei.com haben hierfür eine spannende Lösung entwickelt: CocoonJS. Es handelt sich hierbei um ein Hybrid-App-Framework, welches HTML5-Inhalte als App verpackt und auf die mobilen Geräte bringt (neben Apples App Store, dem Google Play Store und Amazon Android Store werden auch noch viele weitere Plattformen unterstützt).
Der entscheidende Vorteil gegenüber der Konkurrenz wie PhoneGap: CocoonJS ist speziell auf 3D-Anwendungen und Spiele ausgelegt und hat hier diverse Optimierungen vorgenommen. Unter anderem wird WebGL (bzw. ein Subset davon) mittlerweile nativ unterstützt und ThreeJS läuft einwandfrei.
Wie funktioniert CocoonJS? Man erstellt sich einen (kostenlosen) Account, richtet ein neues Projekt ein und lädt entsprechende Splashscreen-Grafiken für den Ladebildschirm hoch. Anschließend muss man nur noch ein ZIP mit dem Programmcode ergänzen und auswählen, für welche Stores man die App verpackt haben möchte. Fertig. Wenig später erhält man eine E-Mail mit dem Link zu den fertig verpackten Apps.
Damit man gerade in der Entwicklungsphase nicht ständig neue Apps bauen lassen muss, bietet Cocoon auch einen Launcher (Link zum Launcher im Google Play Store). Hier kann man die Programm-ZIPs auch direkt von der SD-Karte starten, oder gar einen URL angeben, von welchem die Ressourcen geladen werden sollen. Der Launcher bietet neben einer eingebauten FPS-Anzeige (Frames per second) zum Messen der Performance auch eine umfangreiche Konsole und die Möglichkeit, Profiling-Daten auf der SD-Karte zur späteren Auswertung zu speichern.
Fazit
Wer bereits JavaScript beherrscht, wird mit Hilfe von ThreeJS relativ flott brauchbare Ergebnisse erzielen können, die auf einer Vielzahl von Plattformen ohne größere Anpassungen lauffähig sind. Und all das, ohne sich im Detail mit Shader Language oder den tiefen von OpenGL ES herumschlagen zu müssen.
Die Performance von WebGL hinkt natürlich kommerziellen 3D-/Game-Engines mit nativem Programmcode je Betriebssystem (wie Unity3D) noch deutlich hinterher, lässt sich aber dank Tools wie CocoonJS dennoch akzeptabel umsetzen.
Es ist zu erwarten, dass die Unterstützung für WebGL nach wie vor weiter ausgebaut werden wird, und die Performance langfristig immer weiter an die von nativen 3D-Anwendungen heranreichen wird.
Schreibe einen Kommentar