Tanztonite in WebVR

Ich war begeistert, als sich das Google Data Arts-Team an Moniker und mich wendete, um gemeinsam die Möglichkeiten zu erkunden, die WebVR bietet. Ich habe die Arbeit des Teams im Laufe der Jahre beobachtet, und die Projekte haben mich schon immer angesprochen. Aus unserer Zusammenarbeit entstand Dance Tonite, ein sich ständig veränderndes VR-Tanzerlebnis mit LCD Soundsystem und seinen Fans. Und so haben wir es gemacht:

Konzept

Wir haben mit der Entwicklung einer Reihe von Prototypen mit WebVR begonnen, einem offenen Standard, der es ermöglicht, über den Browser eine Website zu besuchen und in die VR zu gehen. Ziel ist es, es jedem leichter zu machen, sich an VR-Erlebnissen zu erfreuen, unabhängig davon, welches Gerät Sie haben.

Das haben wir uns zu Herzen genommen. Was wir uns ausgedacht haben, sollte mit allen Arten von VR funktionieren: von VR-Headsets für Smartphones wie Daydream View von Google, Cardboard und Gear VR von Samsung bis hin zu raumbezogenen Systemen wie HTC VIVE und Oculus Rift, die Ihre physischen Bewegungen in Ihrer virtuellen Umgebung widerspiegeln. Am wichtigsten war jedoch, dass es im Web wichtig ist, etwas zu entwickeln, das auch für alle funktioniert, die kein VR-Gerät haben.

1. Motion Capture selbst machen

Wir wollten die Nutzer kreativ einbeziehen und begannen, uns über die Möglichkeiten der Teilhabe und Selbstentfaltung mithilfe von VR nachzudenken. Wir waren beeindruckt von der Genauigkeit, mit der man sich in VR bewegen und umsehen kann, und von der Qualität. Das brachte uns auf eine Idee. Wie wäre es, statt Bewegungen aufzuzeichnen oder etwas anzuschauen?

Jemand nimmt sich selbst auf Dance Tonite auf. Auf dem Bildschirm hinter ihnen ist zu sehen, was sie in ihrem Headset sehen

Wir erstellten einen Prototyp, in dem wir die Positionen unserer VR-Brillen und -Controller beim Tanzen aufzeichneten. Wir ersetzten die aufgezeichneten Positionen durch abstrakte Formen und waren von den Ergebnissen begeistert. Die Ergebnisse waren so menschlich und enthielten so viel Persönlichkeit! Wir stellten schnell fest, dass wir mit WebVR günstige Motion Capture-Aufnahmen von zu Hause machen konnten.

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 Ihr Code neue Frames aus der richtigen Perspektive rendern kann. Über die GamePad API mit WebVR können wir auch über das Objekt GamepadPose auf die Position/Ausrichtung der Nutzercontroller zugreifen. Wir speichern einfach alle Werte für Position und Ausrichtung in jedem Frame, um die Bewegungen der Nutzenden zu „aufzeichnen“.

2. Minimalismus und Kostüme

Mit den heutigen VR-Geräten für Raummaße können wir drei Punkte des Körpers eines Nutzers verfolgen: seinen Kopf und seine beiden Hände. In Dance Tonite wollten wir die Menschheit bei der Bewegung dieser 3 Punkte im Weltraum im Blick behalten. 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, die menschlichen Gehirne zum Arbeiten zu bringen.

Dieses Video, das die Arbeit des schwedischen Psychologen Gunnar Johansson zeigt, war eines der Beispiele, auf die wir Bezug genommen haben, als es darum ging, Dinge so weit wie möglich zu reduzieren. Sie zeigt, wie schwebende weiße Punkte sofort als Körper zu erkennen sind, wenn sie in Bewegung gesehen werden.

Visuell haben wir uns bei dieser Aufnahme von Margarete Hastingss Reinszenierung des triadischen Balletts von Oskar Schlemmer im Jahr 1970 von den farbigen Räumen und geometrischen Kostümen inspiriert.

Während Schlemmer sich für abstrakte geometrische Kostüme entschieden hatte, die Bewegungen seiner Tänzer auf die von Puppen und Marionetten zu beschränken, hatten wir das entgegengesetzte Ziel für Dance Tonite.

