Tanztonite in WebVR

Ich war begeistert, als das Google Data Arts-Team Moniker und mich fragte, ob wir gemeinsam die Möglichkeiten von WebVR erkunden wollten. Ich habe die Arbeit des Teams im Laufe der Jahre beobachtet und die Projekte haben mich immer angesprochen. Die Zusammenarbeit führte zu Dance Tonite, einem sich ständig ändernden VR-Tanz-Erlebnis mit LCD Soundsystem und ihren Fans. Und so sind wir vorgegangen.

Konzept

Wir haben damit begonnen, eine Reihe von Prototypen mit WebVR zu entwickeln, einem offenen Standard, der es ermöglicht, VR zu nutzen, indem man eine Website über den Browser besucht. Unser Ziel ist es, allen Nutzern den Einstieg in die virtuelle Realität zu erleichtern – unabhängig von ihrem Gerät.

Wir haben uns das zu Herzen genommen. Unsere Lösung sollte mit allen Arten von VR funktionieren, von VR-Headsets, die mit Smartphones funktionieren, wie Google Daydream View, Cardboard und Samsung Gear VR, bis hin zu Raum-Scale-Systemen wie HTC VIVE und Oculus Rift, die Ihre physischen Bewegungen in der virtuellen Umgebung widerspiegeln. Am wichtigsten war uns, dass wir etwas entwickeln, das auch für alle funktioniert, die kein VR-Gerät haben.

1. DIY-Motion-Capture

Da wir die Nutzer kreativ einbinden wollten, haben wir uns die Möglichkeiten für Beteiligung und Selbstdarstellung mithilfe von VR angesehen. Wir waren beeindruckt, wie sehr man sich in VR bewegen und umsehen konnte und wie zuverlässig die Funktion ist. Das gab uns eine Idee. Anstatt Nutzer etwas ansehen oder erstellen zu lassen, könnten Sie ihre Bewegungen aufzeichnen.

Jemand, der sich bei Dance Tonite selbst aufnimmt Auf dem Bildschirm dahinter ist zu sehen, was die Person in ihrem Headset sieht.

Wir haben einen Prototyp entwickelt, bei dem wir die Positionen unserer VR-Brille und ‑Controller beim Tanzen aufgezeichnet haben. Wir ersetzten die aufgezeichneten Positionen durch abstrakte Formen und waren von den Ergebnissen begeistert. Die Ergebnisse waren so menschlich und hatten so viel Persönlichkeit! Wir haben schnell gemerkt, dass wir mit WebVR zu Hause kostengünstig Motion-Capture-Aufnahmen machen können.

Mit WebVR hat der Entwickler über das Objekt VRPose Zugriff auf die Kopfposition und -ausrichtung des Nutzers. Dieser Wert wird von der VR-Hardware für jeden Frame aktualisiert, sodass der Code neue Frames aus der richtigen Perspektive rendern kann. Durch die GamePad API mit WebVR können wir auch über das Objekt GamepadPose auf die Position/Ausrichtung der Controller des Nutzers zugreifen. Wir speichern einfach alle diese Position- und Orientierungswerte in jedem Frame und erstellen so eine „Aufzeichnung“ der Bewegungen des Nutzers.

2. Minimalismus und Kostüme

Mit den heutigen VR-Geräten im Raummaßstab können wir drei Punkte des Körpers der Nutzenden erfassen: den Kopf und zwei Hände. Bei Dance Tonite wollten wir den Fokus bei der Bewegung dieser drei Punkte im Weltraum auf die Menschlichkeit fokussieren. Um dies zu erreichen, haben wir die Ästhetik so minimal wie möglich gehalten, um uns auf die Bewegung zu konzentrieren. Uns gefiel die Idee, das Gehirn der Menschen zu nutzen.

Dieses Video, das die Arbeit des schwedischen Psychologen Gunnar Johansson zeigt, war eines der Beispiele, auf die wir gesprochen haben, wenn wir darüber nachdenken, Dinge so weit wie möglich zu entfernen. Es zeigt, wie schwebende weiße Punkte in Bewegung sofort als Objekte erkannt werden.

