Dare vita alla Terra di Mezzo con WebGL mobile
In passato, offrire esperienze interattive, basate sul web e con un'elevata componente multimediale su dispositivi mobili e tablet è stata una sfida. I principali vincoli sono stati le prestazioni, la disponibilità dell'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 iniziato un progetto con alcuni amici di Google e Warner Bros. per creare un'esperienza web mobile-first per il nuovo film Lo Hobbit, Lo Hobbit: La desolazione di Smaug. Realizzare un esperimento di Chrome mobile con contenuti multimediali è stata un'impresa davvero stimolante e impegnativa.
L'esperienza è ottimizzata per Chrome per Android sui nuovi dispositivi Nexus, dove ora abbiamo accesso a WebGL e Web Audio. Tuttavia, una parte significativa dell'esperienza è accessibile anche su dispositivi e browser non WebGL grazie al compositing con accelerazione hardware e alle animazioni CSS.
L'intera esperienza si basa su una mappa della Terra di Mezzo e sui luoghi e sui personaggi dei film Lo Hobbit. L'utilizzo di WebGL ci ha permesso di mettere in scena ed esplorare il ricco mondo della trilogia de Lo Hobbit e di lasciare che gli utenti controllassero l'esperienza.
Problemi di WebGL sui dispositivi mobili
Innanzitutto, il termine "dispositivi mobili" è molto ampio. Le specifiche dei dispositivi variano molto. Pertanto, in qualità di sviluppatore, devi decidere se supportare più dispositivi con un'esperienza meno complessa o, come abbiamo fatto in questo caso, limitare i dispositivi supportati a quelli in grado di mostrare un mondo 3D più realistico. Per "Journey through Middle-earth" ci siamo concentrati sui dispositivi Nexus e su cinque smartphone Android molto diffusi.
Nell'esperimento abbiamo utilizzato three.js, come abbiamo fatto per alcuni dei nostri progetti WebGL precedenti. Abbiamo iniziato l'implementazione creando una versione iniziale del gioco Trollshaw che funzionasse bene sul tablet Nexus 10. Dopo alcuni test iniziali sul dispositivo, avevamo in mente un elenco di ottimizzazioni molto simile a quello che useremmo normalmente per un laptop con specifiche ridotte:
- Utilizza modelli low-poly
- Utilizza texture a bassa risoluzione
- Riduci il numero di drawcall il più possibile fondendo la geometria
- Semplifica i materiali e l'illuminazione
- Rimuovere gli effetti di post-produzione e disattivare l'antialiasing
- Ottimizza le prestazioni di JavaScript
- Esegui il rendering della tela WebGL a metà delle dimensioni e aumenta la scala con CSS
Dopo aver applicato queste ottimizzazioni alla nostra prima versione approssimativa del gioco, abbiamo ottenuto una frequenza fotogrammi costante di 30 FPS che ci soddisfaceva. A quel punto, il nostro obiettivo era migliorare le immagini senza influire negativamente sulla frequenza fotogrammi. Abbiamo provato molti trucchi: alcuni hanno avuto un impatto significativo sul rendimento, altri non hanno avuto l'effetto sperato.
Utilizza modelli low-poly
Iniziamo dai modelli. L'utilizzo di modelli low-poly riduce sicuramente i tempi di download e di inizializzazione della scena. Abbiamo scoperto che potevamo aumentare notevolmente la complessità senza influire molto sul rendimento. I modelli di troll che utilizziamo in questo gioco sono composti da circa 5000 volti e la scena da circa 40.000 volti, il che funziona bene.

Per un'altra località (non ancora rilasciata) dell'esperienza, abbiamo riscontrato un impatto maggiore sul rendimento dalla riduzione dei poligoni. In questo caso, abbiamo caricato oggetti con meno poligoni per i dispositivi mobili rispetto a quelli caricati per i computer. La creazione di insiemi diversi di modelli 3D richiede un po' di lavoro extra e non è sempre necessaria. Dipende molto dalla complessità dei modelli.
Quando lavoriamo su scene di grandi dimensioni con molti oggetti, cerchiamo di adottare una strategia per suddividere la geometria. In questo modo abbiamo potuto attivare e disattivare rapidamente le maglie 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 esecuzione per l'ottimizzazione dinamica o di unire la geometria in pre-produzione per risparmiare richieste.
Utilizza texture a bassa risoluzione
Per ridurre il tempo di caricamento sui dispositivi mobili, abbiamo scelto di caricare texture diverse, che erano la metà delle dimensioni delle texture su computer. Sembra che tutti i dispositivi possano gestire dimensioni delle texture fino a 2048 x 2048 px e la maggior parte può gestire 4096 x 4096 px. La ricerca di texture sulle singole texture non sembra essere un problema una volta caricate sulla GPU. Le dimensioni totali delle texture devono essere contenute nella memoria della GPU per evitare che le texture vengano caricate e scaricate costantemente, ma probabilmente questo non è un grosso problema per la maggior parte delle esperienze web. Tuttavia, è importante combinare le texture nel minor numero possibile di spritesheet per ridurre il numero di chiamate di disegno, un aspetto che ha un grande impatto sulle prestazioni sui dispositivi mobili.

