Das Hobbit-Erlebnis

Mit Mobile WebGL Mittelerde zum Leben erwecken

Daniel Isaksson
Daniel Isaksson

In der Vergangenheit war es eine Herausforderung, interaktive, webbasierte und Multimedia-Inhalte auf Mobiltelefone und Tablets zu bringen. Die Haupteinschränkungen sind die Leistung, die API-Verfügbarkeit, die Einschränkungen von HTML5-Audio auf Geräten und die fehlende nahtlose Inline-Videowiedergabe.

Anfang des Jahres haben wir gemeinsam mit Freunden von Google und Warner Bros. ein Projekt gestartet, bei dem wir für den neuen Hobbit-Film Der Hobbit: Smaugs Einöde ein mobiles Weberlebnis bieten. Die Entwicklung eines Multimedia-Experiments für Mobilgeräte war eine inspirierende und anspruchsvolle Aufgabe.

Die Funktion wurde für Chrome für Android auf den neuen Nexus-Geräten optimiert, auf denen wir jetzt Zugriff auf WebGL und Web Audio haben. Ein großer Teil der App ist jedoch auch auf Nicht-WebGL-Geräten und in Browsern zugänglich – dank hardwarebeschleunigter Erstellung und CSS-Animationen.

Das gesamte Erlebnis basiert auf einer Karte von Mittelerde und den Schauplätzen und Charakteren aus den Hobbit-Filmen. Dank WebGL konnten wir die faszinierende Welt der Hobbit-Trilogie erforschen und den Nutzern die Kontrolle überlassen.

Herausforderungen von WebGL auf Mobilgeräten

Der Begriff „Mobilgeräte“ ist sehr weit gefasst. Die technischen Daten der Geräte variieren stark. Als Entwickler müssen Sie also entscheiden, ob Sie mehr Geräte mit weniger komplexen Funktionen unterstützen möchten oder ob Sie, wie in diesem Fall, die unterstützten Geräte auf diejenigen beschränken möchten, die eine realistischere 3D-Welt darstellen können. Bei „Reise durch Mittelerde“ konzentrierten wir uns auf Nexus-Geräte und fünf beliebte Android-Smartphones.

Im Test haben wir wie bei einigen unserer vorherigen WebGL-Projekte three.js verwendet. Wir begannen mit der Implementierung, indem wir eine erste Version des Trollshaw-Spiels entwickelten, das gut auf dem Nexus 10-Tablet laufen sollte. Nach einigen ersten Tests auf dem Gerät hatten wir eine Liste von Optimierungen im Kopf, die ähnlich aussahen wie die, die wir normalerweise für einen Laptop mit geringer Leistung verwenden würden:

  • Low-Poly-Modelle verwenden
  • Texturen mit niedriger Auflösung verwenden
  • Reduzieren Sie die Anzahl der Zeichenaufrufe so weit wie möglich, indem Sie Geometrie zusammenführen.
  • Materialien und Beleuchtung vereinfachen
  • Posteffekte entfernen und Kantenglättung deaktivieren
  • JavaScript-Leistung optimieren
  • WebGL-Canvas in halber Größe rendern und mit CSS hochskalieren

Nachdem wir diese Optimierungen auf unsere erste grobe Version des Spiels angewendet hatten, hatten wir eine konstante Framerate von 30 fps, mit der wir zufrieden waren. Zu diesem Zeitpunkt war es unser Ziel, die Bilder zu verbessern, ohne die Framerate zu beeinträchtigen. Wir haben viele Tricks ausprobiert: Einige haben sich wirklich auf die Leistung ausgewirkt, einige hatten nicht so große Auswirkungen, wie wir gehofft hatten.

Low-Poly-Modelle verwenden

Beginnen wir mit den Modellen. Die Verwendung von Low-Poly-Modellen trägt sicherlich zu mehr Downloadzeit und zur Initialisierung der Szene bei. Wir haben festgestellt, dass wir die Komplexität erheblich erhöhen können, ohne die Leistung erheblich zu beeinträchtigen. Die Trollmodelle, die wir in diesem Spiel verwenden, haben etwa 5.000 Flächen und die Szene hat ca. 40.000 Flächen, was einwandfrei funktioniert.

Einer der Trolle im Trollwald
Einer der Trolle im Trollwald

