Mise à jour : nous avons aussi réalisé une version WebGL !
The Sub® est à la fois un réfrigérateur à bière et un tireur à bière conçu par Heineken en partenariat avec Krups. Le concept est similaire à celui des machines à café modernes : cet appareil vous permet d’insérer une « cartouche » de bière, de la garder au frais, et de vous servir un verre à la maison comme un pro. Visitez ce site pour plus d’informations.
Au moment où j’écris ces lignes, un popup store baptisé The Sub®Store expose cet appareil sous toutes ses formes et couleurs à Paris, et fermera ses portes le 20 décembre.
Pour cet événement, on nous a demandé de développer l’une des applications destinées à mettre en valeur l’appareil dans le magasin. Notre application présentait certains aspects techniques de l’appareil, son fonctionnement interne, comme la façon dont la bière sort, à quelle température, et comment elle est maintenue au frais.
Nous avons travaillé pour Megalo, qui a conçu l’UI, l’UX et tous les assets 2D de l’application. Nous avons également travaillé avec GHiOM, qui a créé les modèles 3D utilisés dans cette application.
La vidéo suivante montre l’application en action.
The Sub by Heineken par Da Viking Code sur Vimeo.
Ce dessin technique 3D manipulable a été incroyable à créer. À l’intérieur de ce modèle 3D, certains points « hotspot » cliquables déclenchent des événements où la caméra zoome sur une position précise pour montrer plus de détails sur un processus ou un élément donné, en affichant du texte descriptif ou des animations.
Cela devait tourner sur un seul type d’appareil, un iPad Air 2, et a été créé avec Unity.
Wireframe et rayons X
Parlons de la 3D. Nous devions travailler avec un modèle 3D complet de l’extérieur et de l’intérieur de l’appareil, heureusement optimisé mais toujours de haute qualité. Les maquettes étaient principalement des fichiers vectoriels rastérisés, très proches de dessins techniques ou de plans, et nous devions nous en approcher le plus possible. Mais ensuite, pour mettre en avant certains objets, nous devions appliquer une sorte d’effet sur eux, et le terme utilisé était « rayons X » ; « wireframe » était le terme employé pour expliquer comment l’appareil entier devait apparaître à l’écran.
Pourtant, « wireframe » est un terme très trompeur, car cela signifie normalement afficher chaque sommet d’un objet, alors que la maquette montrait quelque chose de plus proche d’un appareil dessiné à la main, où lignes et courbes n’apparaissaient que lorsque nécessaire. Par là, j’entends quelque chose de plus proche d’un dessin façon manga, si vous voulez. Pour résumer, les lignes et courbes affichées à l’écran devaient dépendre du point de vue sur l’objet. C’est tout. Pas de wireframe… Juste un effet de contour dépendant de la position et de l’angle de vue de la caméra.
Avec ça en tête, nous avons sorti ShaderForge pour la première fois, et ce fut un outil incroyable. D’abord et avant tout, l’effet de contour est présent par défaut ! Il n’y avait donc rien à craindre de ce côté-là. Cependant, certaines courbes des dessins techniques n’étaient pas des contours et étaient dessinées explicitement pour montrer des « lignes » fines et creuses qui faisaient le tour de l’appareil. Pour certaines lignes qui devaient être présentes, nous ne nous sommes pas appuyés sur l’effet de contour, mais plutôt sur de véritables objets polygonaux de couleur unie pour créer des lignes. Cela a parfaitement fonctionné et nous a donné le résultat idéal, entre le contour et le wireframe complet. En fait, appelons ça du contour et du wireframe sélectif.