Unsere Wahl der Formen hängt davon ab, wie viele Informationen sie durch die Rotation vermitteln. Eine Kugel sieht unabhängig von ihrer Drehung gleich aus, aber ein Kegel zeigt tatsächlich in die Richtung, in die er hinsieht, und sieht von vorn und hinten anders aus.

3. Loop-Pedal für Bewegung

Wir wollten große Gruppen von Tanzpersonen beim Tanzen und in Bewegung zeigen. Eine Liveübertragung wäre nicht möglich, da VR-Geräte nicht in ausreichender Anzahl vorhanden sind. Wir wollten aber, dass Gruppen von Menschen durch Bewegungen aufeinander reagieren. Wir ließen uns an Norman McClarens Rekursion in seinem Video "Canon" aus dem Jahr 1964 erinnern.

McClarens Performance umfasst eine Reihe hoch choreografierter Sätze, die nach jeder Schleife miteinander interagieren. Ähnlich wie bei einem Loop-Pedal in der Musik, bei dem Musiker mit verschiedenen Stücken von Livemusik jammen, wollten wir eine Umgebung schaffen, in der Nutzer lockere Versionen von Auftritten frei improvisieren können.

4. Zimmer mit Verbindungstür

Zimmer mit Verbindungstür

Wie viele andere Musikstücke werden auch die Titel von LCD Soundsystem mithilfe von präzisen zeitlichen Messungen aufgebaut. Ihr Titel "Tonite", der in unserem Projekt enthalten ist, enthält genau 8 Sekunden lange Maße. Die Nutzer sollten für jede 8-Sekunden-Schleife im Track eine Leistung durchführen. Auch wenn sich der Rhythmus dieser Messwerte nicht ändert, tun sich dies doch ihre musikalischen Inhalte. Im Laufe des Songs gibt es Momente mit verschiedenen Instrumenten und Gesang, auf die die Künstler unterschiedlich reagieren können. Jede dieser Messungen wird als Raum ausgedrückt, in dem Menschen eine passende Leistung durchführen können.

Leistungsoptimierung: keine Frames entfernen

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.

Im VR-Modus ist es am übelsten, dass die Framerate nicht mit der Bewegung Schritt hält. Wenn du den Kopf drehst, das Bild, das deine Augen sehen, jedoch nicht mit der Bewegung deines Innenohrs übereinstimmt, kommt es zu einer sofortigen Magenabwanderung. Aus diesem Grund mussten große Verzögerungen bei der Framerate vermieden werden. Hier sind einige Optimierungen, die wir implementiert haben.

1. Instanzierte Puffergeometrie

Da für unser gesamtes Projekt nur eine Handvoll 3D-Objekte verwendet werden, konnten wir mit der instanzierten Puffergeometrie eine enorme Leistungssteigerung erzielen. Im Grunde können Sie damit Ihr Objekt einmal auf die GPU hochladen und in einem einzigen Zeichenaufruf so viele "Instanzen" dieses Objekts zeichnen, wie Sie möchten. 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 Instanz-Zwischenspeicher-Geometrie ist Teil von ThreeJS. Wir haben jedoch die experimentelle und laufende Fork von Dusan Bosnjak verwendet, die THREE.InstanceMesh implementiert, was die Arbeit mit Instanzd-Puffergeometrie vereinfacht.

2. Garbage Collector vermeiden

Wie bei vielen anderen Skriptsprachen gibt JavaScript automatisch Arbeitsspeicher frei, da es erkennt, welche Objekte, die zugewiesen wurden, nicht mehr verwendet werden. Dieser Vorgang wird als automatische Speicherbereinigung bezeichnet.

Entwickler haben keinen Einfluss darauf, wann dies geschieht. Die automatische Speicherbereinigung könnte jederzeit an unserer Tür auftauchen und den Müll entleeren, was dazu führen würde, dass die Rahmen fallen, wenn sie sich ihre Zeit nehmen.

Die Lösung dafür besteht darin, durch Recycling unserer Objekte so wenig Müll wie möglich zu erzeugen. Anstatt für jede Berechnung ein neues Vektorobjekt zu erstellen, haben wir Scratch-Objekte zur Wiederverwendung markiert. Da wir sie festhalten und unsere Verweise auf sie außerhalb unseres Zuständigkeitsbereichs verschieben, wurden sie nicht zur Entfernung gekennzeichnet.