Bei einem anderen (noch nicht veröffentlichten) Standort wurde die Leistung durch die Reduzierung der Polygone stärker beeinträchtigt. In diesem Fall wurden für Mobilgeräte weniger Polygonobjekte als für Desktops geladen. Das Erstellen verschiedener Gruppen von 3D-Modellen ist nicht immer mit zusätzlichem Arbeitsaufwand verbunden. Es hängt wirklich davon ab, wie komplex Ihre Modelle zu Beginn sind.

Bei der Arbeit an großen Szenen mit vielen Objekten haben wir versucht, bei der Unterteilung der Geometrie strategisch vorzugehen. So konnten wir weniger wichtige Mesh-Netzwerke schnell ein- und ausschalten, um eine Einstellung zu finden, die für alle Mobilgeräte funktionierte. Dann könnten wir die Geometrie zur dynamischen Optimierung zur dynamischen Optimierung in JavaScript oder in der Vorproduktion zusammenführen, um Anfragen zu speichern.

Texturen mit niedriger Auflösung verwenden

Um die Ladezeit auf Mobilgeräten zu verkürzen, haben wir uns entschieden, verschiedene Texturen zu laden, die nur halb so groß sind wie die auf Desktop-Computern. Es hat sich herausgestellt, dass alle Geräte Texturgrößen von bis zu 2048 × 2048 Pixel und die meisten auch 4096 × 4096 Pixel verarbeiten können. Die Textursuche für die einzelnen Texturen scheint nach dem Hochladen in die GPU kein Problem zu sein. Die Gesamtgröße der Texturen muss in den GPU-Speicher passen, um zu vermeiden, dass Texturen ständig hoch- und heruntergeladen werden. Dies ist für die meisten Web-Erlebnisse aber wahrscheinlich kein großes Problem. Allerdings ist es wichtig, Texturen in möglichst wenigen Sprite Sheets zu kombinieren, um die Anzahl der Zeichenaufrufe zu reduzieren. Dies hat einen großen Einfluss auf die Leistung auf Mobilgeräten.

Textur für einen der Trolle im Trollwald
Textur für einen der Trolle im Trollwald
(Originalgröße: 512 × 512 Pixel)

Materialien und Beleuchtung vereinfachen

Auch die Wahl der Materialien kann sich stark auf die Leistung auswirken und muss auf Mobilgeräten mit Bedacht eingesetzt werden. Wir haben die Leistung optimiert, indem wir MeshLambertMaterial (pro Vertex-Lichtberechnung) in third.js anstelle von MeshPhongMaterial (pro Texel-Lichtberechnung) verwendet haben. Im Grunde haben wir versucht, so einfache Shader mit so wenigen Lichtberechnungen wie möglich zu verwenden.

Um zu sehen, wie sich die verwendeten Materialien auf die Leistung einer Szene auswirken, können Sie die Materialien der Szene mit einem MeshBasicMaterial überschreiben . Dadurch erhältst du einen guten Vergleich.

scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});

JavaScript-Leistung optimieren

Bei der Entwicklung von Spielen für Mobilgeräte stellt die GPU nicht immer die größte Hürde dar. Die CPU wird viel Zeit benötigt, insbesondere für physikalische und skeletische Animationen. Ein Trick, der manchmal hilfreich ist, besteht je nach Simulation darin, diese teuren Berechnungen nur in jedem zweiten Frame durchzuführen. Sie können auch die verfügbaren JavaScript-Optimierungsmethoden für Objekt-Pooling, automatische Speicherbereinigung und Objekterstellung verwenden.

Das Aktualisieren vorab zugewiesener Objekte in Schleifen, anstatt neue Objekte zu erstellen, ist ein wichtiger Schritt, um „Schwierigkeiten bei der automatischen Speicherbereinigung“ während des Spiels zu vermeiden.

Betrachten Sie zum Beispiel Code wie den folgenden:

var currentPos = new THREE.Vector3();

function gameLoop() {
  currentPos = new THREE.Vector3(0+offsetX,100,0);
}

Eine verbesserte Version dieser Schleife vermeidet das Erstellen neuer Objekte, die durch die automatische Speicherbereinigung gelöscht werden müssen:

var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
  currentPos.copy(originPos).x += offsetX;
  //or
  currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}