(dimensioni originali 512 x 512 px)
Semplifica i materiali e l'illuminazione
Anche la scelta dei materiali può influire notevolmente sulle prestazioni e deve essere gestita con saggezza sui dispositivi mobili. L'utilizzo di MeshLambertMaterial
(per il calcolo della luce per vertice) in three.js anziché MeshPhongMaterial
(per il calcolo della luce per texel) è una delle tecniche che abbiamo utilizzato per ottimizzare le prestazioni. In sostanza, abbiamo cercato di utilizzare shader il più semplici possibile con il minor numero di calcoli di illuminazione.
Per vedere in che modo i materiali che utilizzi influiscono sul rendimento di una scena, puoi sostituire i materiali della scena con un MeshBasicMaterial
. In questo modo avrai un buon confronto.
scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});
Ottimizzare le prestazioni di JavaScript
Quando crei giochi per dispositivi mobili, la GPU non è sempre l'ostacolo più grande. Viene impiegato molto tempo sulla CPU, in particolare per la fisica e le animazioni scheletriche. A volte, a seconda della simulazione, può essere utile eseguire questi calcoli costosi solo ogni secondo frame. Puoi anche utilizzare le tecniche di ottimizzazione di JavaScript disponibili per quanto riguarda il pooling di oggetti, la garbage collection e la creazione di oggetti.
L'aggiornamento degli oggetti preallocati nei loop anziché la creazione di nuovi oggetti è un passaggio importante per evitare "intoppi" durante la raccolta dei rifiuti durante il gioco.
Ad esempio, considera il seguente codice:
var currentPos = new THREE.Vector3();
function gameLoop() {
currentPos = new THREE.Vector3(0+offsetX,100,0);
}
Una versione migliorata di questo ciclo evita la creazione di nuovi oggetti che devono essere sottoposti a 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 degli eventi devono aggiornare solo le proprietà e lasciare che sia il ciclo di rendering requestAnimationFrame
a occuparsi dell'aggiornamento della fase.
Un altro suggerimento è ottimizzare e/o precalcolare le operazioni di ray-casting. Ad esempio, se devi attaccare un oggetto a una mesh durante un movimento lungo un percorso statico, puoi "registrare" le posizioni durante un ciclo e poi leggere questi dati anziché eseguire il ray-casting sulla mesh. In alternativa, come facciamo nell'esperienza Rivendell, puoi utilizzare il ray-casting per cercare le interazioni del mouse con una mesh invisibile più semplice e a basso numero di poligoni. La ricerca di collisioni su un mesh ad alta polifonia è molto lenta e deve essere evitata in un ciclo di gioco in generale.
Esegui il rendering della tela WebGL a metà delle dimensioni e aumenta la scala con CSS
Le dimensioni della tela WebGL sono probabilmente il singolo parametro più efficace che puoi modificare per ottimizzare le prestazioni. Maggiore è la tela che utilizzi per disegnare la scena 3D, più pixel devono essere disegnati in ogni frame. Questo, ovviamente, influisce sulle prestazioni.Nexus 10, con il suo display ad alta densità di 2560 x 1600 pixel, deve gestire quattro volte il numero di pixel di un tablet a bassa densità. Per ottimizzare questo aspetto per i dispositivi mobili, utilizziamo un trucco che prevede l'impostazione della metà delle dimensioni (50%) della tela e poi la scalatura fino alle dimensioni previste (100%) con le trasformazioni 3D CSS con accelerazione hardware. Lo svantaggio è che l'immagine è pixellata e le linee sottili possono diventare un problema, ma su uno schermo ad alta risoluzione l'effetto non è così negativo. Vale assolutamente la pena di ottenere un rendimento migliore.

Oggetti come componenti di base
Per poter creare il grande labirinto del castello di Dol Guldur e la valle infinita di Rivendell, abbiamo realizzato una serie di modelli 3D di elementi di base che riutilizziamo. Il riutilizzo degli oggetti ci consente di garantire che vengano istantaneamente creati e caricati all'inizio dell'esperienza e non nel mezzo.