Visuell haben uns die farbigen Räume und geometrischen Kostüme in dieser Aufnahme der 1970er-Jahre-Neuinszenierung von Margarete Hastings von Oskar Schlemmers Triadic Ballet inspiriert.

Während Schlemmer abstrakte geometrische Kostüme auswählte, um die Bewegungen seiner Tänzer auf die von Puppen und Marionetten zu beschränken, hatten wir für „Dance Tonite“ das gegenteilige Ziel.

Letztendlich richteten wir unsere Auswahl an Formen darauf aus, wie viele Informationen sie durch die Rotation vermitteln. Ein Kreis sieht unabhängig von seiner Drehung gleich aus, ein Kegel hingegen zeigt in die Richtung, in die er schaut, und sieht von vorne anders aus als von hinten.

3. Loop-Pedal für Bewegung

Wir wollten große Gruppen von aufgenommenen Personen zeigen, die miteinander tanzen und sich bewegen. Eine Live-Umsetzung wäre nicht möglich, da es noch nicht genügend VR-Geräte gibt. Wir wollten aber trotzdem Gruppen von Menschen, die durch Bewegung aufeinander reagieren. Uns fiel die rekursive Performance von Norman McLaren in seinem Video „Canon“ aus dem Jahr 1964 ein.

McClarens Performance besteht aus einer Reihe von hoch choreografierten Bewegungen, die nach jeder Schleife miteinander interagieren. Ähnlich wie bei einem Loop-Pedal in der Musik, bei dem Musiker mit sich selbst tanzen, indem sie verschiedene Live-Musik übereinanderlegen, wollten wir eine Umgebung schaffen, in der Nutzer losere Versionen von Auftritten frei improvisieren können.

4. Zimmer mit Verbindungstür

Zimmer mit Verbindungstür

Wie viele andere Musikstücke sind auch die Titel von LCD Soundsystem mit genau getimten Takten aufgebaut. Der Titel Tonite, der in unserem Projekt vorgestellt wird, hat Messungen, die genau acht Sekunden lang sind. Wir wollten, dass die Nutzer eine Performance für jeden 8-Sekunden-Loop im Titel machen. Auch wenn sich der Rhythmus dieser Maßnahmen nicht ändert, aber ihre musikalischen Inhalte. Im Verlauf des Songs gibt es Momente mit verschiedenen Instrumenten und Gesang, auf die die Darsteller unterschiedlich reagieren können. Jedes dieser Messwerte wird als Raum ausgedrückt, in dem Menschen eine passende Leistung aufstellen können.

Leistungsoptimierungen: keine Frame-Ausfälle

Es ist nicht einfach, ein plattformübergreifendes VR-Erlebnis zu schaffen, das auf einer einzigen Codebasis mit optimaler Leistung für jedes Gerät oder jede Plattform ausgeführt wird.

Eine der übelsten Nebenwirkungen von VR ist Übelkeit, die durch eine Framerate verursacht wird, die nicht mit Ihren Bewegungen Schritt hält. Wenn Sie den Kopf drehen, die Bilder, die Sie sehen, aber nicht mit der Bewegung übereinstimmen, die Ihr Innenohr wahrnimmt, führt das sofort zu Übelkeit. Aus diesem Grund mussten wir eine große Verzögerung bei der Framerate vermeiden. Hier sind einige Optimierungen, die wir umgesetzt haben.

1. Instanziierte Puffergeometrie

Da in unserem gesamten Projekt nur eine Handvoll 3D-Objekte verwendet wird, konnten wir mithilfe von instanziierter Shader-Geometrie eine enorme Leistungssteigerung erzielen. Im Grunde können Sie Ihr Objekt einmal auf die GPU hochladen und mit einem einzigen Draw-Aufruf beliebig viele „Instanzen“ dieses Objekts zeichnen. In Dance Tonite gibt es nur drei verschiedene Objekte (einen Kegel, einen Zylinder und einen Raum mit einem Loch), aber potenziell Hunderte von Kopien dieser Objekte. Die Instanzzwischenspeicher-Geometrie ist Teil von ThreeJS. Wir haben jedoch die experimentelle und noch laufende Abspaltung von Dusan Bosnjak verwendet, die THREE.InstanceMesh implementiert. Dadurch wird die Arbeit mit Instanced Buffer Geometry wesentlich einfacher.

