Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 46

Optimisation des objets identiques avec InstancedMesh

Recommended resources

Bonjour ! J'espère que vous êtes prêt pour cette nouvelle leçon sur l'optimisation.

Dans la leçon précédente, nous nous sommes concentrés sur l'optimisation de la "logique" de nos applications React en prévenant les re-rendus inutiles avec React.memo et useMemo. Nous avons vu comment optimiser le travail du CPU. Aujourd'hui, nous allons nous attaquer à un autre type de goulot d'étranglement, tout aussi critique en 3D : la communication entre le CPU et le GPU.

Imaginez que vous deviez créer une forêt, un champ d'astéroïdes ou une pluie de particules. Votre premier réflexe de développeur serait probablement de faire une boucle pour créer des milliers d'objets. Malheureusement, cette approche mène directement à des performances catastrophiques.

L'objectif de cette leçon est de vous apprendre à optimiser le rendu de nombreux objets identiques avec la technique de l'instanciation (InstancedMesh). C'est l'une des techniques les plus puissantes et les plus importantes pour créer des scènes 3D riches et fluides sur le web.

1. Le Problème : Les "Draw Calls"

Pour que le GPU (la carte graphique) puisse dessiner un objet, le CPU (le processeur) doit lui envoyer une commande. Cette commande, c'est ce qu'on appelle un "draw call". Si votre scène contient 5000 arbres, une approche naïve générera 5000 draw calls à chaque image.

Le problème n'est pas que le GPU est incapable de dessiner ces 5000 arbres. Les GPU modernes sont extrêmement puissants. Le véritable goulot d'étranglement est la communication constante entre le CPU et le GPU. Envoyer des milliers de commandes à chaque rafraîchissement (60 fois par seconde) sature ce canal de communication et fait chuter drastiquement le nombre d'images par seconde (FPS).

Impact of Draw Calls on 3D Rendering Performance
Cette image illustre parfaitement l'impact des draw calls. À gauche, une scène avec près de 9000 objets individuels (et donc 9000 draw calls) tourne à un très lent 12 FPS. À droite, la même scène visuelle, mais optimisée pour n'utiliser qu'un seul draw call, atteint un fluide 60 FPS.

Pour mieux comprendre comment les draw calls sont comptabilisés et pourquoi ils sont si coûteux, la vidéo suivante offre une excellente explication.

Three.js Optimization - Best Practices and Techniques

Cette vidéo de Valentin's coding book explique ce que sont les draw calls et comment des éléments comme les ombres ou la transparence peuvent augmenter leur nombre de manière significative.

Regardez la section de 00:03:00 à 00:04:52. Concentrez-vous sur la définition d'un draw call et sur l'exemple qui montre comment leur nombre peut exploser à partir de seulement deux boîtes.

La solution pour contourner ce problème ? Envoyer une seule commande pour dessiner tous les objets identiques. C'est le principe de l'instanciation.


2. La Solution : L'Instanciation (Instancing)

L'instanciation est une technique qui permet de dessiner de multiples copies ("instances") d'un mĂŞme objet en un seul draw call.