In Rivendell abbiamo una serie di sezioni del suolo che riposizioniamo costantemente in profondità Z man mano che il percorso dell'utente procede. Quando l'utente supera le sezioni, queste vengono riposizionate in lontananza.
Per il castello di Dol Guldur volevamo che il labirinto venisse rigenerato per ogni partita. Per farlo, abbiamo creato uno script che rigenera il labirinto.
Se unisci l'intera struttura in un unico grande mesh fin dall'inizio, la scena sarà molto grande e il rendimento sarà scarso. Per risolvere il problema, abbiamo deciso di nascondere e mostrare i componenti di base a seconda che siano visibili o meno. Fin dall'inizio, avevamo in mente di utilizzare uno script raycaster 2D, ma alla fine abbiamo utilizzato il culling frustrum di three.js integrato. Abbiamo riutilizzato lo script raycaster per aumentare lo zoom sul "pericolo" che il giocatore sta affrontando.
L'aspetto successivo da gestire è l'interazione dell'utente. Sul computer hai input da mouse e tastiera; sui dispositivi mobili, gli utenti interagiscono con tocco, scorrimento, pizzico, orientamento del dispositivo e così via.
Utilizzo dell'interazione tocco nelle esperienze web mobile
Aggiungere il supporto tocco non è difficile. Esistono ottimi articoli sull'argomento. Tuttavia, ci sono alcuni piccoli aspetti che possono complicare la situazione.
Puoi usare entrambi il tocco e il mouse. Chromebook Pixel e altri laptop con tocco supportano sia il mouse sia il tocco. Un errore comune è verificare se il dispositivo è abilitato al tocco e poi aggiungere solo ascoltatori di eventi tocco e nessuno per il mouse.
Non aggiornare il rendering nei listener di eventi. Salva invece gli eventi tocco in variabili e rispondi a questi eventi nel loop di rendering requestAnimationFrame. In questo modo, le prestazioni vengono migliorate e vengono raggruppati anche gli eventi in conflitto. Assicurati di riutilizzare gli oggetti anziché crearne di nuovi nei listener di eventi.
Ricorda che è multitouch: event.touches è un array di tutti i tocchi. In alcuni casi è più interessante esaminare event.targetTouches o event.changedTouches e reagire solo ai tocchi che ti interessano. Per distinguere i tocchi dagli scorrimenti, utilizziamo un ritardo prima di verificare se il tocco si è spostato (scorrimento) o se è fermo (tocco). Per ottenere un pizzico, misuriamo la distanza tra i due tocchi iniziali e come cambia nel tempo.
In un mondo 3D devi decidere come la videocamera reagisce alle azioni del mouse rispetto a quelle di scorrimento. Un modo comune per aggiungere il movimento della fotocamera è seguire il movimento del mouse. Questa operazione può essere eseguita con il controllo diretto utilizzando la posizione del mouse o con un movimento delta (modifica della posizione). Non è sempre opportuno avere lo stesso comportamento su un dispositivo mobile come su un browser desktop. Abbiamo effettuato test approfonditi per decidere cosa fosse giusto per ogni versione.
Quando si utilizzano schermi e touchscreen più piccoli, spesso le dita dell'utente e le immagini di interazione con l'interfaccia utente ostacolano ciò che vuoi mostrare. È qualcosa a cui siamo abituati quando progettiamo app native, ma che non abbiamo mai dovuto prendere in considerazione prima con le esperienze web. Si tratta di una vera sfida per i designer e gli UX designer.
Riepilogo
La nostra esperienza complessiva con questo progetto è che WebGL su dispositivi mobili funziona molto bene, in particolare sui dispositivi più recenti e di fascia alta. Per quanto riguarda le prestazioni, sembra che il numero di poligoni e le dimensioni delle texture influiscano principalmente sui tempi di download e di inizializzazione e che i materiali, gli shader e le dimensioni della tela WebGL siano gli elementi più importanti da ottimizzare per le prestazioni su dispositivi mobili. Tuttavia, è la somma delle parti che influisce sul rendimento, quindi tutto ciò che puoi fare per ottimizzare conta.
Scegliere come target i dispositivi mobili significa anche dover abituarsi a pensare alle interazioni touch e non solo alle dimensioni in pixel, ma anche alle dimensioni fisiche dello schermo. In alcuni casi abbiamo dovuto avvicinare la fotocamera 3D per vedere cosa stava succedendo.
L'esperimento è stato lanciato ed è stato un viaggio fantastico. Mi auguro che ti piaccia.
Vuoi provare? Fai il tuo viaggio nella Terra di Mezzo.