2. Garbage Collector vermeiden

Wie bei vielen anderen Scriptsprachen wird in JavaScript automatisch Speicher freigegeben, indem ermittelt wird, welche zugewiesenen Objekte nicht mehr verwendet werden. Dieser Vorgang wird als automatische Speicherbereinigung bezeichnet.

Entwickler haben keinen Einfluss darauf, wann dies geschieht. Die automatische Speicherbereinigung kann jederzeit vor unseren Türen auftauchen und mit dem Leeren des Mülls beginnen, was dazu führt, dass Frames verloren gehen, während sie sich die Zeit vertreiben.

Die Lösung besteht darin, so wenig Müll wie möglich zu produzieren, indem wir unsere Gegenstände recyceln. Anstatt für jede Berechnung ein neues Vektorobjekt zu erstellen, haben wir die Scratch-Objekte zur Wiederverwendung gekennzeichnet. Da wir sie behalten, indem wir die Verweise darauf außerhalb unseres Zuständigkeitsbereichs verschieben, wurden sie nicht zur Entfernung markiert.

Hier ist beispielsweise der Code, mit dem die Positionsmatrix des Kopfes und der Hände des Nutzers in das Array mit den Position/Rotationswerten umgewandelt wird, das wir in jedem Frame speichern. Durch die Wiederverwendung von SERIALIZE_POSITION, SERIALIZE_ROTATION und SERIALIZE_SCALE vermeiden wir die Speicherzuweisung und die Garbage Collection, die stattfinden würden, wenn jedes Mal, wenn die Funktion aufgerufen wird, neue Objekte erstellt würden.

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. Bewegung und progressive Wiedergabe werden serialisiert

Um die Bewegungen von Nutzern im VR-Modus zu erfassen, mussten wir die Position und Drehung der Headsets und der Controller serialisieren und diese Daten auf unsere Server hochladen. Wir haben damit begonnen, die vollständigen Transformationsmatrizen für jeden Frame zu erfassen. Die Leistung war gut, aber bei 16 Zahlen multipliziert mit jeweils 3 Positionen bei 90 Frames pro Sekunde führte dies zu sehr großen Dateien und damit zu langen Wartezeiten beim Hoch- und Herunterladen der Daten. Indem wir nur die Positions- und Drehungsdaten aus den Transformationsmatrizen extrahierten, konnten wir diese Werte von 16 auf 7 senken.

Da Nutzer im Web oft auf einen Link klicken, ohne genau zu wissen, was sie erwartet, müssen wir visuelle Inhalte schnell präsentieren, da sie sonst innerhalb von Sekunden wieder weg sind.

Deshalb wollten wir dafür sorgen, dass unser Projekt so schnell wie möglich wieder verfügbar ist. Ursprünglich haben wir JSON als Format zum Laden unserer Bewegungsdaten verwendet. Das Problem ist, dass wir die gesamte JSON-Datei laden müssen, bevor wir sie parsen können. Nicht sehr fortschrittlich.

Damit ein Projekt wie Dance Tonite immer mit der höchstmöglichen Framerate angezeigt wird, hat der Browser nur eine kleine Zeit pro Frame für JavaScript-Berechnungen. Wenn Sie zu lange dauern, ruckeln die Animationen. Anfangs kam es zu Rucklern, da diese riesigen JSON-Dateien vom Browser decodiert wurden.

Wir sind auf ein praktisches Streaming-Datenformat gestoßen, das als NDJSON oder durch Zeilenumbruch getrenntes JSON bezeichnet wird. Der Trick besteht darin, eine Datei mit einer Reihe gültiger JSON-Strings zu erstellen, die jeweils in einer eigenen Zeile stehen. So können wir die Datei während des Ladevorgangs parsen und die Leistung anzeigen, bevor sie vollständig geladen ist.

