Tanz auf hundert Step-Sequenzern

Aus ihrem schicken 3-D-Intro für die Frankfurter MTV European Music Awards 2012 entwickelte die Berliner Motion-Design-Agentur SEHSUCHT ein Browser-Musik-Game, in dem der User im Takt zur Musik die Puppen tanzen lässt. Hier erzählen sie, wie sie das interaktive Spiel im WebGL-Framework ThreeJS umsetzten



Um ins nächste Level zu kommen, bewegt man das Zoetrop per Maus- geste im Takt. Dazu ziehen Sie die Maus immer dann nach links, wenn der gelbe Balken in der unteren Anzeige­leiste über den rosa Strich fährt. Das Zoetrop gibt einen Warnsound ab, wenn die ­Scroll-Bewegung nicht erfolgt. Ein Zähler zeigt den Punk­testand an. Für die abgespeckte Tutorial­variante genügt es, die Ballsequenz anzu­schubsen, sodass die Bälle im Kreis bouncen

Drehen die Spieler das Karussell im Takt zur Musik, tanzt der animierte Protagonist Seth auf dem Synthesizer. Wer den Takt hält, kommt in die zweite Runde. So beginnt unser Spiel »The Zoetrope«. Die Spiel­ele­mente stammen von unserem Show-Package der MTV European Music Awards 2012. Anlässlich dieses Events produ­zierte SEHSUCHT Berlin gemeinsam mit dem VIMN MTV World Design Studio Mailand eine Reihe von Trailern, die weltweit auf MTV und während der Award-Show liefen.

Im Mittelpunkt der Animation steht ein schnell drehen­des Karussell, das mithilfe eines Lichtblitzgeräts eine Reihe statischer Elemente zum Leben erweckt und so die Illusion von Bewegung erzeugt. Auf fünf Stockwerken lassen wir unsere Charaktere Seth und Roxy in der Animation tanzen, crowdsurfen und Gitarre spielen. Damit nicht genug: Wir sahen die Chance, in neue Technologien einzutauchen und unser Zoetrop sprichwörtlich auf die nächste Ebene zu brin­gen. Stichwort: Usability! Ziel war es, ein interaktives, mu­sikalisches Zoetrop zu entwickeln, das es dem User ermög­licht, das Ganze per Mouse oder Keyboard im Takt zu drehen und damit Spielpunkte zu sammeln.

Das Game setzten wir mit ThreeJS, einem mittlerweile gut ausgebauten WebGL-Framework um. Mit WebGL kann man 3-D-Daten direkt im Browser darstellen. Außer einem modernen Browser benötigt der User kein weiteres Plug-in. Die Open-SourceThreeJS-JavaScript-Library können Sie kostenlos unter www.threejs.org herunterladen. Da wir unmöglich das gesamte Spiel in diesem Tutorial abarbeiten können, haben wir die Elemente stark reduziert, um einzelne Schritte besser erklären zu können. Und los!

1 3-D-Szene öffnen
Die 3-D-Modelle können Sie für die Nutzung in ThreeJS prinzipiell mit jeder Software erstellen, solange diese den Export in ein unterstütztes Austauschformat (in diesem Fall Wavefront OBJ) unterstützt. Behalten Sie beim Modellieren aber die Unterteilung Ihrer Objekte im Auge, denn WebGL ist nicht so performant wie sein großer Bruder OpenGL.

Mit ThreeJS sind Shading und Ausleuchtung direkt im JavaScript-Code möglich. Wer aber wie wir physikalisch korrekte Schatten und indirektes Licht setzen möchte, sollte die Objekte in einer 3-D-Software inszenieren und daraus Texturen generieren.

Für dieses Tutorial liegt uns eine Szene in Cinema 4D bereits vor. Öffnen Sie die Datei »01 ausleuchten.c4d« mit Cinema 4D, sie enthält eine Ballsequenz. Dreht sich diese im Spiel schnell genug, sieht man einen hüpfenden Ball. Sie können die Datei kurz im Editor rendern, um zu sehen, wie die Webseite später aussehen wird.