L'idée est simple :

  1. On envoie une seule fois les données communes à toutes les instances : la géométrie (la forme) et le matériau (l'apparence).
  2. Puis, on envoie une seule fois une grande liste de données spécifiques à chaque instance : principalement sa matrice de transformation (qui définit sa position, sa rotation et son échelle).

Le GPU reçoit alors l'ordre : "Prends cette géométrie et ce matériau, et dessine-les des milliers de fois en utilisant les transformations de cette liste". Cela réduit drastiquement la communication CPU-GPU et libère les performances.

Create 100,000+ Objects With Instancing - Three.js Instanced Rendering Tutorial

La vidéo suivante, de Wael Yasmina, offre la définition la plus claire et concise de l'instanciation et de son utilité.

Regardez l'introduction de 00:00:02 à 00:02:47. L'auteur explique parfaitement pourquoi l'instanciation est la solution au problème des draw calls.

Maintenant que nous avons compris le "pourquoi" et le "quoi", passons au "comment".


3. Implémenter l'Instanciation avec Three.js

Avant de voir les abstractions de React Three Fiber, il est essentiel de comprendre comment l'instanciation fonctionne en "pur" Three.js. Cela vous donnera des bases solides. La classe clé est THREE.InstancedMesh.

Le processus est le suivant :

  1. On crée un InstancedMesh en lui passant une géométrie, un matériau et le nombre total d'instances que l'on souhaite afficher.
  2. On parcourt une boucle, de 0 jusqu'au nombre d'instances.
  3. À chaque itération, on définit la position, la rotation et l'échelle de l'instance courante. Ces informations sont stockées dans un objet THREE.Matrix4.
  4. On assigne cette matrice à l'instance correspondante avec la méthode mesh.setMatrixAt(index, matrix).

La vidéo que nous avons commencée est un tutoriel parfait pour cela.

Create 100,000+ Objects With Instancing - Three.js Instanced Rendering Tutorial

Continuons avec la vidéo de Wael Yasmina pour voir une démonstration pratique de l'implémentation de InstancedMesh.

Regardez attentivement les sections suivantes : De 00:02:47 à 00:03:30 : Création de l'InstancedMesh de base. De 00:03:30 à 00:06:12 : La partie la plus importante. Observez comment une boucle est utilisée avec un Object3D temporaire et setMatrixAt pour positionner chaque instance. De 00:06:12 à 00:06:42 : Bonus : voyez comment setColorAt permet même de donner une couleur unique à chaque instance.

Ce processus, bien que très puissant, est assez "impératif". Il ne correspond pas tout à fait à la philosophie déclarative de React. Voyons comment le transposer dans React Three Fiber.


4. L'Instanciation dans l'Écosystème React Three Fiber

Avec R3F, il existe deux manières principales de faire de l'instanciation, allant du plus bas niveau au plus haut niveau d'abstraction.

A. L'Approche Bas Niveau : <instancedMesh>

React Three Fiber expose directement la classe InstancedMesh de Three.js sous la forme d'un composant JSX : <instancedMesh>.

Cette approche est un mélange du monde impératif et déclaratif.

  • On dĂ©clare l'<instancedMesh> dans notre JSX.
  • On utilise un useRef pour obtenir une rĂ©fĂ©rence Ă  l'objet Three.js sous-jacent.
  • On utilise un useEffect (qui ne s'exĂ©cute qu'une seule fois au montage) pour faire notre boucle et appeler setMatrixAt, exactement comme dans l'exemple "vanilla" Three.js.

Scaling performance - React Three Fiber

La documentation de React Three Fiber contient un exemple de code concis pour cette approche. C'est un excellent pont entre ce que vous venez de voir en Three.js pur et le monde de React.

Lisez la section "Instancing" et étudiez l'exemple de code du composant Instances. Repérez les éléments familiers : <instancedMesh>, useRef, et useEffect qui contient la boucle et l'appel à setMatrixAt.

Cette méthode vous donne un contrôle total, mais elle reste assez verbeuse. Heureusement, la librairie drei nous offre une solution bien plus élégante.

B. L'Approche Haut Niveau : <Instances> et <Instance> de drei

La librairie @react-three/drei contient des composants qui encapsulent cette logique pour nous. Pour l'instanciation, elle fournit les composants <Instances> et <Instance>.

  • <Instances> : Le composant conteneur. Il prend en charge la crĂ©ation de l'InstancedMesh en coulisses. On lui passe la gĂ©omĂ©trie et le matĂ©riau partagĂ©s.
  • <Instance> : Un composant virtuel que l'on place Ă  l'intĂ©rieur de <Instances>. On peut en crĂ©er autant qu'on veut, et chaque <Instance> peut recevoir ses propres props de transformation (position, rotation, scale, color).

Cette approche est entièrement déclarative et bien plus "React-friendly". Vous n'avez plus besoin de useRef, de useEffect ou de manipuler des matrices manuellement.

threejs-scene-builder | LobeHub

Cet extrait de code montre à quel point l'approche avec drei est simple et intuitive, surtout pour un développeur React.

Trouvez la section "Drei Library - Essential Helpers" et analysez l'exemple de code "Performance-optimized instances" pour le composant Trees. Remarquez la simplicité : on mappe sur un tableau et on retourne un composant <Instance> pour chaque arbre, en passant simplement les transformations en tant que props.

Cette méthode est celle que vous utiliserez dans 90% des cas. Elle est plus lisible, moins sujette aux erreurs et s'intègre parfaitement à la gestion d'état de React.


5. Quand Utiliser l'Instanciation ? (Et quand ne pas le faire ?)

L'instanciation est une solution miracle pour les objets identiques (même géométrie, même matériau). Cependant, il y a des compromis à connaître.

  • Frustum Culling : Normalement, Three.js ne dessine pas les objets qui sont en dehors du champ de vision de la camĂ©ra (c'est le frustum culling). Un InstancedMesh est traitĂ© comme un seul grand objet. Par consĂ©quent, si une seule instance est visible, le GPU devra quand mĂŞme traiter le groupe entier.
  • FlexibilitĂ© : Vous ne pouvez pas appliquer un matĂ©riau radicalement diffĂ©rent Ă  une seule instance (mĂŞme si vous pouvez changer sa couleur avec setColorAt).

Dans le monde réel, le choix d'optimisation est souvent un équilibre.

Three.js Optimization - Best Practices and Techniques

Pour finir, cette section de la vidéo sur l'optimisation présente une étude de cas réelle (le projet 'Colts Club') qui a dû peser le pour et le contre de l'instanciation par rapport à d'autres techniques comme le regroupement de maillages (merging).

Regardez la section de 00:13:13 à 00:14:59. Cela vous donnera un aperçu des décisions complexes d'optimisation dans un grand projet et montrera que l'instanciation, bien que puissante, fait partie d'une boîte à outils plus large.


Conclusion

Félicitations ! Vous venez d'acquérir une des compétences les plus importantes pour la performance en 3D sur le web.

Points clés à retenir :

  • Le rendu de nombreux objets via une boucle crĂ©e un grand nombre de draw calls, ce qui sature la communication CPU-GPU et dĂ©truit les performances.
  • L'instanciation rĂ©sout ce problème en dessinant des milliers d'objets identiques (mĂŞme gĂ©omĂ©trie, mĂŞme matĂ©riau) en un seul draw call.
  • En "pur" Three.js, on utilise InstancedMesh et on configure chaque instance de manière impĂ©rative avec setMatrixAt.
  • En R3F, l'approche la plus idiomatique et la plus simple est d'utiliser les composants <Instances> et <Instance> de la librairie @react-three/drei.
  • L'instanciation est idĂ©ale pour des Ă©lĂ©ments rĂ©pĂ©titifs comme des arbres, des particules, de l'herbe, des projectiles, etc.

Aperçu de la prochaine leçon :

Maintenant que nous savons créer des scènes complexes et performantes, un autre aspect de l'expérience utilisateur devient primordial : le temps de chargement. Dans la prochaine leçon, nous apprendrons à gérer le chargement de nos modèles 3D, textures et autres ressources de manière élégante avec Suspense et le hook useProgress, pour que l'utilisateur ne se retrouve jamais face à un écran vide.