So sieht ein Abschnitt in einer unserer Aufzeichnungen aus:

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

Mit NDJSON konnten wir die Datendarstellung der einzelnen Frames der Aufführungen als Strings beibehalten. Wir konnten warten, bis die erforderliche Zeit erreicht war, bevor wir sie in Positionsdaten decodieren, um die erforderliche Verarbeitung über einen längeren Zeitraum zu verteilen.

4. Bewegung interpolieren

Da wir zwischen 30 und 60 Aufführungen gleichzeitig anzeigen wollten, mussten wir unsere Datenübertragungsrate noch weiter senken. Das Data Arts-Team hat sich in seinem Projekt Virtual Art Sessions mit demselben Problem befasst. Dabei werden Aufzeichnungen von Künstlern abgespielt, die in VR mit Tilt Brush malen. Das Problem wurde gelöst, indem Zwischenversionen der Nutzerdaten mit niedrigeren Frameraten erstellt und bei der Wiedergabe zwischen den Frames interpoliert wurden. Wir waren überrascht, dass wir kaum einen Unterschied zwischen einer interpolierten Aufnahme mit 15 fps und der ursprünglichen Aufnahme mit 90 fps feststellen konnten.

Du kannst das selbst ausprobieren, indem du Dance Tonite mit dem Abfragestring ?dataRate= dazu zwingst, die Daten mit verschiedenen Geschwindigkeiten abzuspielen. So können Sie die aufgezeichnete Bewegung bei 90 Bildern pro Sekunde, 45 Bildern pro Sekunde oder 15 Bildern pro Sekunde vergleichen.

Für die Position führen wir eine lineare Interpolation zwischen dem vorherigen und dem nächsten Keyframe durch, je nachdem, wie nah wir uns zeitlich zwischen den Keyframes befinden (Verhältnis):

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

Zur Orientierung führen wir eine sphärische lineare Interpolation (slerp) zwischen den Frames aus. Die Ausrichtung wird als Quaternion gespeichert.

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. Bewegungen mit Musik synchronisieren

Damit wir wissen, welcher Frame der aufgezeichneten Animationen wiedergegeben werden soll, müssen wir die aktuelle Zeit der Musik auf die Millisekunde genau kennen. Das HTML-Audioelement eignet sich zwar perfekt für das progressive Laden und die Wiedergabe von Audio, die von ihm bereitgestellte Zeiteigenschaft ändert sich jedoch nicht synchron mit der Frame-Schleife des Browsers. Es ist immer etwas daneben. Manchmal einen Bruchteil einer Millisekunde zu früh, manchmal einen Bruchteil zu spät.

Das führt zu Rucklern in unseren wunderschönen Tanzaufnahmen, was wir um jeden Preis vermeiden möchten. Um das zu beheben, haben wir unseren eigenen Timer in JavaScript implementiert. So können wir sicher sein, dass die Zeitspanne zwischen den Frames genau der Zeit entspricht, die seit dem letzten Frame vergangen ist. Immer wenn der Timer mehr als 10 ms von der Musik abweicht, wird er wieder synchronisiert.

6. Aussortieren und Nebel

Jede Geschichte braucht ein gutes Ende und wir wollten den Nutzern, die bis zum Ende unserer Erfahrung gekommen sind, etwas Überraschendes bieten. Beim Verlassen des letzten Raums betreten Sie eine ruhige Landschaft aus Kegeln und Zylindern. Sie fragen sich: „Ist das das Ende?“ Wenn Sie sich weiter in das Feld bewegen, sorgen die Töne der Musik plötzlich dafür, dass sich verschiedene Gruppen von Kegeln und Zylindern zu Tänzern formieren. Sie sind mitten auf einer großen Party! Als die Musik abrupt stoppt, fällt alles auf den Boden.

