Blanc Cambouis est une entreprise qui vend du mobilier haut de gamme à ses clients. Pour permettre au client de visualiser le produit qu’il achète, on nous a demandé de créer un visualiseur d’objet 3D temps réel sur mesure, capable de gérer différents types de modèles et de les personnaliser à la volée.
Pendant qu’Integral Service travaillait sur la partie web du produit, nous, chez Da Viking Code, avons travaillé sur le visualiseur d’objets.
Pour répondre à leur besoin, nous avons construit une application Three.js (basée sur WebGL) en utilisant le langage Haxe et l’extern Haxe pour Three.js de yar3333. Il nous fallait une solution modulaire pour importer le mobilier dans le moteur, mais aussi pour permettre les multiples options de personnalisation du meuble sans implémenter une nouvelle logique pour chaque modèle.
Export avec Unity
La première tâche de ce projet a été de pouvoir créer et personnaliser la scène 3D à exporter vers Three.js : on nous livrait des modèles fbx et nous voulions une scène Json. Pour cela, nous avons utilisé Unity pour régler l’échelle des objets, les différentes parties des objets et les lumières de la scène, puis nous les avons exportés avec l’exporteur JSON Three.js de Nick Janssen.
Unity exportant la scène
Convention de nommage et personnalisation
Comme l’objet devait avoir différentes options de personnalisation activables dans le moteur (ouvert, fermé, avec ou sans panneau arrière, différents matériaux de bois, différentes réflectivités et différentes couleurs sur certaines parties, parfois avec différents motifs), il nous fallait un moyen d’exprimer tous ces différents états de l’objet afin que le même moteur puisse gérer chaque modèle avec ses options particulières de façon transparente.
Une convention de nommage a été choisie afin que le moteur puisse identifier les objets dynamiques, comprendre leur comportement et appliquer les changements sur le modèle en conséquence. Le site web n’a plus qu’à appeler certaines fonctions avec certains paramètres, et il peut changer la couleur, la texture, les modèles, la luminosité à la volée !
exemple de conventions et de la manière dont elles se comportent
Personnaliser les motifs du mobilier avec un matériau phong Three.js sur mesure
Comme nous l’avons vu, le mobilier peut être personnalisable en matériau (bois, plastique), en couleur (couleur unie), ou en motifs (motifs imprimés répétés) pour certaines parties de chaque modèle de meuble (panneaux, poignées de porte…).
Nous avons choisi d’avoir un shader unifié pour afficher soit une couleur, soit une texture, soit un motif noir et blanc, où les couleurs noir et blanc seraient remplacées par des couleurs choisies par l’utilisateur, donnant aux motifs beaucoup plus de personnalisation. Ainsi, un seul matériau peut être utilisé pour chaque cas, et pour tous les sous-objets du modèle affiché.
Nous avons commencé par écrire un shader glsl personnalisé pour webgl, en utilisant le ShaderMaterial de three.js. Cependant, cela ne prendrait pas en compte les informations d’éclairage, la brillance, la métallicité, les cartes environnementales ou les cartes réfléchissantes, tout ce que three.js fait déjà et rend disponible par défaut, ce qu’il aurait fallu réécrire depuis zéro en supposant qu’on puisse le connecter correctement aux uniforms par défaut de three.js.
Nous avons donc décidé de le faire à la manière de three.js : en utilisant ses extraits de code shader préécrits, appelés « shader chunks ». Ce sont des extraits de code glsl « assemblés » à l’exécution par Three.js en interne pour construire ses matériaux par défaut disponibles.
Une excellente idée pour éviter la redondance de code et mieux maintenir son code source, je suppose. Il a été difficile de trouver un tutoriel ou de bonnes informations sur ce sujet, la plupart des articles « personnalisez votre propre shader three.js » vous expliquent comment partir de rien… et finissent peut-être par colorer quelque chose avec une couleur unie, sans lumières.
En explorant comment le matériau phong de base était défini et le dossier three.js/src/renderers/shaders/ sur le github de Three.js, nous avons fini par comprendre.
Heureusement, tous les shader chunks, les uniforms, sont nommés de manière intuitive, ce qui permet de comprendre quel chunk/lib correspond à quel matériau de base — en plus de ça, une fois qu’on entre dans l’esprit des gens qui écrivent un projet open source et qu’on commence à comprendre pourquoi les choses semblent être éparpillées un peu partout, ça devient plus facile.
Il est possible de faire des recoupements pour savoir quel « snippet » doit être « inclus » où, en parcourant par exemple les fichiers ShaderLib. Nous garderons Phong comme exemple, et voici ses « specs » indiquant quelles UniformLibs (extraits de code pour définir les uniforms dans le programme shader) utiliser.
Et à partir de là, on peut aussi déduire quel extrait de vertex / fragment shader est utilisé. Voici le lien vers celui du vertex.
Et encore à partir de là, on peut déduire quelles autres dépendances il faut pour reconstruire, recompiler petit à petit un shader phong complet qui nous permettra d’utiliser les propriétés de brillance, toutes les options d’éclairage et les cartes réfléchissantes (et ainsi de suite) que propose three.js, sans vraiment tout réécrire… ni décompiler quoi que ce soit à l’exécution.
C’est donc un processus douloureux. Mais une fois que c’est fait, on comprend mieux three.js, et on peut personnaliser n’importe quel autre shader sans repartir de zéro, et se permettre de s’appuyer sur l’incroyable travail des contributeurs sur les shaders d’éclairage par exemple.
En codant en Haxe, il y aura le problème de savoir si vous avez les externs nécessaires, mais traduire du .js vers haxe, et les ajouter à vos externs pour avoir l’accès nécessaire, est facile. Le code peut être long, mais les choses ont du sens, vous verrez, tout est une question d’accéder aux « snippets » ou « chunks » via des statics.
Voici donc un exemple complet en .js qui reconstitue un matériau Phong de Three.js et le personnalise en « injectant » du code glsl pour modifier les couleurs du fragment avant le traitement de l’éclairage, ou les positions du vertex avant
Vous constaterez que la partie javascript de cet exemple crée les objets, les lumières, le shader lui-même, des uniforms personnalisés comme le temps, les couleurs et d’autres valeurs nécessaires pour cet exemple. La partie html contient le code fragment et vertex glsl « injecté », dont une partie, comme l’algorithme de bruit de Perlin (par Stefan Gustavson), n’est pas de nous, alors n’oubliez pas de vérifier les commentaires.
Cela aide à ne pas encombrer la partie js avec du code supplémentaire, on a donc un bel exemple ; la partie html n’est vraiment là que pour cet exemple, la partie .js peut être adaptée pour tout le monde et sera, je l’espère, bien comprise, car le but est vraiment de personnaliser un matériau three.js de base en ajoutant des chunks ou des extraits du code three.js disponible un par un dans un tableau, puis en les joignant avec “\n” pour obtenir un programme shader valide, et voilà.
Pour l’essentiel, c’est ce qu’utilise ce projet, donc le code personnalisé réel, le code hors three.js, utilise simplement un motif noir et blanc, deux couleurs en entrée, et fait un lerp entre les couleurs d’entrée en fonction de la valeur de l’échantillon de texture, ce qui donne un motif à deux couleurs avec n’importe quelle paire de couleurs voulue.