Hier sehen Sie zum Beispiel unseren Code, mit dem die Positionsmatrix des Kopfs und der Hände des Nutzers in das Array mit Positions-/Drehwerten konvertiert wird, das wir für jeden Frame speichern. Durch die Wiederverwendung von SERIALIZE_POSITION, SERIALIZE_ROTATION und SERIALIZE_SCALE vermeiden Sie die Speicherzuweisung und die automatische Speicherbereinigung, die entstehen würden, wenn bei jedem Aufruf der Funktion neue Objekte erstellt werden 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. Serialisierung von Bewegung und Progressive-Wiedergabe

Um die Bewegungen der Nutzer in VR zu erfassen, mussten wir die Position und Drehung von Headset und Controllern serialisieren und diese Daten auf unsere Server hochladen. Zuerst haben wir die vollständigen Transformationsmatrizen für jeden Frame erfasst. Das hat gut funktioniert, aber mit 16 Zahlen multipliziert mit 3 Positionen bei jeweils 90 Bildern pro Sekunde führte dies zu sehr großen Dateien und daher lange Wartezeiten beim Hoch- und Herunterladen der Daten. Da wir nur die Positions- und Rotationsdaten aus den Transformationsmatrizen extrahiert haben, konnten wir diese Werte von 16 auf 7 reduzieren.

Da Besucher häufig auf einen Link klicken, ohne genau zu wissen, was sie erwartet, müssen wir visuelle Inhalte schnell bereitstellen, da sie sonst innerhalb von Sekunden wieder verschwinden.

Aus diesem Grund wollten wir dafür sorgen, dass unser Projekt so schnell wie möglich starten kann. Anfangs verwendeten wir JSON als Format, um unsere Bewegungsdaten zu laden. Das Problem besteht darin, dass wir die vollständige JSON-Datei laden müssen, bevor wir sie parsen können. Nicht sehr progressiv.

Damit ein Projekt wie Dance Tonite mit der höchstmöglichen Framerate angezeigt wird, hat der Browser nur eine geringe Zeitdauer pro Frame für JavaScript-Berechnungen. Wenn Sie zu lange brauchen, ruckeln die Animationen. Zuerst ruckelten wir bei, da diese riesigen JSON-Dateien vom Browser decodiert wurden.

Wir sind auf ein praktisches Streamingdatenformat gestoßen, das auf NDJSON oder durch Zeilenumbruch getrennten JSON basiert. Der Trick hier besteht darin, eine Datei mit einer Reihe gültiger JSON-Strings zu erstellen, die jeweils in einer eigenen Zeile stehen. Auf diese Weise können Sie die Datei parsen, während sie geladen wird. So können wir die Leistung prüfen, bevor sie vollständig geladen ist.

So sieht ein Abschnitt 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 können wir die Datendarstellung der einzelnen Frames der Auftritte als Strings beibehalten. Wir konnten warten, bis die erforderliche Zeit erreicht ist, bevor wir sie in Positionsdaten decodierten und so die erforderliche Verarbeitung über die Zeit verteilten.

4. Bewegung interpolieren

Da wir 30 bis 60 Auftritte gleichzeitig zeigen wollten, mussten wir unsere Datenrate noch weiter senken. Das Data Arts-Team ging im Rahmen des Projekts Virtual Art Sessions an das gleiche Problem heran, bei dem Aufnahmen von Künstlern, die in VR malen, mit Tilt Brush wiedergegeben wurden. Das Problem wurde dadurch gelöst, dass Zwischenversionen der Nutzerdaten mit niedrigeren Framerates erstellt und während 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 Aufzeichnung mit 90 fps erkennen konnten.

