L'expérience du Hobbit

Donner vie à la Terre du Milieu avec WebGL pour mobile

Daniel Isaksson
Daniel Isaksson

Jusqu'à présent, proposer des expériences interactives, Web et multimédias sur les mobiles et les tablettes représentait un véritable défi. Les principales contraintes sont les performances, la disponibilité de l'API, les limites de l'audio HTML5 sur les appareils et l'absence de lecture fluide des vidéos intégrées.

Plus tôt cette année, avec des amis de Google et Warner Bros., nous avons lancé un projet visant à offrir une expérience Web mobile first pour le nouveau film du Hobbit, Le Hobbit: La désolation de Smaug. Concevoir une expérience Chrome Experiment pour mobile, très riche en contenus multimédias, s'est révélée une tâche à la fois inspirante et difficile.

L'expérience est optimisée pour Chrome pour Android sur les nouveaux appareils Nexus où nous avons désormais accès à WebGL et Web Audio. Cependant, une grande partie de l'expérience est également accessible sur les appareils et navigateurs non WebGL, grâce à la composition avec accélération matérielle et aux animations CSS.

L'expérience est basée sur une carte de la Terre du Milieu, ainsi que sur les lieux et personnages des films du Hobbit. WebGL nous a permis de mettre en scène et d'explorer le monde riche de la trilogie Hobbit, et de laisser les utilisateurs contrôler l'expérience.

Défis liés à WebGL sur les appareils mobiles

Tout d'abord, le terme "appareils mobiles" est très large. Les caractéristiques techniques des appareils varient beaucoup. En tant que développeur, vous devez donc décider si vous souhaitez prendre en charge davantage d'appareils offrant une expérience moins complexe ou, comme nous l'avons fait dans le cas présent, limiter les appareils compatibles à ceux capables d'afficher un monde 3D plus réaliste. Pour "Voyage à travers la Terre du Milieu", nous nous sommes concentrés sur les appareils Nexus et sur cinq smartphones Android populaires.

Dans ce test, nous avons utilisé three.js, comme nous l'avons fait pour certains de nos projets WebGL précédents. Nous avons commencé par créer une version initiale du jeu des Trollshaws qui fonctionnerait bien sur la tablette Nexus 10. Après quelques premiers tests sur l'appareil, nous avions en tête une liste d'optimisations qui ressemblait à ce que nous utiliserions normalement pour un ordinateur portable peu performant:

  • Utiliser des modèles low poly
  • Utiliser des textures basse résolution
  • Réduire le nombre d'appels de dessin le plus possible en fusionnant la géométrie
  • Simplifiez les matériaux et l'éclairage
  • Supprimer les effets de post et désactiver l'anticrénelage
  • Optimisez les performances de JavaScript
  • Afficher le canevas WebGL à la moitié et l'agrandir avec CSS

Après avoir appliqué ces optimisations à notre première version approximative du jeu, nous avions une fréquence d'images stable de 30 FPS, qui nous convenait. Notre objectif était alors d'améliorer les visuels sans nuire à la fréquence d'images. Nous avons essayé de nombreuses astuces: certaines ont eu un réel impact sur les performances, et quelques-unes n'ont pas eu autant d'impact que nous l'espérions.

Utiliser des modèles low poly

Commençons par les modèles. L'utilisation de modèles low poly améliore considérablement le temps de téléchargement et l'initialisation de la scène. Nous avons constaté que nous pouvions augmenter considérablement la complexité sans affecter les performances. Les modèles de trolls que nous utilisons dans ce jeu sont d'environ 5 000 visages et la scène compte environ 40 000 visages, ce qui fonctionne bien.

Un des trolls de la forêt des Trollshaws
L'un des trolls de la forêt des Trollshaws

Pour un autre emplacement (pas encore disponible) dans l'expérience, nous avons constaté un impact plus important sur les performances en réduisant le nombre de polygones. Dans ce cas, nous avons chargé des objets à un polygone inférieur pour les appareils mobiles par rapport aux objets des ordinateurs de bureau. La création de différents ensembles de modèles 3D nécessite des efforts supplémentaires et n'est pas toujours nécessaire. Tout dépend de la complexité de vos modèles au départ.

Lorsque nous travaillions sur de grandes scènes comportant de nombreux objets, nous avons essayé d'être stratégique sur la façon de diviser la géométrie. Cela nous a permis d'activer et de désactiver rapidement les réseaux maillés moins importants, et de trouver un paramètre qui fonctionnait pour tous les appareils mobiles. Nous pourrions ensuite choisir de fusionner la géométrie en JavaScript au moment de l'exécution pour une optimisation dynamique, ou de la fusionner en préproduction pour enregistrer les demandes.

Utiliser des textures basse résolution

