WebGPU / TSL
WebGPU est un nouveau standard web qui fournit une API bas niveau pour le rendu graphique et l'exécution de calculs sur le GPU. Il est conçu pour succéder à WebGL, offrant de meilleures performances et des fonctionnalités plus avancées.
Bonne nouvelle, il est désormais possible de l'utiliser avec Three.js avec des modifications minimales de la base de code.
Dans cette leçon, nous allons explorer comment utiliser WebGPU avec Three.js et React Three Fiber, et comment écrire des shaders en utilisant le nouveau Three Shading Language (TSL).
Si vous êtes nouveau dans l'univers des shaders, je vous recommande de d'abord compléter le chapitre Shaders avant de poursuivre avec celui-ci.
WebGPU Renderer
Pour utiliser l'API WebGPU au lieu de l'API WebGL, nous devons utiliser un WebGPURenderer
(Pas de section dédiée dans la documentation de Three.js pour l'instant) à la place de WebGLRenderer.
Avec React Three Fiber, lors de la création d'un composant <Canvas>
, la configuration du renderer est effectuée automatiquement. Cependant, nous pouvons remplacer le renderer par défaut en passant une fonction à la prop gl
du composant <Canvas>
.
Dans App.jsx
, nous avons un composant <Canvas>
qui utilise le WebGLRenderer
par défaut. Modifions-le pour utiliser le WebGPURenderer
à la place.
Tout d'abord, nous devons arrêter le frameloop
jusqu'à ce que le WebGPURenderer
soit prêt. Nous pouvons le faire en définissant la prop frameloop
sur never
.
// ... import { useState } from "react"; function App() { const [frameloop, setFrameloop] = useState("never"); return ( <> {/* ... */} <Canvas // ... frameloop={frameloop} > {/* ... */} </Canvas> </> ); } export default App;
Ensuite, nous devons importer la version WebGPU de Three.js :
import * as THREE from "three/webgpu";
Lors de l'utilisation de WebGPU, nous devons utiliser le module
three/webgpu
à la place du module par défautthree
. Cela est dû au fait que leWebGPURenderer
n'est pas inclus dans la build par défaut de Three.js.
Ensuite, nous pouvons utiliser la prop gl
pour créer une nouvelle instance de WebGPURenderer
:
// ... function App() { const [frameloop, setFrameloop] = useState("never"); return ( <> {/* ... */} <Canvas // ... gl={(canvas) => { const renderer = new THREE.WebGPURenderer({ canvas, powerPreference: "high-performance", antialias: true, alpha: false, stencil: false, shadowMap: true, }); renderer.init().then(() => { setFrameloop("always"); }); return renderer; }} > {/* ... */} </Canvas> </> ); } // ...
Nous créons une nouvelle instance de WebGPURenderer
et lui passons l'élément canvas
. Nous définissons également certaines options pour le renderer, telles que powerPreference
, antialias
, alpha
, stencil
, et shadowMap
. Ces options sont similaires à celles utilisées dans le WebGLRenderer
.
Enfin, nous appelons la méthode init()
du renderer pour l'initialiser. Une fois l'initialisation terminée, nous définissons l'état frameloop
à "always"
pour commencer le rendu.
Voyons le résultat dans le navigateur :
Notre cube est désormais rendu en utilisant le WebGPURenderer
à la place du WebGLRenderer
.
C'est aussi simple que cela ! Nous avons configuré avec succès un WebGPURenderer
dans notre application React Three Fiber. Vous pouvez maintenant utiliser la même API Three.js pour créer et manipuler des objets 3D, tout comme vous le feriez avec le WebGLRenderer
.
Le plus grand changement concerne l'écriture des shaders. L'API WebGPU utilise un langage de shading différent de WebGL, ce qui signifie que nous devons écrire nos shaders d'une manière différente, en WGSL au lieu de GLSL.
C'est là que le Three Shading Language (TSL) entre en jeu.
Langage de Shading Three
TSL est un nouveau langage de shading conçu pour être utilisé avec Three.js afin d'écrire des shaders de manière plus conviviale grâce à une approche basée sur les nœuds.
Un grand avantage de TSL est qu'il est indépendant du moteur de rendu, ce qui signifie que vous pouvez utiliser les mêmes shaders avec différents moteurs de rendu, tels que WebGL et WebGPU.
Cela simplifie l'écriture et la maintenance des shaders, car vous n'avez pas à vous soucier des différences entre les deux langages de shading.
Il est également à l'épreuve du temps, car si un nouveau moteur de rendu est publié, nous pourrions utiliser les mêmes shaders sans aucune modification tant que TSL le supporte.
Le Three Shading Language est encore en développement, mais il est déjà disponible dans les dernières versions de Three.js. La meilleure façon de l'apprendre, et de suivre les modifications, est de consulter la page wiki du Three Shading Language. Je l'ai utilisé de manière intensive pour apprendre à l'utiliser.
Matériaux basés sur des nœuds
Pour comprendre comment créer des shaders avec TSL, nous devons comprendre ce que signifie être basé sur des nœuds.
Dans une approche basée sur des nœuds, nous créons des shaders en connectant différents nœuds pour créer un graphe. Chaque nœud représente une opération ou fonction spécifique, et les connexions entre les nœuds représentent le flux de données.
Cette approche présente de nombreux avantages, tels que :
- Représentation visuelle : Il est plus facile de comprendre et de visualiser le flux de données et les opérations dans un shader.
- Réutilisabilité : Nous pouvons créer des nœuds réutilisables qui peuvent être utilisés dans différents shaders.
- Flexibilité : Nous pouvons facilement modifier et changer le comportement d'un shader en ajoutant ou retirant des nœuds.
- Extensibilité : Ajouter/Personnaliser des fonctionnalités à partir de matériaux existants est désormais un jeu d'enfant.
- Agnostic : TSL générera le code approprié pour le moteur de rendu cible, qu'il s'agisse de WebGL (GLSL) ou de WebGPU (WGSL).
Avant de commencer à coder notre premier matériau basé sur des nœuds, nous pouvons utiliser le playground Three.js en ligne pour expérimenter visuellement avec le système de nœuds.
Ouvrez le playground Three.js et en haut, cliquez sur le bouton Examples, puis choisissez l'exemple basic > fresnel
.
Vous devriez voir un éditeur de matériau basé sur des nœuds avec deux nœuds color
et un nœud float
attachés à un nœud fresnel
. (Color A, Color B, et Fresnel Factor)
Le nœud fresnel
est connecté à la couleur du Basic Material
, colorant ainsi la théière avec un effet fresnel.
Utilisez le bouton Splitscreen
pour visualiser le résultat à droite.
Disons que nous voulons affecter l'opacité du Basic Material
en fonction du temps. Nous pouvons ajouter un nœud Timer
et le connecter à un nœud Fract
pour remettre le temps à 0 une fois qu'il atteint 1. Puis nous le connectons à l'entrée opacity
du Basic Material
.
Notre théière apparaît maintenant progressivement avant de disparaître et de réapparaître.
Prenez le temps de jouer avec les différents nœuds et voyez comment ils affectent le matériau.
Maintenant que nous avons une compréhension de base du fonctionnement du matériau basé sur des nœuds, voyons comment utiliser le nouveau matériau basé sur des nœuds de Three.js dans React Three Fiber.
Implémentation de React Three Fiber
Jusqu'à présent, avec WebGL, nous avons utilisé le MeshBasicMaterial, le MeshStandardMaterial ou même le ShaderMaterial personnalisé pour créer nos matériaux.
En utilisant WebGPU, nous devons utiliser de nouveaux matériaux compatibles avec TSL. Leurs noms sont les mêmes que ceux que nous utilisions auparavant, précédés de Node
avant Material
:
MeshBasicMaterial
->MeshBasicNodeMaterial
MeshStandardMaterial
->MeshStandardNodeMaterial
MeshPhysicalMaterial
->MeshPhysicalNodeMaterial
- ...
Pour les utiliser de manière déclarative avec React Three Fiber, nous devons les extend
. Dans App.jsx
:
// ... import { extend } from "@react-three/fiber"; extend({ MeshBasicNodeMaterial: THREE.MeshBasicNodeMaterial, MeshStandardNodeMaterial: THREE.MeshStandardNodeMaterial, }); // ...
Dans les futures versions de React Three Fiber, cela pourrait être fait automatiquement.
Nous pouvons maintenant utiliser les nouveaux MeshBasicNodeMaterial
et MeshStandardNodeMaterial
dans nos composants.
Remplaçons le MeshStandardMaterial
du cube dans notre composant Experience
par le MeshStandardNodeMaterial
:
<mesh> <boxGeometry args={[1, 1, 1]} /> <meshStandardNodeMaterial color="pink" /> </mesh>
Nous pouvons utiliser le MeshStandardNodeMaterial
tout comme nous utiliserions le MeshStandardMaterial
.
Notre cube repose maintenant sur le MeshStandardNodeMaterial
au lieu du MeshStandardMaterial
. Nous pouvons désormais utiliser des nodes pour personnaliser le matériau.
Node de Couleur
Apprenons à créer des nœuds personnalisés pour personnaliser nos matériaux avec TSL.
Tout d'abord, créons un nouveau composant nommé PracticeNodeMaterial.jsx
dans le dossier src/components
.
export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { return <meshStandardNodeMaterial color={colorA} />; };
Et dans Experience.jsx
, remplaçons notre cube par un plan utilisant le PracticeNodeMaterial
:
// ... import { PracticeNodeMaterial } from "./PracticeNodeMaterial"; export const Experience = () => { return ( <> {/* ... */} <mesh rotation-x={-Math.PI / 2}> <planeGeometry args={[2, 2, 200, 200]} /> <PracticeNodeMaterial /> </mesh> </> ); };
Nous avons un plan avec le PracticeNodeMaterial
.
Pour personnaliser notre matériau, nous pouvons maintenant modifier les différents nœuds à notre disposition en utilisant différents nœuds. La liste des nœuds disponibles se trouve dans la page wiki.
Commençons simplement avec le nœud colorNode
pour changer la couleur de notre matériau. Dans PracticeNodeMaterial.jsx
:
import { color } from "three/tsl"; export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { return <meshStandardNodeMaterial colorNode={color(colorA)} />; };
Nous définissons la prop colorNode
en utilisant le nœud color
du module three/tsl
. Le nœud color
prend une couleur comme argument et renvoie un nœud de couleur utilisable dans le matériau.
Cela nous donne le même résultat qu'avant, mais maintenant nous pouvons ajouter plus de nœuds pour personnaliser notre matériau.
Importons les nœuds mix
et uv
du module three/tsl
et utilisons-les pour mélanger deux couleurs basées sur les coordonnées UV du plan.
import { color, mix, uv } from "three/tsl"; export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { return ( <meshStandardNodeMaterial colorNode={mix(color(colorA), color(colorB), uv())} /> ); };
Il exécutera les différents nœuds pour obtenir la couleur finale du matériau. Le nœud mix
prend deux couleurs et un facteur (dans ce cas, les coordonnées UV) et renvoie une couleur qui est un mélange des deux couleurs basé sur le facteur.
C'est exactement la même chose que d'utiliser la fonction mix
dans GLSL, mais maintenant nous pouvons l'utiliser dans une approche basée sur des nœuds. (Beaucoup plus lisible!)
Nous pouvons maintenant voir les deux couleurs mélangées en fonction des coordonnées UV du plan.
Ce qui est incroyable, c'est que nous ne partons pas de zéro. Nous utilisons le MeshStandardNodeMaterial
existant et lui ajoutons simplement nos nœuds personnalisés. Ce qui signifie que les ombres, les lumières et toutes les autres fonctionnalités du MeshStandardNodeMaterial
sont toujours disponibles.
Déclarer des nœuds en ligne est acceptable pour une logique de nœud très simple, mais pour une logique plus complexe, je vous recommande de déclarer les nœuds (et plus tard les uniforms, et plus) dans un hook useMemo
:
// ... import { useMemo } from "react"; export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { const { nodes } = useMemo(() => { return { nodes: { colorNode: mix(color(colorA), color(colorB), uv()), }, }; }, []); return <meshStandardNodeMaterial {...nodes} />; };
Cela fait exactement la même chose qu'avant, mais maintenant nous pouvons ajouter plus de nœuds à l'objet nodes
et les passer au meshStandardNodeMaterial
de manière plus organisée/générique.
En changeant les props colorA
et colorB
, cela n'entraînera pas une recompilation du shader grâce au hook useMemo
.
Ajoutons des contrôles pour changer les couleurs du matériau. Dans Experience.jsx
:
// ... import { useControls } from "leva"; export const Experience = () => { const { colorA, colorB } = useControls({ colorA: { value: "skyblue" }, colorB: { value: "blueviolet" }, }); return ( <> {/* ... */} <mesh rotation-x={-Math.PI / 2}> <planeGeometry args={[2, 2, 200, 200]} /> <PracticeNodeMaterial colorA={colorA} colorB={colorB} /> </mesh> </> ); };
La couleur par défaut fonctionne correctement, mais la mise à jour des couleurs n'a aucun effet.
C'est parce que nous devons passer les couleurs en tant qu'uniforms
au meshStandardNodeMaterial
.
Uniformes
Pour déclarer des uniformes dans TSL, nous pouvons utiliser le nœud uniform
du module three/tsl
. Le nœud uniform
prend une valeur en argument (elle peut être de différents types comme float
, vec3
, vec4
, etc.) et renvoie un nœud uniforme qui peut être utilisé dans les différents nœuds tout en étant mis à jour depuis le code de notre composant.
Remplaçons les couleurs en dur par des uniformes dans PracticeNodeMaterial.jsx
:
// ... import { uniform } from "three/tsl"; export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { const { nodes, uniforms } = useMemo(() => { const uniforms = { colorA: uniform(color(colorA)), colorB: uniform(color(colorB)), }; return { nodes: { colorNode: mix(uniforms.colorA, uniforms.colorB, uv()), }, uniforms, }; }, []); return <meshStandardNodeMaterial {...nodes} />; };
Nous déclarons un objet uniforms
pour une meilleure organisation du code et nous utilisons les valeurs uniformes à la place de la valeur par défaut que nous avons obtenue lors de la création de nos nœuds.
En les renvoyant dans le useMemo
, nous avons maintenant accès aux uniformes dans notre composant.
Dans un useFrame
, nous pouvons mettre à jour les uniformes :
// ... import { useFrame } from "@react-three/fiber"; export const PracticeNodeMaterial = ({ colorA = "white", colorB = "orange", }) => { // ... useFrame(() => { uniforms.colorA.value.set(colorA); uniforms.colorB.value.set(colorB); }); return <meshStandardNodeMaterial {...nodes} />; };
Utilisez la méthode
value.set
lorsque vous mettez à jour un uniforme d'objet. Par exemple, les uniformescolor
ouvec3
. Pour les uniformesfloat
, vous devez définir la valeur directement :uniforms.opacity.value = opacity;
Les couleurs se mettent maintenant à jour correctement en temps réel.
Avant d'en faire plus avec la couleur, voyons comment nous pouvons affecter la position des sommets de notre plan en utilisant le positionNode
.
Position Node
Le nœud positionNode
nous permet d'affecter la position des sommets de notre géométrie.
React Three Fiber: The Ultimate Guide to 3D Web Development
✨ You have reached the end of the preview ✨
Go to the next level with Three.js and React Three Fiber!
Get full access to this lesson and the complete course when you enroll:
- 🔓 Full lesson videos with no limits
- 💻 Access to the final source code
- 🎓 Course progress tracking & completion
- 💬 Invite to our private Discord community
One-time payment. Lifetime updates included.