Soweit möglich sollten Event-Handler nur Eigenschaften aktualisieren und die Rendering-Schleife von requestAnimationFrame für die Aktualisierung des Anzeigebereichs verwenden lassen.

Ein weiterer Tipp ist die Optimierung und/oder Vorausberechnung von Strahlengussvorgängen. Wenn Sie beispielsweise während einer Bewegung des statischen Pfads ein Objekt an ein Netz anbringen müssen, können Sie die Positionen während einer Schleife „aufzeichnen“ und dann aus diesen Daten lesen, anstatt Raycasting an das Netz zu senden. Und wie beim Rivendell-Spiel wird auch bei Raycast nach Mausinteraktionen gesucht – dank des einfachen, unsichtbaren Low-Poly-Mesh-Netzwerks. Die Suche nach Kollisionen in einem High-Poly-Mesh-Netzwerk ist sehr langsam und sollte in einer Spielschleife im Allgemeinen vermieden werden.

WebGL-Canvas in halber Größe rendern und mit CSS hochskalieren

Die Größe des WebGL-Canvas ist wahrscheinlich der effektivste Parameter, den Sie zur Leistungsoptimierung anpassen können. Je größer der Canvas, den Sie zum Zeichnen Ihrer 3D-Szene verwenden, desto mehr Pixel müssen auf jedem Frame gezeichnet werden. Dies wirkt sich natürlich auf die Leistung aus.Das Nexus 10 mit seinem hochauflösenden Display mit 2560 x 1600 Pixeln muss im Vergleich zu einem Tablet mit niedriger Dichte viermal so viele Pixel verschieben. Zur Optimierung für Mobilgeräte verwenden wir einen Trick, bei dem wir den Canvas auf die Hälfte der Größe (50%) setzen und ihn dann mit hardwarebeschleunigten CSS-3D-Transformationen auf die beabsichtigte Größe (100%) skalieren. Der Nachteil dabei ist ein verpixeltes Bild, bei dem dünne Linien zum Problem werden können. Auf einem hochauflösenden Bildschirm ist das aber nicht so schlecht. Die zusätzliche Leistung lohnt sich auf jeden Fall.

Dieselbe Szene ohne Canvas-Skalierung beim Nexus 10 (16 fps) und einer Skalierung auf 50% (33 fps)
Dieselbe Szene ohne Canvas-Skalierung auf dem Nexus 10 (16 fps) und skaliert auf 50% (33 fps).

Objekte als Bausteine

Um das große Labyrinth der Burg Dol Guldur und des endlosen Tals von Bruchtal zu erschaffen, haben wir eine Reihe von 3D-Bausteinmodellen erstellt, die wir wiederverwenden. Durch die Wiederverwendung von Objekten können wir dafür sorgen, dass die Objekte zu Beginn des Tests und nicht mitten im Spiel instanziiert und hochgeladen werden.

3D-Objektbausteine, die im Labyrinth von Dol Guldur verwendet werden.
3D-Objektbausteine, die im Labyrinth von Dol Guldur verwendet werden

In Rivendell gibt es eine Reihe von Bodenabschnitten, die wir im Verlauf der User Journey ständig in Z-Tiefe neu positionieren. Wenn der Nutzer an Abschnitten vorbeigeht, werden diese in der Ferne neu positioniert.

Für die Burg Dol Guldur sollte das Labyrinth für jedes Spiel neu gestaltet werden. Dazu haben wir ein Skript erstellt, das das Labyrinth neu generiert.

Wenn die gesamte Struktur von Anfang an zu einem großen Mesh-Netzwerk zusammengeführt wird, führt dies zu einer sehr großen Szene und schlechter Leistung. Aus diesem Grund haben wir uns entschieden, die Bausteine je nachdem, ob sie zu sehen sind, ein- und auszublenden. Von Anfang an hatten wir die Idee, ein 2D-Raycaster-Skript zu verwenden, aber am Ende haben wir das integrierte Sichtbereich-Culing von Three.js verwendet. Wir haben das Raycaster-Skript wiederverwendet, um die „Gefahr“, der der Spieler ausgesetzt ist, heranzuzoomen.