2 Texturen backen und bearbeiten
Nun backen wir aus dieser Ausleuchtung Texturen. Damit Sie diese auf Ihrem Modell richtig backen können, benötigen Sie eine gute UV-Map, also eine Repräsentation Ihres 3-D-Models im 2-D-Raum. Stellen Sie sich vor, man würde dafür dem 3-D-Model die Schale abzie­hen und diese flach auf ein Blatt Papier legen. In unserem Beispiel haben die Modelle bereits gute UV-Maps. Sie können also gleich mit dem Backen der Texturen beginnen.

Wählen Sie in der Objekthierarchie nun den Boden (»floor«) und die Bälle (»bouncing ball«) aus. Dann klicken Sie mit der rechten Maustaste darauf und wählen »Cinema 4D tags«/»Textur backen«. Damit haben wir ein neues Tag auf den Objekten erstellt .

Klicken Sie darauf, und wech­seln Sie im Attribute Manager auf den Tab »Tag«. Dort bestimmen Sie den Namen der zu erstellenden Texturen. Stellen Sie das Format auf JPEG sowie die Breite und Höhe auf 2000 Pixel. Im Tag für den springenden Ball aktivieren Sie unter »Optionen« »Farbe« und unter dem nun ent­stan­denen Klappmenü »Licht«, »Schatten«, »Leuchten« und »Diffusion«.

Für den Boden aktivieren Sie im Objekt­ma­nager lediglich »Schatten« und »Ambient Occlusion« .


Zuletzt klicken Sie auf »Bearbeiten Projektvoreinstellun­gen« und stellen das Eingangsfarbprofil auf »linear«.

Würden Sie nun rendern, sähen die Farben blasser aus – in den gebackenen Texturen sind diese aber final richtig. Mar­kieren Sie nun beide »Texturen backen«-Tags und klicken Sie auf »backen«. Nach dem Backen haben Sie drei JPEG-Bilder, die Sie in Photoshop öffnen können. Dort multiplizieren Sie die Texturen des Bodens auf eine Farbfläche mit dem Farbwert #3f95a8. Reduzieren Sie die Deckkraft der Ambient-Occlusion-Ebene auf 60 Prozent, die der Schat­ten­ebene auf 30 Prozent.

Als Nächstes müssen wir die Textur der Bälle an den ­Rändern ­erweitern, um keine weißen Nähte zu erhalten. Dafür müssen Sie sie freistellen . Kopieren Sie dann die Ebene und zeichnen Sie die untere der beiden mit dem Gaußschen Weichzeichner mit einem Zwei-Pixel-Radius weich.

Wenn Sie diese Ebene nun mehrfach duplizieren, erscheinen die wei­chen Ränder immer dichter . Skalieren Sie die Größe der Da­teien auf 1000 x 1000 Pixel und speichern Sie sie als JPEG .

Diese Texturen können Sie nun in Cinema 4D testen. Öffnen Sie dazu im mitgelieferten Material die Datei »tex­tu­ren prüfen.c4d«, und laden Sie Ihre Texturen in den Leuch­tenkanal der Materialien. Wenn alles richtig ist, soll­te Ihre Szene so aussehen wie vor dem Backen, allerdings ist die Beleuchtung jetzt in den Texturen gespeichert.

3 OBJ-Daten exportieren und konvertieren
Löschen Sie nun alle 3-D-Objekt-Tags und kopieren Sie diese einzeln in die neuen Cinema-4D-Dateien. Klicken Sie auf »Datei › Exportieren › Wavefront obj«, und exportieren Sie beide Dateien bei einem Skalierungsfaktor von 1. Falls Sie eine komplexere Szene exportieren wollen, empfiehlt es sich, ein Plug-in wie Riptide Pro zu verwenden. Vorteil: Riptide Pro benötigt nur einen Exportvorgang, um für alle Objek­te einer Szene einzelne OBJ-Datei zu erzeugen. Darüber hin­aus empfiehlt sich das Plug-in, wenn Sie die 3-D-Objekte direkt in ThreeJS ausleuchten wollen, denn es exportiert im Ge­gensatz zu Cinema 4D auch Phong-Normalen, die für eine glatte Oberflächendarstellung niedrig aufgelös­ter Mod­elle sorgen. Da wir die Beleuchtung gebacken haben, brauchen wir keine Phong-Normalen. Diese Oberflä­chen­glättung ist dann bereits in den Texturen vorhanden.

