L'esperienza dello Hobbit

Dare vita alla Terra di Mezzo con WebGL mobile

Daniel Isaksson
Daniel Isaksson

Storicamente, portare esperienze interattive, basate sul web e ricche di contenuti multimediali su cellulari e tablet è stata una sfida. I vincoli principali sono stati le prestazioni, la disponibilità delle API, le limitazioni dell'audio HTML5 sui dispositivi e la mancanza di una riproduzione video in linea senza interruzioni.

All'inizio di quest'anno abbiamo avviato un progetto con gli amici di Google e Warner Bros. per realizzare un'esperienza web mobile-first per il nuovo film Lo Hobbit, The Hobbit: The Desolation of Smaug. Creare un esperimento Chrome per dispositivi mobili ricco di contenuti multimediali è stato davvero stimolante e impegnativo.

L'esperienza è ottimizzata per Chrome per Android sui nuovi dispositivi Nexus, a cui ora abbiamo accesso a WebGL e Web Audio. Tuttavia, una gran parte dell'esperienza è accessibile anche su dispositivi e browser non WebGL grazie alla compositing con accelerazione hardware e alle animazioni CSS.

L'intera esperienza si basa su una mappa della Terra di Mezzo, oltre ai luoghi e ai personaggi dei film dello Hobbit. L'utilizzo di WebGL ci ha consentito di rappresentare ed esplorare il ricco mondo della trilogia di Hobbit, consentendo agli utenti di controllare l'esperienza.

Le sfide di WebGL sui dispositivi mobili

Innanzitutto, il termine "dispositivi mobili" è molto generico. Le specifiche dei dispositivi variano molto. Quindi, in qualità di sviluppatore, devi decidere se vuoi supportare più dispositivi con un'esperienza meno complessa o, come abbiamo fatto in questo caso, limitare i dispositivi supportati a quelli in grado di visualizzare un mondo 3D più realistico. Per "Viaggio nella Terra di Mezzo" ci siamo concentrati sui dispositivi Nexus e su cinque tra i più diffusi smartphone Android.

Nell'esperimento, abbiamo utilizzato three.js, come abbiamo fatto per alcuni dei nostri precedenti progetti WebGL. Abbiamo iniziato l'implementazione creando una versione iniziale del gioco Trollshaw che avrebbe funzionato bene sul tablet Nexus 10. Dopo alcuni test iniziali sul dispositivo, abbiamo pensato a un elenco di ottimizzazioni molto simili a quelle che avremmo utilizzato normalmente su un laptop con prestazioni inferiori a una certa soglia:

  • Utilizza modelli low poly
  • Utilizza texture a bassa risoluzione
  • Riduci il più possibile il numero di chiamate di disegno unendo la geometria
  • Semplificare i materiali e l'illuminazione
  • Rimuovi gli effetti dei post e disattiva l'antialiasing
  • Ottimizza il rendimento di JavaScript
  • Visualizza la tela WebGL dimezzata e scala con CSS

Dopo aver applicato queste ottimizzazioni alla nostra prima versione approssimativa del gioco, avevamo una frequenza fotogrammi stabile di 30 f/s che eravamo soddisfatti. A quel punto il nostro obiettivo era migliorare le immagini senza influire negativamente sulla frequenza fotogrammi. Abbiamo provato molti trucchi: alcuni si sono rivelati efficaci sulle prestazioni; altri non hanno avuto l'effetto che ci aspettavamo.

Utilizza modelli low poly

Iniziamo con i modelli. L'uso di modelli low poly facilita di sicuro il tempo di download e il tempo necessario per inizializzare la scena. Abbiamo scoperto che potevamo aumentare notevolmente la complessità senza influire molto sulle prestazioni. I modelli di troll che utilizziamo in questo gioco includono circa 5000 volti e la scena include circa 40.000 volti, quindi funziona bene.

Uno dei troll della foresta dei Trollshaw
Uno dei troll della foresta dei Trolls

Per un altro luogo (non ancora rilasciato) nell'esperienza abbiamo osservato un impatto maggiore sulle prestazioni dovuto alla riduzione dei poligoni. In tal caso, abbiamo caricato oggetti con poligoni inferiori per i dispositivi mobili rispetto a oggetti caricati per desktop. La creazione di diversi insiemi di modelli 3D richiede un po' di lavoro aggiuntivo e non è sempre obbligatorio. Dipende davvero da quanto sono complessi inizialmente i modelli.

Quando lavoravamo su scene di grandi dimensioni con molti oggetti, cercavamo di adottare misure strategiche su come dividere la geometria. Questo ci ha permesso di attivare e disattivare rapidamente mesh meno importanti, per trovare un'impostazione che funzionasse per tutti i dispositivi mobili. Poi, potremmo scegliere di unire la geometria in JavaScript in fase di runtime per l'ottimizzazione dinamica, oppure di unirla in pre-produzione per salvare le richieste.

Utilizza texture a bassa risoluzione