Der nächste wichtige Punkt ist die Nutzerinteraktion. Auf Desktop-Computern gibt es eine Maus- und Tastatureingabe. Auf Mobilgeräten interagieren die Nutzer z. B. durch Tippen, Wischen, Auseinander- und Zusammenziehen und Geräteausrichtung.

Interaktion mit Berührungen im mobilen Web

Touch-Unterstützung lässt sich im Handumdrehen einrichten. Es gibt großartige Artikel zu diesem Thema. Es gibt jedoch ein paar kleine Dinge, die es komplizierter machen können.

Sie können Touch-Gesten und die Maus verwenden. Das Chromebook Pixel und andere Laptops mit Touchscreen unterstützen sowohl Maus als auch Touch. Ein häufiger Fehler besteht darin, zu überprüfen, ob das Gerät Touchbedienung aktiviert ist, und dann nur Touch-Event-Listener und keinen für Maus hinzuzufügen.

Aktualisieren Sie das Rendering in Event-Listenern nicht. Speichern Sie die Touch-Ereignisse stattdessen in Variablen und reagieren Sie in der requestAnimationFrame-Renderingschleife auf sie. Dies verbessert die Leistung und es werden auch widersprüchliche Ereignisse zusammengeführt. Achten Sie darauf, dass Sie Objekte wiederverwenden, anstatt neue Objekte in den Ereignis-Listenern zu erstellen.

Denken Sie daran, dass es sich um Multitouch handelt: Bei „event.touches“ handelt es sich um eine Reihe aller Berührungen. In manchen Fällen ist es interessanter, stattdessen event.targetTouches oder event.changedTouches zu betrachten und einfach auf die Berührungen zu reagieren, die Sie interessieren. Um Tippen und Wischen voneinander zu trennen, verwenden wir eine Verzögerung, bevor wir prüfen, ob die Berührung sich bewegt (wischen) oder ob sie still steht (tippen). Auseinanderziehen Sie den Abstand zwischen den ersten Berührungen und wie sich dieser im Laufe der Zeit verändert.

In einer 3D-Umgebung müssen Sie entscheiden, wie Ihre Kamera auf Maus- bzw. Wischbewegungen reagiert. Eine gängige Methode zum Hinzufügen von Kamerabewegungen ist die Verfolgung der Mausbewegung. Dies kann entweder durch direkte Steuerung über die Mausposition oder durch eine Deltabewegung (Positionsänderung) erfolgen. Sie möchten auf Mobilgeräten nicht immer dasselbe Verhalten wie Desktop-Browser haben. Wir haben ausgiebige Tests durchgeführt, um zu entscheiden, was für jede Version passend ist.

Bei kleineren Bildschirmen und Touchscreens werden Sie feststellen, dass die Finger und die Interaktionsgrafiken der Benutzeroberfläche oft im Weg sind, was Sie zeigen möchten. Das ist etwas, das wir beim Entwerfen nativer Apps gewohnt sind, aber bei Weberlebnissen mussten wir uns bisher noch keine Gedanken darüber machen. Dies ist eine echte Herausforderung für Designschaffende und UX-Designschaffende.

Zusammenfassung

Unsere Erfahrung aus diesem Projekt ist, dass WebGL auf Mobilgeräten sehr gut funktioniert, insbesondere auf neueren High-End-Geräten. Im Hinblick auf die Leistung scheint die Anzahl und Texturgröße von Polygonen vor allem die Download- und Initialisierungszeit zu beeinflussen, und die Materialien, Shader und die Größe des WebGL-Canvas sind die wichtigsten Bestandteile der Optimierung der mobilen Leistung. Es ist jedoch die Summe der Teile, die sich auf die Leistung auswirken, sodass Sie alles tun können, um die Anzahl zu optimieren.

Die Ausrichtung auf Mobilgeräte bedeutet auch, dass Sie sich erst einmal an die Interaktionen mit Berührung gewöhnen müssen. Dabei geht es nicht nur um die Pixelgröße, sondern auch um die physische Größe des Bildschirms. In einigen Fällen mussten wir die 3D-Kamera näher rücken, um zu sehen, was vor sich geht.

Das Experiment hat begonnen und es war eine fantastische Entwicklung. Viel Spaß damit!

Möchten Sie es einmal ausprobieren? Begib dich auf deine eigene Reise nach Mittelerde.