Die OBJ-Dateien könnten wir nun direkt in ThreeJS einbinden. Um aber die 3-D-Daten so klein wie möglich zu halten, sollten Sie diese vorher in das binäre Format von ThreeJS umwandeln, denn es halbiert die Dateigröße. Dazu benötigen wir ein Python-Script, das im ThreeJS-­Paket enthalten ist. Auf Mac OS X ist Python vorinstalliert. Sollten Sie ein anderes Betriebssystem nutzen, müssen Sie vorher Python her­unter­la­den und installieren.

Verschieben Sie jetzt die »convert_obj_three.py«-Da­tei in denselben Ordner, in dem auch ihre OBJ-Daten liegen, und öffnen Sie diese mit dem Python Launcher. Geben Sie nacheinander folgende Befehle ein und bestätigen Sie sie per Eingabetaste:

python convert_obj_three.py -i floor.obj -o floor.js -t binary
python convert_obj_three.py -i bouncing_ball.obj -o bouncing_ball.js -t binary

Mit dem ersten Befehl wandeln Sie eine »floor.obj«-Datei in eine »floor.js«-Datei im binären Format um. Der zweite Befehl macht das Gleiche für den springenden Ball. Wenn Ihre Dateien anders heißen, müssen Sie die Befehle natürlich entsprechend variieren. Python sollte jetzt für jede OBJ-Datei eine JS-Datei und eine BIN-Datei erstellt haben.

4 HTML5-Grundgerüst vorbereiten
Da nun alle 3-D-Daten und -Texturen auf­be­reitet sind, wenden wir uns der Webseite zu und fügen die Elemente in ThreeJS zusammen. Eine HTML5-Datei ist bereits vorbereitet. Neben dem HTML5-Grundgerüst enthält sie die grafische Oberfläche unse­res Test-Zoetrops und lädt ThreeJS sowie einige ThreeJS-Hilfs­klas­sen. Die Oberfläche besteht aus einfachen HTML5- und CSS3-Befehlen, Sie können also auch Ihre eigene Ober­fläche gestalten. Wichtig ist lediglich, dass jedes Ober­flächenelement eine eigene CSS-ID bekommt, mit deren Hilfe wir sie später finden und ein- beziehungsweise ausblenden können. Als Schaltflächen nutzen wir HTML-­Hyperlinks, die wir ebenfalls mit einer CSS-ID ausstatten (zum Beispiel <a id=”meineID”> Schalter </a>).

5 WebGL-Kompatibilität prüfen und initialisieren
Nun können wir die ThreeJS-Detector-Klasse benutzen, um zu prüfen, ob Browser und Grafikkarte des Nutzers WebGL unterstützen. Ist kein WebGL verfügbar, blenden wir das Oberflächenelement mit der entsprechenden Nachricht ein, ansonsten blenden wir die erste Spielanweisung ein und bereiten unsere ThreeJS-Szene vor. Dazu registrieren wir zunächst die Variablen für die Szene und das Spiel und erstellen einen neuen DIV-Container für unseren WebGL-Inhalt.

Außerdem erstellen wir eine neue Szene sowie eine Kamera mit einer Brennweite von 50 und dem Aspekt-Ratio des Browsers. Die Kamera positionieren wir im Raum und erstellen ein neues Kontrollelement, mit dem wir sie durch den 3-D-Raum bewegen können. Dabei geben wir mittels controls.target die Blickrichtung an und sorgen mit einem Listener dafür, dass neu gerendert wird, wenn die Kamera bewegt wird.

if ( !Detector.webgl ) {
document.getElementById( “noWebGl” ).style.display = ‘inline’;
}
else {
document.getElementById( “playInstruction” ).style.display = ‘inline’;
var container, stats, camera, scene, renderer;
var controls, group, lastFrameTime = Date.now();
var mouseDown = false, lastMousePosition, mouseSpeed = 0, rotationIncrease = 0;
container = document.createElement( ‘div’ );
container.id = “webGlContainer”;
document.body.appendChild( container );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 20000 );
camera.position = new THREE.Vector3(700,600,-2000) ;
controls = new THREE.TrackballControls2( camera );
controls.addEventListener( ‘change’, render );
controls.target = new THREE.Vector3(0,0,0);