Pour réduire le temps de chargement sur les appareils mobiles, nous avons choisi de charger des textures correspondant à la moitié de la taille des textures sur ordinateur. Tous les appareils sont compatibles avec les tailles de texture allant jusqu'à 2 048 x 2 048 pixels, et la plupart des tailles de 4 096 x 4 096 pixels. La recherche de textures sur des textures individuelles ne semble pas poser problème une fois qu'elles ont été importées dans le GPU. La taille totale des textures doit tenir dans la mémoire du GPU afin d'éviter que des textures ne soient constamment mises à niveau et téléchargées. Toutefois, ce n'est probablement pas un problème majeur pour la plupart des expériences Web. Cependant, il est important de combiner les textures pour réduire au maximum les feuilles de sprites afin de réduire le nombre d'appels de dessin. Cela a un impact important sur les performances sur les appareils mobiles.

Texture de l'un des trolls de la forêt des Trollshaws
Texture de l'un des trolls de la forêt des Trollshaws
(taille d'origine 512 x 512 px)

Simplifiez les matériaux et l'éclairage

Le choix des matériaux peut également avoir un impact important sur les performances et doit être géré judicieusement sur les mobiles. Nous avons utilisé MeshLambertMaterial (par calcul de la lumière des sommets) dans three.js au lieu de MeshPhongMaterial (par calcul de la lumière texel) pour optimiser les performances. En fait, nous avons essayé d'utiliser des nuanceurs aussi simples que possible avec le moins de calculs d'éclairage possible.

Pour voir comment les matériaux que vous utilisez affectent les performances d'une scène, vous pouvez les remplacer par un MeshBasicMaterial . Cela vous permettra de faire une bonne comparaison.

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

Optimisez les performances de JavaScript

Lorsque vous créez des jeux pour mobile, le GPU n'est pas toujours le principal obstacle. Le processeur passe beaucoup de temps, en particulier la physique et les animations squelettiques. Une astuce qui aide parfois, selon la simulation, consiste à n'exécuter ces calculs coûteux que toutes les deux images. Vous pouvez également utiliser les techniques d'optimisation JavaScript disponibles pour le pooling d'objets, la récupération de mémoire et la création d'objets.

Il est important de mettre à jour les objets pré-alloués dans des boucles au lieu de créer des objets pour éviter les problèmes de récupération de mémoire pendant le jeu.

Prenons l'exemple d'un code de ce type:

var currentPos = new THREE.Vector3();

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

Une version améliorée de cette boucle évite de créer des objets qui doivent faire l'objet d'une récupération de mémoire:

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

Dans la mesure du possible, les gestionnaires d'événements ne doivent mettre à jour que les propriétés et laisser la boucle de rendu requestAnimationFrame gérer la mise à jour de l'espace de création.

Un autre conseil consiste à optimiser et/ou à précalculer les opérations de diffusion de rayons. Par exemple, si vous devez associer un objet à un maillage lors d'un mouvement de chemin statique, vous pouvez "enregistrer" les positions pendant une boucle, puis lire ces données au lieu de diffuser des rayons sur le maillage. Comme dans l'expérience Rivendell, la fonctionnalité Raycast détecte les interactions avec la souris avec un maillage invisible low-poly plus simple. La recherche de collisions sur un maillage high-poly est très lente et doit être évitée dans une boucle de jeu en général.

Afficher le canevas WebGL à la moitié et l'agrandir avec CSS

La taille du canevas WebGL est probablement le paramètre le plus efficace que vous puissiez ajuster pour optimiser les performances. Plus le canevas que vous utilisez pour dessiner votre scène en 3D est grand, plus le nombre de pixels devant être dessinés sur chaque image est important. Cela affecte, évidemment, les performances.Avec son écran haute densité de 2 560 x 1 600 pixels, la Nexus 10 doit quatre fois plus de pixels que la tablette basse densité. Pour optimiser ce résultat sur mobile, nous utilisons une astuce qui consiste à réduire la taille du canevas (50%), puis à l'ajuster à la taille prévue (100%) à l'aide de transformations 3D CSS avec accélération matérielle. L'inconvénient est une image pixélisée où les lignes fines peuvent devenir un problème, mais sur un écran haute résolution, l'effet n'est pas si mal. Les performances supplémentaires en valent la peine.

Même scène sans mise à l'échelle du canevas sur la Nexus 10 (16 FPS) et ajustée à 50% (33 FPS)
La même scène sans mise à l'échelle du canevas sur la Nexus 10 (16 FPS) et ajustée à 50% (33 FPS)

Objets servant de base

Pour créer le grand labyrinthe du château de Dol Guldur et de la vallée sans fin de Fondcombe, nous avons créé un ensemble de modèles 3D de blocs de construction que nous réutilisons. Le fait de réutiliser des objets nous permet de nous assurer qu'ils sont instanciés et importés au début de l'expérience, et non au milieu.

Blocs de construction d'objets 3D utilisés dans le labyrinthe de Dol Guldur.
Blocs de construction d'objets 3D utilisés dans le labyrinthe de Dol Guldur

Dans Rivendell, nous avons un certain nombre de sections de terrain que nous repositionnons constamment en profondeur de plan à mesure que le parcours utilisateur progresse. À mesure que l'utilisateur passe des sections, celles-ci sont repositionnées à une certaine distance.

Pour le château de Dol Guldur, nous voulions que le labyrinthe soit régénéré à chaque partie. Pour ce faire, nous avons créé un script qui régénère le labyrinthe.

La fusion de toute la structure en un seul maillage dès le départ aboutit à une scène très étendue et à de mauvaises performances. Pour résoudre ce problème, nous avons décidé de masquer et d'afficher les éléments de base selon qu'ils sont visibles ou non. Dès le départ, nous avons eu l'idée d'utiliser un script raycaster 2D, mais nous avons fini par utiliser la sélection de frustrum three.js intégrée. Nous avons réutilisé le script Raycaster pour zoomer sur le "danger" auquel le joueur est confronté.

L’autre chose importante à gérer est l’interaction utilisateur. Sur ordinateur, il s'agit de la saisie à la souris et au clavier. Sur les appareils mobiles, les utilisateurs interagissent avec l'écran tactile, le balayage, le pincement, l'orientation de l'appareil, etc.

Utiliser l'interaction tactile dans les expériences Web mobiles

L'ajout de la fonctionnalité tactile n'est pas compliqué. Vous trouverez d'excellents articles sur le sujet. Mais il y a de petites choses qui peuvent rendre les choses plus compliquées.

Vous pouvez utiliser à la fois l'écran tactile et la souris. Le Chromebook Pixel et les autres ordinateurs portables à écran tactile sont compatibles avec la souris et l'écran tactile. Une erreur courante consiste à vérifier si l'appareil est compatible avec les fonctionnalités tactiles, puis à n'ajouter que des écouteurs d'événements tactiles, et aucun d'entre eux pour la souris.

Ne modifiez pas le rendu dans les écouteurs d'événements. Enregistrez les événements tactiles dans des variables et réagissez-y dans la boucle de rendu requestAnimationFrame. Cela permet d'améliorer les performances et de fusionner les événements conflictuels. Veillez à réutiliser des objets au lieu de créer des objets dans les écouteurs d'événements.

N'oubliez pas qu'il s'agit d'un outil multipoint: event.touches englobe tous les aspects. Dans certains cas, il est plus intéressant d'examiner event.targetTouches ou event.changedTouches à la place, et simplement réagir aux gestes qui vous intéressent. Pour distinguer les gestes de balayage, nous utilisons un délai avant de vérifier si l'élément tactile a bougé (balayage) ou s'il est immobile (appui). Pour un pincement, nous mesurons la distance entre les deux premiers contacts et son évolution au fil du temps.

Dans un monde 3D, vous devez choisir la réaction de votre caméra lorsqu'elle passe par la souris ou le balayage de l'écran. Une méthode courante pour ajouter des mouvements de caméra consiste à suivre le mouvement de la souris. Il peut s'agir d'un contrôle direct à l'aide de la position de la souris ou d'un mouvement delta (changement de position). Il ne faut pas toujours que les utilisateurs se comportent de la même manière sur un appareil mobile que dans un navigateur pour ordinateur. Nous avons effectué des tests approfondis pour déterminer la solution la mieux adaptée à chaque version.

Lorsque vous travaillez sur des écrans tactiles de petite taille, vous constaterez que les doigts de l'utilisateur et les éléments graphiques d'interaction avec l'interface utilisateur vous empêchent souvent de voir ce que vous souhaitez montrer. Nous sommes habitués à ce point lors de la conception d'applications natives, mais nous n'avions pas vraiment eu à y penser auparavant avec les expériences Web. Il s'agit d'un véritable défi pour les concepteurs et les concepteurs UX.

Résumé

D'après notre expérience globale, la technologie WebGL sur mobile fonctionne très bien, en particulier sur les appareils récents et haut de gamme. En termes de performances, il semble que le nombre de polygones et la taille de texture affectent principalement les temps de téléchargement et d'initialisation, et que les matériaux, les nuanceurs et la taille du canevas WebGL sont les éléments les plus importants à optimiser pour les performances sur mobile. Cependant, c'est la somme de tous les éléments qui influe sur les performances. Toutes les actions que vous pouvez prendre pour optimiser les performances sont donc prises en compte.

Cibler les appareils mobiles signifie également que vous devez vous habituer à penser aux interactions tactiles et que ce n'est pas seulement la taille en pixels, mais aussi la taille physique de l'écran. Dans certains cas, il nous fallait rapprocher la caméra 3D pour bien voir ce qui se passait.

Le test est lancé et ce fut une expérience fantastique. Nous espérons qu'elle vous plaira.

Pour essayer, Embarquez pour votre voyage en Terre du Milieu.