Sie können Dance Tonite mit dem Abfragestring ?dataRate= erzwingen, um die Daten in verschiedenen Geschwindigkeiten wiederzugeben. Damit 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 durch, je nachdem, wie nahe wir zwischen den Keyframes sind (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 Ausrichtung führen wir eine sphärische lineare Interpolation (Slerp) zwischen den Keyframes durch. Die Ausrichtung wird als Quaternionen gespeichert.

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

5. Bewegungen mit Musik synchronisieren

Um zu ermitteln, welcher Frame der aufgezeichneten Animationen wiedergegeben werden soll, müssen wir die aktuelle Zeit der Musik bis auf die Millisekunde genau kennen. Es stellt sich heraus, dass das HTML-Audioelement zwar perfekt für das schrittweise Laden und Abspielen von Ton geeignet ist, die bereitgestellte Zeiteigenschaft jedoch nicht synchron mit der Frameschleife des Browsers ist. Die Abweichung ist immer ein wenig. Manchmal kommt ein Bruchteil einer ms zu früh, manchmal ein Bruchteil zu spät.

Dies führt zu Rucklern in unseren wunderschönen Tanzaufnahmen, die wir unbedingt vermeiden sollten. Um dies zu beheben, haben wir unseren eigenen Timer in JavaScript implementiert. Auf diese Weise können wir sicher sein, dass die Zeit, die zwischen den Frames wechselt, genau der Zeit entspricht, die seit dem letzten Frame vergangen ist. Wenn unser Timer mehr als 10 ms von der Musik abweicht, synchronisieren wir ihn wieder.

6. Culling und Nebel

Jede Geschichte braucht ein gutes Ende. Wir wollten etwas Überraschendes für die Nutzer schaffen, die es bis zum Ende geschafft haben. Wenn Sie den letzten Raum verlassen, betreten Sie eine ruhige Landschaft aus Kegeln und Zylindern. „Ist das das Ende?“, fragen Sie sich. Wenn man sich weiter in das Feld hineinbewegt, entstehen durch die Töne der Musik plötzlich verschiedene Gruppen von Kegeln und Zylindern zu Tänzern. Man befindet sich inmitten einer riesigen Party! Als die Musik plötzlich stoppt, fällt alles auf den Boden.

Für die Zuschauer kam dies zwar toll an, es gab aber einige Hürden im Hinblick auf die Leistung. Die VR-Geräte im Raum und ihre High-End-Gaming-Rigs schnitten perfekt ab – mit den 40 ungeraden Zusatzleistungen, die für unser neues Endprodukt nötig waren. Auf einigen Mobilgeräten wurde die Framerate halbiert.

Um dem entgegenzuwirken, haben wir Nebel eingeführt. Nach einer gewissen Entfernung wird alles langsam schwarz. Da wir nicht sichtbare Elemente nicht berechnen oder zeichnen müssen, werten wir die Leistung in Räumen auf, die nicht sichtbar sind. So sparen wir Arbeit für CPU und GPU. Aber wie finden Sie den richtigen Abstand?

Einige Geräte können mit allem umgehen, was Sie auf sie auswerfen, andere sind strenger. Wir haben uns für eine gleitende Skala entschieden. Indem wir die Anzahl der Bilder pro Sekunde kontinuierlich messen, können wir die Entfernung des Nebels entsprechend anpassen. Solange unsere Framerate reibungslos funktioniert, versuchen wir, mehr Rendering-Arbeit zu übernehmen, indem wir den Nebel beseitigen. Wenn die Framerate nicht gleichmäßig genug läuft, rücken wir den Nebel näher heran, sodass wir das Rendering 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 etwas dabei: VR für das Web entwickeln

Beim Entwerfen und Entwickeln plattformübergreifender, asymmetrischer Angebote müssen die Anforderungen der einzelnen Nutzer je nach Gerät berücksichtigt werden. Bei jeder Designentscheidung mussten wir herausfinden, wie sich das auf andere Nutzende auswirken könnte. Wie sorgen Sie dafür, dass das, was Sie in VR sehen, genauso aufregend ist wie ohne VR und umgekehrt?

1. Die gelbe Kugel

Unsere VR-Nutzer im Raum würden also die Vorstellungen durchführen. Aber wie würden die Nutzer von mobilen VR-Geräten wie Cardboard, Daydream View oder Samsung Gear das Projekt erleben? Dafür haben wir ein neues Element in unsere Umgebung eingeführt: die gelbe Kugel.

Die gelbe Kugel
Die gelbe Kugel

Wenn Sie das Projekt im VR-Modus betrachten, tun Sie dies aus der Sicht der gelben Kugel. Während du von Raum zu Raum schwebst, reagieren die Tänzer auf deine Anwesenheit. Sie zeigen zu Ihnen, tanzen um Sie herum, machen lustige Bewegungen hinter Ihrem Rücken und bewegen sich schnell aus dem Weg, damit sie nicht auf Sie stoßen. Die gelbe Kugel steht immer im Mittelpunkt.

Der Grund dafür ist, dass sich die gelbe Kugel während der Aufnahme mit der Musik durch die Mitte des Raums bewegt und wieder in Schleife wiedergegeben wird. Die Position der Kugel gibt dem Künstler eine Vorstellung davon, wo er sich im Laufe der Zeit befindet und wie viel Zeit er in seiner Schleife noch übrig hat. Es bietet ihnen die Möglichkeit, eine Performance aufzubauen.

2. Ein anderer Blickwinkel

Wir wollten Nutzer ohne VR nicht ausschließen, vor allem weil sie wahrscheinlich unsere größte Zielgruppe sind. Anstatt ein künstliches VR-Erlebnis zu schaffen, wollten wir bildschirmbasierten Geräten ein ganz eigenes Erlebnis bieten. Wir hatten die Idee, die Auftritte von oben aus einer isometrischen Perspektive zu zeigen. Diese Perspektive hat eine lange Geschichte in Computerspielen. Es wurde erstmals 1982 in Zaxxon verwendet, einem Weltraum-Shooter-Spiel. Während sich VR-Nutzer mitten im Geschehen befinden, bietet die isometrische Perspektive einen gottähnlichen Blick auf das Geschehen. Wir haben die Modelle leicht vergrößert und einen Hauch von Puppenhaus-Ästhetik erhalten.

3. Schatten: vortäuschen, bis du es schaffst

Wir haben festgestellt, dass es für einige unserer Nutzer schwierig war, die Tiefe in unserer isometrischen Perspektive zu sehen. Daher bin ich mir sicher, dass Zaxxon aus diesem Grund auch eines der ersten Computerspiele in der Geschichte war, das einen dynamischen Schatten unter seine Flugobjekte projiziert hat.

Schatten

Wie sich herausstellt, ist das Erstellen von Schatten in 3D schwierig. Das gilt besonders für Geräte mit eingeschränktem Zugriff wie Smartphones. Anfangs mussten wir die schwierige Entscheidung treffen, um sie aus der Gleichung zu entfernen, aber nachdem er den Autor von Three.js und den erfahrenen Demo-Hacker Herr Doob um Rat gefragt hatte, kam er auf die neue Idee, sie zu fälschen.

Anstatt berechnen zu müssen, wie jedes unserer unverankerten Objekte unsere Lichter verdeckt und daher Schatten verschiedener Formen wirft, zeichnen wir unter jedem von ihnen dasselbe kreisförmige, verschwommene Texturbild. Da unsere Visualisierungen von vornherein nicht versuchen, die Realität zu imitieren, haben wir herausgefunden, dass wir mit nur wenigen Anpassungen ganz einfach damit durchkommen können. Je näher die Objekte dem Boden kommen, werden die Texturen dunkler und kleiner. Nach oben werden die Texturen transparenter und größer.

Dazu haben wir diese Textur mit einem Farbverlauf von weichem Weiß zu Schwarz (ohne Alphatransparenz) verwendet. Wir legen das Material als transparent fest und verwenden subtraktive Überblendungen. So können sie sich gut verschmelzen, wenn sie sich ü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. Präsenz zeigen

Besucher ohne VR können auf die Köpfe eines Künstlers klicken, um die Tänzer aus der Perspektive der Tänzerin zu beobachten. Aus diesem Blickwinkel werden viele kleine Details sichtbar. Die Tänzer versuchen, sich schnell bei ihren Auftritten anzuschauen. Wenn die Kugel in den Raum eintritt, siehst du, wie sie nervös in ihre Richtung blicken. Als Betrachter können Sie diese Bewegungen zwar nicht beeinflussen, sie vermitteln jedoch überraschend gut das Gefühl des Eintauchens. Auch hier haben wir unseren Nutzern lieber eine dezente, mausgesteuerte Imitat-VR-Version präsentiert.

5. Aufnahmen teilen

Wir wissen, wie stolz ihr auf euch sein könnt, wenn ihr eine aufwendig choreografierte Aufnahme von 20 Schichten mit Darstellern macht, die aufeinander reagieren. Unsere Nutzenden würden sie wahrscheinlich ihren Freunden zeigen wollen. Ein Standbild dieser Leistung verdeutet jedoch nicht genug. Stattdessen wollten wir unseren Nutzern ermöglichen, Videos ihrer Auftritte zu teilen. Warum nicht ein GIF? Unsere Animationen sind flach schattiert, passend für die begrenzten Farbpaletten des Formats.

Aufnahmen teilen

Deshalb haben wir GIF.js eingeführt, eine JavaScript-Bibliothek, mit der Sie animierte GIFs über den Browser codieren können. Es verlagert die Codierung von Frames auf Web-Worker, die im Hintergrund als separate Prozesse ausgeführt werden können, um so die Vorteile mehrerer Prozessoren zu nutzen, die nebeneinander arbeiten.

Leider war der Codierungsprozess aufgrund der für die Animationen benötigten Anzahl von Frames immer noch zu langsam. Mit dem GIF können mithilfe einer begrenzten Farbpalette kleine Dateien erstellt werden. Wir haben die meiste Zeit damit verbracht, für jedes Pixel die passendste Farbe zu finden. Wir konnten diesen Prozess durch Hacken in einer kleinen Abkürzung um das Zehnfache optimieren: Wenn die Farbe des Pixels mit der des letzten übereinstimmt, verwenden Sie dieselbe Farbe aus der Palette wie zuvor.

Jetzt hatten wir schnelle Codierungen, aber die resultierenden GIF-Dateien waren zu groß. Im GIF-Format können Sie angeben, wie jeder Frame über dem letzten Frame angezeigt werden soll, indem Sie seine Beseitigungsmethode definieren. Um kleinere Dateien zu erhalten, aktualisieren wir nicht jedes Pixel für jeden Frame, sondern nur die geänderten Pixel. Der Codierungsprozess wurde zwar noch einmal verlangsamt, aber dadurch konnten wir auch die Dateigrößen gut reduzieren.

6. Solide Grundlage: Google Cloud und Firebase

Das Back-End einer Website mit von Nutzern erstellten Inhalten kann oft kompliziert und instabil sein. Dank Google Cloud und Firebase haben wir jedoch ein einfaches und robustes System entwickelt. Wenn ein Künstler einen neuen Tanz in das System hochlädt, wird dieser 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 HTTP-Trigger von Cloud Functions for Firebase auf. Dadurch wird ein Serverprozess ausgelöst, der die Einreichung validiert, einen Datenbankeintrag erstellt und die Aufzeichnung 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. Das bedeutet, dass unsere Daten weltweit schnell zugänglich sind und wir uns keine Gedanken über hohe Traffic-Lasten machen müssen, die die Datenverfügbarkeit in irgendeiner Weise beeinträchtigen.

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

7. Service Worker

Service Worker sind eine relativ aktuelle Innovation zur Verwaltung des Caching von Website-Assets. In unserem Fall laden Service Worker unsere Inhalte blitzschnell für wiederkehrende Besucher und ermöglichen sogar, dass die Website offline arbeitet. Dies sind wichtige Funktionen, da viele unserer Besucher Mobilfunkverbindungen von unterschiedlicher Qualität nutzen werden.

Dank eines praktischen Webpack-Plug-ins, das den Großteil der Arbeit abnimmt, war das Hinzufügen von Service Workern zum Projekt einfach. In der folgenden Konfiguration generieren wir einen Service Worker, der automatisch alle unsere statischen Dateien im Cache speichert. Sie ruft die neueste Playlist-Datei aus dem Netzwerk ab, falls verfügbar, da die Playlist ständig aktualisiert wird. Alle JSON-Aufzeichnungsdateien sollten, sofern verfügbar, aus dem Cache abgerufen werden, da sich diese nie ändern.

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 verarbeitet das Plug-in keine progressiven Medien-Assets wie unsere Musikdateien. Daher haben wir den Cloud Storage-Header Cache-Control für diese Dateien auf public, max-age=31536000 gesetzt, sodass der Browser die Datei bis zu ein Jahr lang im Cache speichert.

Fazit

Wir sind schon gespannt darauf, wie Künstler dieses Spielerlebnis ergänzen und es als Tool für den kreativen Ausdruck mithilfe von Bewegung einsetzen werden. Wir haben den gesamten Open-Source-Code veröffentlicht, den Sie unter https://github.com/puckey/dance-tonite finden. In den Anfängen von VR und insbesondere WebVR sind wir gespannt, welche neuen kreativen und unerwarteten Entwicklungen das neue Medium einschlagen wird. Tanz weiter