6 Modelle und Texturen laden
Um später nicht einzelne 3-D-Objekte drehen zu müs­sen, erstellen Sie eine Gruppe, die Sie der Szene hinzufügen und die als Überobjekt für alle 3-D-Modelle fungiert. Dann laden wir unsere 3-D-Modelle mittels getModelJS und unsere Texturen mittels getTexture. ­Diese beiden Routinen werden wir unter Punkt 9 definieren.

group = new THREE.Object3D(); // alle 3 D Objekte werden Unterobjekte dieser Gruppe
scene.add( group );
getModelJS( ‘objekte/floor.js’ , getTexture( ‘texturen/floor.jpg’ ) );
getModelJS( ‘objekte/bouncing_ball.js’ , getTexture( texturen/bouncing_ball.jpg’ ) );

7 Renderer starten
Uns fehlt nun nur noch ein Renderer, der unsere Szene darstellt. Neben den Dimensionen der Darstellungsfläche aktivieren wir hier das Antialiasing. Da­bei sollten Sie wissen, dass vie­le ­Grafikkarten diese Kantenglättung nicht bei WebGL-­In­halten unterstützen. Wich­tig ist auch, die richtigen Gamma-Einstellungen zu wählen, ansonsten werden unsere Texturen falsch dargestellt. Mit der Funktion animate, die wir ebenfalls später definieren werden, starten wir ­unseren Renderer.

renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight);
renderer.gammaInput = false;
renderer.gammaOutput = false;
container.appendChild( renderer.domElement );
animate();

8 Listener erstellen
Der letzte Initialisierungsschritt ist das Registrieren von Listenern. Das machen wir zunächst für unsere Schaltflächen. Bei Klick auf die Schaltfläche der ers­ten Anweisung soll nur die erste Anweisung aus- und die nächste Anweisung eingeblendet werden. Beim Klick auf die Schaltfläche der zweiten Anweisung registrieren wir die Listener für die Maus, mit denen wir das Zoetrop steuern.

document.getElementById(“playInstruction-action”).onmousedown = function() {
document.getElementById( “playInstruction” ).style.display = ‘none’;
document.getElementById( “playInstruction2” ).style.display = ‘inline’;
};
document.getElementById(“playInstruction2-action”).onmousedown = function() {
document.getElementById( “playInstruction2” ).style.display = ‘none’;
container.onmousedown = MouseDownHandler;
container.onmouseup = MouseUpHandler;
container.onmouseout = MouseUpHandler;
container.onmousemove = MouseMoveHandler;
};
}

9 Laderoutinen definieren
Die Funktionen getTexture und getModelJS haben wir bereits benutzt, aber noch nicht definiert. GetTexture lädt eine Textur mithilfe eines neuen Image Loaders und gibt diese, sobald sie fertig geladen ist, zurück.

function getTexture( url ) {
var texture = new THREE.Texture();
var loader = new THREE.ImageLoader();
loader.addEventListener( ‘load’, function ( event ) {
texture.image = event.content;
texture.needsUpdate = true;
} );
loader.load( url );
return texture;
}

GetModelJS lädt eine 3-D-Datei mittels eines neuen Binary Loaders. Ist die Datei geladen, kann der Befehl new THREE.MeshFaceMaterial aus den geladenen Daten ein Mesh er­stellen, dem wir anschließend ein Material mit unserer Textur zuweisen. Der Parameter color steuert hier lediglich die Helligkeit der Textur. Zuletzt wird das Modell zu unserer Gruppe hinzugefügt.

function getModelJS( url, texture ){
var loader = new THREE.BinaryLoader(),
callback = function( geometry ) { createMesh( geometry ) };
loader.load( url , callback);
function createMesh( geometry ) {
zmesh = new THREE.Mesh( geometry, new THREE.MeshFace
Material() );
zmesh.material = new THREE.MeshBasicMaterial( { color: 0xffffff, map: texture } );
group.add( zmesh );
}
}