Das war für Zuschauer zwar toll, stellte uns aber vor einige Leistungsherausforderungen. VR-Geräte in Raumgröße und ihre High-End-Gaming-Rigs haben bei den rund 40 zusätzlichen Aufführungen, die für unser neues Ende erforderlich waren, einwandfrei funktioniert. Die Framerate wurde jedoch auf bestimmten Mobilgeräten halbiert.

Um dem entgegenzuwirken, haben wir Nebel eingeführt. Nach einer bestimmten Entfernung wird alles langsam schwarz. Da wir das, was nicht sichtbar ist, nicht berechnen oder zeichnen müssen, werden die Leistungen in nicht sichtbaren Räumen reduziert. So können wir sowohl die CPU als auch die GPU entlasten. Aber wie entscheiden Sie sich für die richtige Entfernung?

Einige Geräte können alles verarbeiten, was Sie ihnen vorlegen, andere sind stärker eingeschränkt. Wir haben uns für eine gleitende Skala entschieden. Durch das kontinuierliche Messen der Anzahl der Frames pro Sekunde können wir die Entfernung des Nebels entsprechend anpassen. Solange unsere Framerate gleichmäßig funktioniert, versuchen wir, den Nebel aus dem Weg zu räumen und so mehr Renderingaufgaben zu übernehmen. Wenn die Framerate nicht flüssig genug läuft, bringen wir den Nebel näher, damit wir die Renderingleistung in der Dunkelheit überspringen können.

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

Für alle da: VR für das Web entwickeln

Wenn Sie plattformübergreifende, asymmetrische Designs entwerfen und entwickeln, müssen Sie die Anforderungen der einzelnen Nutzer je nach Gerät berücksichtigen. Bei jeder Designentscheidung mussten wir uns überlegen, wie sich diese auf andere Nutzer auswirken könnte. Wie sorgen Sie dafür, dass das, was Sie in VR sehen, genauso spannend ist wie ohne VR und umgekehrt?

1. Die gelbe Kugel

Unsere VR-Nutzer im Raum würden also die Performances vorführen, aber wie würden die Nutzer mobiler VR-Geräte wie Cardboard, Daydream View oder Samsung Gear das Projekt erleben? Dazu haben wir ein neues Element in unsere Umgebung eingeführt: den gelben Globus.

Der gelbe Kreis
Der gelbe Kreis

Wenn Sie sich das Projekt in VR ansehen, sehen Sie es aus der Perspektive des gelben Balls. Wenn Sie von Raum zu Raum schweben, reagieren die Tänzer auf Ihre Anwesenheit. Sie machen Ihnen Zeichen, tanzen um Sie herum, machen lustige Bewegungen hinter Ihrem Rücken und springen schnell zur Seite, damit sie Sie nicht anrempeln. Der gelbe Kreis steht immer im Mittelpunkt.

Der Grund dafür ist, dass sich die gelbe Kugel beim Aufzeichnen eines Auftritts synchron mit der Musik durch die Mitte des Raums bewegt und in Schleifen herumläuft. Anhand der Position des Orbs kann der Darsteller einschätzen, wo er sich zeitlich befindet und wie viel Zeit ihm noch im Loop bleibt. Sie bietet einen natürlichen Fokus, um eine Leistung aufzubauen.

2. Ein anderer Blickwinkel

Wir wollten Nutzer ohne VR nicht ausschließen, da sie wahrscheinlich unsere größte Zielgruppe sind. Anstatt eine Art Fake-VR-Erlebnis zu schaffen, wollten wir Bildschirmgeräten ein ganz eigenes Erlebnis bieten. Wir hatten die Idee, die Leistungen aus isometrischer Perspektive zu zeigen. Diese Perspektive hat eine lange Geschichte in Computerspielen. Er wurde erstmals in Zaxxon verwendet, einem Weltraum-Shooter aus dem Jahr 1982. Während VR-Nutzer mitten im Geschehen sind, bietet die isometrische Perspektive einen Überblick über die Aktion. Wir vergrößerten die Modelle etwas, um etwas Puppenhaus-Ästhetik zu verleihen.