Il existe peut-être un shader pour ça. Mais dans ce cas, il était simplement plus facile, avec un résultat tout aussi joli, de créer de « vraies » lignes par-dessus les objets.
Il y a ensuite cet effet de rayons X pour certains objets mis en avant. Lui aussi dépendait de l’angle de la caméra. Nous avons simplement utilisé un shader de Fresnel.
Il y a pourtant bien plus de types de shaders qu’on ne pourrait l’imaginer ici… Il existe un shader qui pourrait être utilisé pour tous les matériaux de la scène (je ne l’ai finalement pas fait, mais je sais que c’est possible). Cela aide évidemment pour le rendu, mais quand un hotspot était cliqué, il fallait passer en « vue objet unique », disons. Et cela signifie faire disparaître en fondu tous les autres objets.
Pour y parvenir, les propriétés d’un matériau sont simplement interpolées (avec easing) vers les propriétés d’un autre matériau (tant que les noms des propriétés sont identiques).
Unity propose en fait un moyen simple d’interpoler des matériaux avec Material.Lerp. Mais nous avions créé notre propre système d’interpolation avant que les shaders ne soient progressivement fusionnés.
Je ne peux donc pas dire ce que fait exactement Material.Lerp ; il devrait offrir de meilleures performances que notre solution, mais la nôtre fonctionnait très bien.
Une chose à noter cependant. Pour interpoler d’une couleur à une autre sur le matériau, il y avait un problème : au lieu de passer d’un vert foncé à un vert plus clair, la couleur passait par un vert encore plus sombre avant d’atteindre le vert clair (comme si on utilisait un dégradé à 3 « keyframes ») - ce qui n’est absolument pas ce à quoi on s’attend, car l’objet Gradient d’Unity, lui, n’avait pas ce problème. Pour l’interpolation de couleurs, nous avons donc utilisé la fonction suivante, trouvée sur un forum de questions-réponses (merci !) :
public static Color ColorLerp(Color a, Color b, float t = 0.5f)
{
float aR = a.r;
float aG = a.g;
float aB = a.b;
float aA = a.a;
float bR = b.r;
float bG = b.g;
float bB = b.b;
float bA = b.a;
float cR = Mathf.Sqrt(aR * aR * (1.0f - t) + bR * bR * t);
float cG = Mathf.Sqrt(aG * aG * (1.0f - t) + bG * bG * t);
float cB = Mathf.Sqrt(aB * aB * (1.0f - t) + bB * bB * t);
float cA = Mathf.Sqrt(aA * aA * (1.0f - t) + bA * bA * t);
return new Color(cR, cG, cB, cA);
}
Remplir un tube
Il y a eu un défi vraiment intéressant, mais résolu en moins d’une demi-heure. Un liquide virtuel devait remplir un tube 3D (un cylindre torsadé), visible à 0:35 dans la vidéo, et on peut apercevoir un peu du shader concerné dans la capture d’écran de ShaderForge ci-dessus. En gros, une texture en dégradé était mappée sur le tube de façon à commencer en blanc au début du tube et en noir à la fin. À partir de là, avec l’aide de l’éditeur visuel de ShaderForge, il a été très rapide de construire la logique que je savais fonctionnelle en code, mais que je ne voulais pas m’embêter à écrire en shader à ce moment-là… avoir une valeur de seuil animée (tweened) entre 0 et 1 ; tout ce qui est au-dessus de cette valeur, comparé à la valeur d’un seul canal de cette texture, prend une certaine couleur, et tout ce qui est en dessous prend une couleur différente. Faire varier linéairement la valeur de seuil fait simplement monter une couleur de façon linéaire elle aussi sur cet objet, puisqu’il y a un dégradé.
Cette idée simple et rapide m’est venue parce que j’avais commencé plus tôt à écrire du code de shader à la main et essayé cet effet de « dissolution », où au lieu d’avoir deux couleurs on a la texture ou la transparence, et au lieu d’un dégradé mappé sur l’objet, on a par exemple du bruit de Perlin. Je suis certain qu’avec des concepts aussi simples on peut obtenir de très beaux effets, et ShaderForge a permis d’y arriver dans une situation où l’idée était là, mais pas le temps de la coder.
Mouvement de caméra, gimbal lock vs orbite de caméra sans gimbal lock
Petit rappel : l’objet ne bouge pas, c’est la caméra qui bouge. Si vous faites un jour un projet similaire, le choix vous appartient après tout, tout ce qui change est le point de vue relatif - je suis sûr que cette application aurait pu être réalisée avec une caméra statique. Mais j’ai choisi une caméra en orbite. De plus, la caméra devait entrer et sortir de certaines positions/rotations spécifiques dans le monde (les vues d’objet unique après avoir cliqué sur un hotspot). Ces mouvements sont construits à la volée avec des courbes de Bézier… Il est donc possible de parfois traverser l’objet pendant le mouvement, même si cela n’affecte pas l’expérience.
Quoi qu’il en soit, faire orbiter la caméra autour de l’objet était je pense la plus grosse partie de l’application, en tout cas celle qui a été le plus retravaillée. Ça a commencé avec un scénario simple d’orbite de caméra, où la position du toucher affectait deux axes de rotation. Il n’y avait pas de mouvement de zoom avant/arrière à l’époque. Donc en gros, 2 axes de contrôle pour la rotation et pas de « tilt »… pour éviter le gimbal lock ici, puisque j’utilisais des angles d’Euler suivis comme des propriétés séparées des Quaternions, eh bien on limitait la « latitude » possible de la rotation (je voyais ça comme déplacer la caméra sur un globe, je me disais X,Y pour le toucher, donc Longitude/Latitude pour les positions sur la sphère).
Cela a fonctionné un moment. Mais on nous a demandé de pouvoir tourner autour de l’objet sous n’importe quel angle. Cela impliquait bien sûr une « orbite de caméra » sans gimbal lock, mais aussi un contrôle tactile similaire à une trackball, affectant un nouvel axe de rotation de la caméra. Donc plus d’angles d’Euler, uniquement des Quaternions partout.
Comme nous n’utilisons finalement pas cette solution, voici son explication : sur tout événement Touch (mouvement d’un appui tactile), on a le vecteur caméra-vers-centre, où le centre est un point arbitraire d’orbite. À partir de là, on veut le plan tangent à la sphère sur laquelle on orbite. Étant donné que la caméra regarde toujours le centre, on l’a, il peut être défini par les vecteurs up et right de la caméra. C’est un peu comme si l’écran était projeté sur la sphère en fait (enfin c’est exactement ça je pense). À partir de là, on peut contrôler tous les axes de rotation et de position de la caméra pour obtenir un effet similaire à une trackball, bien que je n’aie pas projeté la position tactile sur une sphère pour obtenir cet effet trackball où plus on s’éloigne du centre, moins on a de rotation - ce n’était pas le but recherché.
Bref, on pouvait partir d’une vue de face de l’objet pour arriver à une vue de côté en glissant horizontalement. Puis glisser verticalement pour faire un tour complet à 360° dans la direction où on se trouvait. Exactement ce à quoi on s’attendrait avec un objet flottant devant soi dont on pourrait contrôler toutes les directions.
Cependant, ce niveau de liberté supplémentaire a causé quelques problèmes. C’était plus difficile à contrôler, à garder l’objet « à niveau ». Donc on est essentiellement revenu en arrière. Il n’était pas nécessaire, pour la présentation, de pouvoir manipuler l’objet dans toutes les directions comme un objet inspectable dans Resident Evil ; en fait, c’était plus distrayant et frustrant qu’autre chose, risquant même de détourner les utilisateurs de l’expérience.
En conclusion
Cette application a été vraiment intéressante à créer, avec beaucoup d’UI à mettre en place, et heureusement Unity a très bien géré cela, la gestion de l’UI devenant de plus en plus facile. Découvrir ShaderForge a aussi été formidable ; il y a probablement quelques redondances dans le shader, certaines opérations simples existant déjà sous forme de nodes, et je les ai sans doute répliquées avec plus de nodes que nécessaire. Le Material.Lerping a été très intéressant à travailler, même si Unity fournit un moyen de le faire soi-même - ça a amélioré ma compréhension du langage ShaderLab d’Unity, même sans le manipuler directement, et plus globalement du rendu 3D avec quelques effets spéciaux. Une chose que je n’ai pas mentionnée : on a toujours une vue interne de l’objet, donc il y aurait beaucoup à dire sur les depth buffers ici ! Le problème de couleur était vraiment étrange, j’espère que cette fonction pourra aider d’autres personnes… au final, on aurait pu utiliser un objet Gradient et l’évaluer avec Evaluate() - ç’aurait été ma dernière solution si cette fonction n’avait pas existé. Et merci à GHiOM, l’artiste qui a travaillé sur ce modèle 3D et nous a aidés dans les allers-retours d’idées sur le rendu final (et qui a aussi dû couper et ajouter des éléments à son modèle à cause de nous et de quelques artefacts étranges que pouvait produire l’effet de contour).
Pour plus d’informations, rendez-vous sur notre site : https://davikingcode.com/fr/projets/heineken-sub-3d