10 Render-Rate einstellen
Die ebenfalls bereits zum Einsatz gekommene animate-Rou­tine nutzt die Funktion request­AnimationFrame, um sich selbst in einer kontrol­lierten Endlosschleife aufzurufen und dann das Bild neu zu zeichnen. RequestAnimationFrame wartet dabei so lange mit dem Aufruf, bis das System für einen neuen Zeichenvorgang bereit ist. Damit der Renderer unser ­Zoetrop korrekt darstellt, drosseln wir die Darstellungsrate auf zwölf Bilder pro Sekunde, denn bei einer ­anderen Frame-Rate würde die Animation zu schnell wirken. Dazu merken wir uns den Zeitpunkt des letzten Render-Vorgangs in einer Variablen und rendern erst wieder, wenn genügend Zeit verstrichen ist. Dann aktuali­sieren wir auch die Rotation des Zoetrops und updaten die Kameraposition.

function animate() {
requestAnimationFrame( animate );
if (Date.now() – lastFrameTime >= 1000/12) {
lastFrameTime = Date.now();
rotateZoetrope(); // Zeotrope Rotieren
render(); // Rendern
controls.update();
}
}
function render(){
renderer.render( scene, camera );
}

11 Mausevents bearbeiten
Um das Zoetrop zu steuern, greifen wir als Ers­tes Daten der Maus ab. Listener für Mouseklicks und die Mausbewegung haben wir bereits erstellt. Mit den Funktionen MouseDownHandler und MouseUpHandler speichern wir, ob die Maustaste gedrückt ist.

function MouseDownHandler(e){
mouseDown = true;
mouseSpeed = 0;
}
function MouseUpHandler(e){
mouseDown = false;
}

Falls die Maustaste gerade gedrückt ist, kann MouseMoveHandler die Geschwindigkeit der Maus anhand der aktuel­len und letzten Position errechnen. Die Funktion dividiert jetzt die Geschwindigkeit noch durch einen empirisch ­ermittelten Faktor von 50, damit sie eine angemessene Größe für die Rotation des Zoetrops erhält.

function MouseMoveHandler(e){
if (mouseDown && lastMousePosition != undefined && controls.getState()==-1) {
mouseSpeed = (e.clientX – lastMousePosition)/50;
}
lastMousePosition = e.clientX;
}

12 Zoetrop drehen und Spiel testen
Nun übertragen wir das Maustempo auf die Zoetrop-Rotation. Dafür limitieren wir den Wert von mouseSpeed auf ein Sechstel von Pi, also ein Zwölftel Umdrehung pro Frame. So dreht sich das Zoetrop in einer Sekunde maximal um 360 Grad, und zwar in einem realistisch anmutenden Tempo. Dann nutzen wir die Variable rotationIncrease, die sich dem Wert von mouseSpeed exponentiell annähert, um eine gewis­se Trägheit zu erzeugen. Wir addieren den Wert von rotationIncrease auf die Zoetrop-Rotation und verringern mouseSpeed linear mit einem empirisch ermittelten Wert, sodass das Zoetrop ausrollen würde, wenn es nicht angeschubst wird.

function rotateZoetrope() {
if (mouseSpeed > Math.PI/6) mouseSpeed = Math.PI/6;
else if (mouseSpeed < -Math.PI/6) mouseSpeed = -Math.PI/6;
rotationIncrease += (mouseSpeed – rotationIncrease)*.2;
group.rotation.y += rotationIncrease;
if (mouseSpeed > 0) mouseSpeed -= 1/300;
if (mouseSpeed < 0) mouseSpeed += 1/300;
}

Das Zoetrop ist fertig , viel Spaß beim Tes­ten in Ihrem Browser! 


Tools Cinema 4D (notfalls andere 3-D-Software), 
Photoshop, ThreeJS, JavaScript-Editor

Vorkenntnisse HTML, CSS, Cinema-4-D- und JavaScript-Basics

Ergebnis Bouncing Ball Zoetrope

So viel Zeit muss sein 4 Stunden


Christina Geller, Producerin bei SEHSUCHT Berlin und David Weidemann, Designer und Animator ebenda.


Schlagworte:




Kommentieren

Einfach mit dem PAGE Account anmelden oder Formular ausfüllen

Name *

Email *

*Pflichtfeld

Ihr Kommentar *

 
 

Das könnte Sie auch interessieren