3. Schatten: Fake it till you make it

Wir haben festgestellt, dass es einigen Nutzern schwerfiel, die Tiefe in unserer isometrischen Perspektive zu erkennen. Ich bin mir ziemlich sicher, dass Zaxxon auch eines der ersten Computerspiele in der Geschichte war, bei dem ein dynamischer Schatten unter den fliegenden Objekten projiziert wurde.

Schatten

Es stellt sich heraus, dass es schwierig ist, Schatten in 3D zu erstellen. Das gilt vor allem für Geräte mit begrenztem Platzangebot wie Smartphones. Anfangs mussten wir die schwierige Entscheidung treffen, sie aus der Gleichung herauszufiltern. Doch nachdem wir den Autor von Three.js gefragt und den Demo-Hacker Mr. Doob um Rat gefragt hatten, kam er auf die neue Idee, sie nachzuahmen.

Anstatt zu berechnen, wie jedes unserer schwebenden Objekte unser Licht verdeckt und somit Schatten verschiedener Formen wirft, zeichnen wir dasselbe kreisförmige, verschwommene Texturbild unter jedem der Objekte. Da unsere visuellen Elemente nicht versuchen, die Realität nachzuahmen, konnten wir mit nur wenigen Anpassungen ganz einfach einen guten Effekt erzielen. Je näher sie dem Boden kommen, wird die Textur dunkler und kleiner. Wenn sie nach oben gehen, werden die Texturen transparenter und größer.

Dazu haben wir diese Textur mit einem sanften Farbverlauf von Weiß nach Schwarz (ohne Alphatransparenz) verwendet. Wir legen das Material als transparent fest und verwenden subtraktive Übergänge. So können sie sich gut überschneiden:

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. Da sein

Besucher ohne VR-Headset können durch Klicken auf die Köpfe der Tänzer die Dinge aus der Perspektive der Tänzer sehen. Aus diesem Blickwinkel werden viele kleine Details sichtbar. Um die Schritte synchron zu halten, sehen sich die Tänzer kurz an. Als die Kugel den Raum betritt, sehen Sie, wie sie nervös in ihre Richtung schaut. Auch wenn du als Zuschauer diese Bewegungen nicht beeinflussen kannst, vermittelt es das Gefühl der Immersion überraschend gut. Das war uns lieber, als unseren Nutzern eine langweilige, mausgesteuerte Pseudo-VR-Version zu präsentieren.

5. Aufzeichnungen teilen

Wir wissen, wie stolz du sein kannst, wenn du eine aufwendig choreografierte Aufnahme mit 20 Ebenen von Darstellern hinbekommst, die aufeinander reagieren. Wir wussten, dass unsere Nutzer es ihren Freunden zeigen würden. Ein Standbild dieser Leistung vermittelt jedoch nicht genug. Stattdessen wollten wir unseren Nutzern ermöglichen, Videos ihrer Auftritte zu teilen. Wieso eigentlich kein GIF? Unsere Animationen sind flach schattiert und eignen sich perfekt für die eingeschränkten Farbpaletten des Formats.

Aufzeichnungen teilen

Wir haben uns für GIF.js entschieden, eine JavaScript-Bibliothek, mit der sich animierte GIFs direkt im Browser codieren lassen. Die Codierung von Frames wird an Webworker übergeben, die im Hintergrund als separate Prozesse ausgeführt werden können. So können mehrere Prozessoren parallel genutzt werden.

Leider war der Codierungsvorgang aufgrund der Anzahl der Frames, die wir für die Animationen benötigten, immer noch zu langsam. Das GIF kann mit einer begrenzten Farbpalette kleine Dateien erstellen. Wir haben festgestellt, dass die meiste Zeit für die Suche nach der am besten passenden Farbe für jedes Pixel aufgewendet wurde. Wir konnten diesen Prozess verzehnfachen, indem wir es in einer kurzen Kleinigkeit hacken: Wenn die Farbe des Pixels die gleiche ist wie die des letzten, verwenden wir dieselbe Farbe aus der Palette wie zuvor.