Per ridurre il tempo di caricamento sui dispositivi mobili, abbiamo scelto di caricare diverse texture che erano la metà delle dimensioni delle texture su computer. Abbiamo scoperto che tutti i dispositivi sono in grado di supportare dimensioni della texture fino a 2048 x 2048 px e la maggior parte può gestire anche 4096 x 4096 px. La ricerca delle texture per le singole texture non sembra essere un problema una volta caricate sulla GPU. Le dimensioni totali delle texture devono rientrare nella memoria GPU per evitare che le texture vengano costantemente aggiornate e scaricate, ma questo probabilmente non è un grosso problema per la maggior parte delle esperienze web. Tuttavia, combinare le texture con il minor numero possibile di fogli sprite è importante per ridurre il numero di richiami; si tratta di un aspetto che ha un grande impatto sulle prestazioni sui dispositivi mobili.

Texture di uno dei troll della foresta dei Trollshaw
Texture di uno dei troll della foresta dei Trollshaw
(dimensioni originali 512 x 512 px)

Semplificare i materiali e l'illuminazione

La scelta dei materiali può anche influire notevolmente sulle prestazioni e deve essere gestita in modo appropriato sui dispositivi mobili. Per ottimizzare il rendimento, abbiamo utilizzato MeshLambertMaterial (calcolo per vertice light) in tre.js anziché MeshPhongMaterial (calcolo texel light). In pratica, abbiamo cercato di usare i soliti ombreggiatori con il minor numero possibile di calcoli relativi all'illuminazione.

Per capire in che modo i materiali che utilizzi influiscono sulle prestazioni di una scena, puoi sostituire i materiali della scena con un MeshBasicMaterial . Questo ti consentirà di fare un buon confronto.

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

Ottimizza il rendimento di JavaScript

Quando crei giochi per dispositivi mobili, la GPU non è sempre l'ostacolo maggiore. Molto tempo viene dedicato alla CPU, in particolare alla fisica e alle animazioni scheletriche. Un trucco che a volte può essere d'aiuto, a seconda della simulazione, è l'esecuzione di questi calcoli costosi solo a intervalli regolari. Puoi anche utilizzare le tecniche di ottimizzazione JavaScript disponibili per il pool di oggetti, la garbage collection e la creazione di oggetti.

Aggiornare gli oggetti preallocati in loop anziché crearne di nuovi è un passaggio importante per evitare "singhiozzi" di garbage collection durante il gioco.

Ad esempio, considera un codice simile al seguente:

var currentPos = new THREE.Vector3();

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

Una versione migliorata di questo loop evita di creare nuovi oggetti che devono essere garbage collection:

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);
}

Per quanto possibile, i gestori di eventi devono soltanto aggiornare le proprietà e lasciare che il loop di rendering requestAnimationFrame gestisca l'aggiornamento dello stage.

Un altro suggerimento è ottimizzare e/o precalcolare le operazioni di ray-casting. Ad esempio, se devi collegare un oggetto a una mesh durante un movimento statico del percorso, puoi "registrare" le posizioni durante un loop e poi leggere questi dati invece di eseguire il ray-casting sul mesh. Oppure, come facciamo nell'esperienza di Gran Burrone, ray-cast per cercare interazioni con il mouse con un mesh invisibile low poly più semplice. La ricerca delle collisioni su un mesh ad alto numero di poli è molto lenta e in generale dovrebbe essere evitata in un loop di gioco.

Visualizza la tela WebGL dimezzata e scala con CSS

Le dimensioni della tela WebGL sono probabilmente il parametro più efficace che puoi modificare per ottimizzare le prestazioni. Più grande è la tela che utilizzi per disegnare la scena 3D, maggiore sarà il numero di pixel da disegnare su ogni fotogramma. Questo ovviamente influisce sulle prestazioni.Il Nexus 10, con il suo display ad alta densità da 2560x1600 pixel, deve spingere quattro volte il numero di pixel rispetto a un tablet a bassa densità. Per ottimizzare questo aspetto per i dispositivi mobili, utilizziamo un trucchetto: impostiamo la tela alla metà delle dimensioni (50%) e poi la ridimensioniamo fino alle dimensioni previste (100%) con trasformazioni 3D CSS con accelerazione hardware. Lo svantaggio è un'immagine pixelata in cui le linee sottili possono diventare un problema, ma su uno schermo ad alta risoluzione l'effetto non è poi così male. Vale assolutamente il rendimento extra.

La stessa scena senza il ridimensionamento del canvas sul Nexus 10 (16 f/s) e scalata al 50% (33 f/s)
La stessa scena senza il ridimensionamento del canvas sul Nexus 10 (16 f/s) e scalata al 50% (33 f/s).

Oggetti come componenti di base

Per poter creare il grande labirinto del castello di Dol Guldur e l'infinita valle di Gran Burrone, abbiamo creato un set di modelli 3D di base che abbiamo riutilizzato. Il riutilizzo degli oggetti ci consente di garantire che gli oggetti vengano creati e caricati all'inizio dell'esperienza, non nel mezzo.

Componenti di base di oggetti 3D utilizzati nel labirinto di Dol Guldur.
Componenti di base di oggetti 3D utilizzati nel labirinto di Dol Guldur.

In Gran Bretagna abbiamo una serie di sezioni di terra che riposizionaamo costantemente in Z in profondità man mano che il percorso dell'utente progredisce. Man mano che l'utente supera le sezioni, queste vengono riposizionate in lontananza.

Per il castello di Dol Guldur volevamo che il labirinto venisse rigenerato per ogni gioco. Per farlo, abbiamo creato uno script che rigenera il labirinto.

La fusione dell'intera struttura in un unico grande mesh fin dall'inizio comporta una scena molto grande e prestazioni scadenti. Per risolvere il problema, abbiamo deciso di nascondere e mostrare i componenti di base a seconda che siano visibili o meno. Fin dall'inizio, ci siamo resi conto di usare uno script raycaster 2D, ma alla fine abbiamo usato il culling tre.js frustrum integrato. Abbiamo riutilizzato lo script raycaster per aumentare lo zoom sul "pericolo" affrontato dal player.

L'altro aspetto chiave da gestire è l'interazione dell'utente. Sui computer è disponibile l'input di mouse e tastiera; sui dispositivi mobili gli utenti interagiscono con tocco, scorrimento, pizzicatura, orientamento del dispositivo e così via.

Utilizzo dell'interazione touch nelle esperienze web mobile

Aggiungere il supporto touch non è difficile. Puoi trovare ottimi articoli sull'argomento. Tuttavia, esistono alcuni piccoli fattori che possono complicarlo.

Puoi avere sia il tocco sia il mouse. Il Chromebook Pixel e altri laptop abilitati al tocco offrono il supporto del mouse e del tocco. Un errore comune consiste nel controllare se il dispositivo è abilitato al tocco e nell'aggiungere solo listener di eventi touch e nessuno per il mouse.

Non aggiornare il rendering nei listener di eventi. Salva gli eventi touch nelle variabili e reagisci agli eventi nel loop di rendering requestAnimationFrame. Questo migliora le prestazioni e consente anche di unire gli eventi in conflitto. Assicurati di riutilizzare gli oggetti anziché crearne di nuovi nei listener di eventi.

Ricorda che si tratta di un multi-touch: event.touches è un array di tutti i tocchi. In alcuni casi è più interessante esaminare event.targetTouches o event.changedTouches, per poi reagire semplicemente ai tocchi che ti interessano. Per separare i tocchi dagli scorrimenti, utilizziamo un ritardo prima di controllare se il tocco si è spostato (scorrimento) o se è fermo (tocco). Per pizzicare, misuriamo la distanza tra i due tocchi iniziali e come cambia nel tempo.

In un mondo 3D devi decidere in che modo la videocamera reagisce al mouse e alle azioni di scorrimento. Un modo comune per aggiungere movimento della videocamera è seguire il movimento del mouse. A tal fine, puoi usare il controllo diretto della posizione del mouse o un movimento delta (cambio di posizione). Non è sempre consigliabile avere lo stesso comportamento su un dispositivo mobile e su un browser desktop. Abbiamo eseguito test approfonditi per decidere quale fosse la soluzione più adatta a ogni versione.

In caso di schermi e touchscreen più piccoli, scoprirai che le dita dell'utente e la grafica di interazione con l'interfaccia utente spesso non corrispondono a ciò che vuoi mostrare. Questo è un aspetto a cui siamo abituati quando progettiamo app native, ma a cui non abbiamo pensato prima con le esperienze web. Si tratta di una vera sfida per i designer e i designer UX.

Riepilogo

L'esperienza complessiva di questo progetto è che WebGL sui dispositivi mobili funziona molto bene, in particolare sui dispositivi di fascia alta più recenti. Dal punto di vista delle prestazioni, sembra che il numero dei poligoni e le dimensioni delle texture influiscano principalmente sui tempi di download e inizializzazione. Inoltre, i materiali, gli ombrer e le dimensioni della tela WebGL sono gli aspetti più importanti da ottimizzare per le prestazioni su dispositivi mobili. Tuttavia, è la somma delle parti che influiscono sul rendimento e quindi tutto ciò che puoi fare per ottimizzare i conteggi.

Scegliere come target i dispositivi mobili significa anche che devi abituarti a pensare alle interazioni tattili. Il problema non riguarda solo le dimensioni dei pixel, ma anche le dimensioni fisiche dello schermo. In alcuni casi abbiamo dovuto avvicinare la videocamera 3D per vedere effettivamente cosa stava accadendo.

L'esperimento è stato avviato ed è stato un viaggio fantastico. Ci auguriamo che ti piaccia.

Vuoi provare? Inizia il tuo viaggio verso la Terra di Mezzo.