Die Codierung war jetzt zwar schnell, aber die resultierenden GIF-Dateien waren zu groß. Im GIF-Format können Sie angeben, wie jeder Frame über dem vorherigen angezeigt werden soll, indem Sie die Dispose-Methode definieren. Um kleinere Dateien zu erhalten, aktualisieren wir nicht jedes Pixel in jedem Frame, sondern nur die Pixel, die sich geändert haben. Obwohl wir den Codierungsprozess wieder verlangsamen, hat dies unsere Dateigrößen verringert.

6. Solide Grundlage: Google Cloud und Firebase

Das Back-End einer Website mit nutzergenerierten Inhalten kann oft kompliziert und instabil sein. Wir haben jedoch ein System entwickelt, das dank Google Cloud und Firebase einfach und robust ist. Wenn ein Darsteller einen neuen Tanz in das System hochlädt, wird er anonym durch Firebase Authentication authentifiziert. Er erhält die Berechtigung, seine Aufnahme mit Cloud Storage for Firebase in einen temporären Speicher hochzuladen. Wenn der Upload abgeschlossen ist, ruft der Clientcomputer mit seinem Firebase-Token einen Cloud Functions for Firebase-HTTP-Trigger auf. Dadurch wird ein Serverprozess ausgelöst, der die Einreichung validiert, einen Datenbankeintrag erstellt und die Aufnahme in ein öffentliches Verzeichnis in Google Cloud Storage verschiebt.

Fester Boden

Alle unsere öffentlichen Inhalte werden in einer Reihe von Flatfiles in einem Cloud Storage-Bucket gespeichert. So können unsere Daten weltweit schnell abgerufen werden und wir müssen uns keine Sorgen machen, dass hohe Zugriffszahlen die Datenverfügbarkeit beeinträchtigen.

Wir haben eine Firebase Realtime Database und Cloud Functions-Endpunkte verwendet, um ein einfaches Moderations-/Kurationstool zu erstellen, mit dem wir uns jede neue Einreichung in VR ansehen und neue Playlists von jedem Gerät aus veröffentlichen können.

7. Service Worker

Dienstprogramme sind eine relativ neue Innovation, die das Caching von Website-Assets unterstützt. In unserem Fall laden Service Worker unsere Inhalte für wiederkehrende Besucher blitzschnell und ermöglichen sogar das Offline-Arbeiten der Website. Das ist wichtig, da viele unserer Besucher eine mobile Verbindung mit unterschiedlicher Qualität nutzen.

Dank eines praktischen Webpack-Plug-ins, das die meiste Arbeit für Sie erledigt, war es ganz einfach, dem Projekt Service Workers hinzuzufügen. In der folgenden Konfiguration generieren wir einen Service Worker, der alle unsere statischen Dateien automatisch im Cache speichert. Es wird die neueste Playlistdatei aus dem Netzwerk abgerufen, sofern verfügbar, da die Playlist ständig aktualisiert wird. Alle JSON-Aufzeichnungsdateien sollten aus dem Cache abgerufen werden, sofern verfügbar, da sich diese nie ändern werden.

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

Derzeit werden im Plug-in keine fortlaufend geladenen Medien-Assets wie unsere Musikdateien unterstützt. Wir haben das Problem dadurch behoben, dass wir den Cloud Storage-Cache-Control-Header dieser Dateien auf public, max-age=31536000 gesetzt haben, damit der Browser die Datei bis zu einem Jahr im Cache speichert.

Fazit

Wir sind gespannt, wie Creator diese Funktion nutzen und als Tool für kreative Ausdrucksweise mit Bewegung einsetzen werden. Wir haben den gesamten Open-Source-Code veröffentlicht, den Sie unter https://github.com/puckey/dance-tonite finden. In dieser frühen Phase von VR und insbesondere WebVR sind wir gespannt, in welche neuen kreativen und unerwarteten Richtungen sich dieses neue Medium entwickeln wird. Tanzen an.