Skip to content

Techniques de pinceaux 3D et sculpture de mondes en jeu

Nous voulons que les joueurs puissent sculpter le monde. Pas placer des objets préfabriqués sur une grille. Pas activer ou désactiver des blocs. Mais véritablement remodeler le terrain : creuser des rivières, ériger des montagnes, lisser les flancs des falaises, aménager des grottes. Le genre de choses que ZBrush et le mode Sculpture de Blender permettent aux artistes de faire, mais à 60 images par seconde dans un jeu multijoueur sur navigateur.

Il s'agit d'un problème d'ingénierie complexe qui touche à la représentation des données, à l'extraction de maillages, au calcul sur GPU, aux mathématiques des pinceaux et à la synchronisation réseau. Ce guide présente tout ce que nous avons découvert.

Les deux grandes approches de représentation du terrain

Tout système de sculpture commence par un choix concernant la manière dont les données du terrain sont stockées. Ce choix détermine les types de modifications possibles, leur vitesse d'exécution et leur consommation de mémoire.

Cartes de hauteur

Une carte de hauteur stocke une valeur d'altitude par point de la grille. On peut la considérer comme une image en niveaux de gris dans laquelle la luminosité correspond à l'altitude. Le terrain actuel de notre world/client fonctionne exactement de cette manière : noise.ts génère l'altitude à l'aide d'un bruit de valeur FBM, et chaque Chunk stocke une carte de hauteur Float32Array qui est projetée sur une PlaneGeometry.

Les cartes de hauteur sont rapides. L'échantillonnage se résume à une seule lecture de tableau avec interpolation bilinéaire. Le LOD est trivial, puisqu'il suffit de réduire la résolution de la grille. Le mélange de textures par splatting correspond directement à la grille UV. Les collisions physiques se réduisent à une requête d'altitude.

La topologie constitue leur limite. Une carte de hauteur ne peut représenter qu'une seule altitude par coordonnée (x, z). Pas de grottes. Pas de surplombs. Pas d'arches. Pas de tunnels. Si un joueur sculpte une falaise qui se replie sur elle-même, une carte de hauteur ne peut pas la stocker. Pour un terrain principalement composé de collines et de montagnes, cela convient. Pour une sculpture libre dans laquelle les joueurs peuvent creuser le sol, c'est une impasse.

Représentations volumétriques (champs scalaires 3D)

L'autre possibilité consiste à stocker une valeur en chaque point de l'espace 3D. Si cette valeur est négative à l'intérieur de la matière solide et positive à l'extérieur (ou l'inverse), vous obtenez un champ de distance signée (SDF). Si cette valeur représente simplement une densité (au-dessus d'un certain seuil, la matière est solide ; en dessous, l'espace est vide), vous obtenez un champ de densité.

Les représentations volumétriques prennent en charge toutes les topologies. Grottes, surplombs, îles flottantes, tunnels traversant des montagnes. Le compromis concerne la mémoire et la complexité. Une grille de 256^3 avec des nombres flottants 32 bits occupe 64 Mo. Une grille de 512^3 en occupe 512 Mo. Et cela pour un seul chunk. Des structures de données creuses, comme les octrees ou les cartes de briques, sont nécessaires pour rendre cette approche viable.

L'étape d'extraction du maillage est également loin d'être triviale. Il ne suffit pas de définir la position Y des sommets. Il faut un algorithme capable de lire le champ scalaire et de produire un maillage triangulaire qui approxime la surface à l'endroit où le champ traverse zéro.

Extraction de maillages : Marching Cubes, Surface Nets et Dual Contouring

Marching Cubes

Marching Cubes est l'algorithme d'extraction d'isosurface le plus ancien et le plus largement implémenté. Publié par Lorensen et Cline en 1987, il examine chaque cube de la grille de voxels, dont chaque sommet possède une valeur scalaire. Si certains sommets se trouvent à l'intérieur de la surface (valeur négative) et d'autres à l'extérieur (valeur positive), un morceau de maillage triangulaire est placé dans ce cube.

Chaque cube possède 8 sommets, chacun pouvant se trouver à l'intérieur ou à l'extérieur, ce qui produit 256 configurations possibles (2^8). Par symétrie, celles-ci se réduisent à 15 cas uniques. Une table de correspondance associe chaque cas à un ensemble de triangles. Les points d'intersection sur les arêtes sont déterminés par interpolation linéaire le long des arêtes où le signe change.

Les implémentations GPU récentes ont rendu Marching Cubes suffisamment rapide pour la sculpture en temps réel. Une implémentation de 2025 sur UE5 affecte chaque thread GPU à un cube, ce qui permet d'en traiter des milliers simultanément. L'idée essentielle est que la triangulation de chaque cube est indépendante de celle de ses voisins, ce qui rend l'algorithme extrêmement facile à paralléliser.

MCHex (arxiv 2511.02064, 2025) étend Marching Cubes à la génération adaptative de maillages hexaédriques avec des valeurs jacobiennes positives garanties, ce qui améliore l'approximation des frontières pour les maillages de simulation.

rupMC atteint des performances plusieurs dizaines de fois supérieures à celles des implémentations séquentielles et quatre fois supérieures à celles des variantes DMC parallèles grâce à des architectures hétérogènes CPU/GPU.

Sa principale limite : Marching Cubes gère mal les détails anguleux. Une arête à 90 degrés devient une courbe arrondie. Pour la sculpture de terrain, cela reste généralement acceptable, car les terrains naturels sont principalement lisses, mais cela pose problème pour les éléments architecturaux.

Surface Nets

Surface Nets désigne une famille plus récente d'algorithmes qui produit des maillages plus lisses à partir de champs scalaires discrets. Au lieu de placer les sommets sur les arêtes des cubes, comme le fait Marching Cubes, Surface Nets place un sommet dans chaque cube contenant la surface, puis relie les sommets adjacents pour former des quadrilatères.

Le résultat est naturellement plus lisse. Un article de 2024 (arxiv 2401.14906) a présenté une implémentation parallèle très performante de Surface Nets, une à deux fois plus rapide en ordre de grandeur que les algorithmes séquentiels. Le crate Rust fast-surface-nets génère environ 20 millions de triangles par seconde sur un seul cœur à 2,5 GHz grâce à de petites tables de correspondance et à l'accélération SIMD.

bevy-sculpter (v0.18.0, janvier 2026) utilise Surface Nets comme stratégie principale de génération de maillages. Le crate propose une sculpture volumétrique basée sur les SDF avec quatre types de pinceaux : CSG dur (ajout ou retrait instantané), pinceau continu lisse (pour une commande maintenue), flou (lissage de la surface) et aplatissement (réglage sur une hauteur cible). Il inclut également une réinitialisation des distances SDF à l'aide de la méthode Fast Sweeping, afin de restaurer les propriétés correctes du champ de distance signée après les modifications.

Surface Nets constitue un bon compromis entre Marching Cubes, qui est simple et rapide, mais produit des maillages crénelés sur les données binaires, et Dual Contouring, qui préserve les détails anguleux mais est complexe.

Dual Contouring

Dual Contouring préserve les détails anguleux que Marching Cubes et Surface Nets ne peuvent pas conserver. Pour cela, il utilise non seulement le signe du champ à chaque sommet, mais aussi le gradient (la normale) aux intersections avec les arêtes. Une minimisation par QEF (fonction d'erreur quadratique) positionne le sommet à l'intérieur de chaque cellule à l'emplacement qui satisfait au mieux toutes les contraintes d'intersection avec les arêtes.

Résultat : les arêtes vives et les angles sont préservés dans le maillage extrait. Un cube aux faces perpendiculaires reste un cube.

Le compromis est la complexité. La résolution de la QEF présente des dépendances entre les cellules qui rendent difficile la parallélisation sur GPU. Elle peut produire des maillages non-manifold, avec des arêtes partagées par plus de deux polygones. Et son implémentation est plus complexe que celle de Marching Cubes, même si Johannes Jendersie note qu'une implémentation fonctionnelle de Dual Contouring représente environ 200 lignes de code, contre plus de 500 pour une implémentation robuste de Marching Cubes.

Cubical Marching Squares (CMS) a été proposé comme solution intermédiaire : indépendant entre les cellules, et donc adapté au GPU, tout en préservant une partie des détails anguleux.

Quelle approche choisir

Pour un système de sculpture de terrain destiné aux joueurs dans un navigateur :

Surface Nets est le meilleur candidat. Il produit des maillages lisses, donnant un aspect naturel au terrain, sans les artefacts de crénelage que Marching Cubes génère sur les données binaires. Il est suffisamment rapide pour recalculer les maillages en temps réel. Il est également plus simple à implémenter que Dual Contouring.

Marching Cubes reste un choix solide lorsque la parallélisation sur GPU est prioritaire, puisque chaque cube est indépendant, ou lorsque vous avez besoin de la prise en charge la plus large possible par les bibliothèques. L'éditeur SDF WebGPU de Reinder Nijhoff (janvier 2026) implémente à la fois Marching Cubes et Surface Nets dans son pipeline d'extraction, entièrement exécuté sur le GPU.

Dual Contouring convient mieux aux situations dans lesquelles la précision architecturale compte davantage que les performances. Ce n'est pas la solution idéale pour la sculpture de terrain en temps réel dans un navigateur.

L'algorithme Transvoxel : raccorder les différents niveaux de détail

Lorsque le terrain voxelisé est maillé à différentes résolutions (LOD0 près du joueur, LOD2 au loin), des fissures apparaissent aux frontières. Pour les cartes de hauteur, le problème est simple : il suffit d'interpoler les sommets du bord pour les aligner sur le voisin de résolution inférieure. Notre fichier chunk.ts actuel fait exactement cela dans stitchEdge().

Pour un terrain volumétrique, le problème est bien plus complexe. L'entrée d'une grotte en LOD0 peut produire 30 triangles sur la frontière. La même zone en LOD1 peut en produire 8, avec une topologie totalement différente. Il n'existe aucun moyen simple d'effectuer une interpolation linéaire entre les deux.

L'algorithme Transvoxel d'Eric Lengyel (2009) résout ce problème à l'aide de « cellules de transition ». À la frontière entre deux niveaux de LOD, l'algorithme prend en compte 9 échantillons haute résolution au lieu des 8 sommets d'un cube, ce qui produit 512 configurations possibles réparties en 73 classes d'équivalence. Chaque classe correspond à un motif triangulaire prédéfini qui comble parfaitement l'écart entre les deux résolutions.

L'algorithme travaille sur les données locales des voxels, ce qui permet de retrianguler rapidement une région modifiée. C'est indispensable pour la sculpture en temps réel : lorsqu'un joueur modifie le terrain près d'une frontière de LOD, seules les cellules de transition doivent être reconstruites.

Une implémentation Rust est disponible sous la forme du crate transvoxel. Les tables de correspondance d'origine sont disponibles sur transvoxel.org.

Mathématiques des pinceaux

Un pinceau de sculpture est une fonction qui modifie les valeurs d'un champ scalaire dans un certain rayon autour d'un point cible. Les principes mathématiques sont étonnamment similaires dans toutes les implémentations, de Blender à Unreal Engine en passant par les systèmes de jeu en temps réel.

Fonctions d'atténuation

L'atténuation du pinceau détermine comment l'intensité de la modification diminue du centre vers le bord. Blender 5.1 définit les profils standard suivants :

Lisse : f(d) = 3d^2 - 2d^3 (interpolation d'Hermite, la même fonction smoothstep que dans notre noise.ts)

Sphérique : forte au centre, avec une atténuation prononcée près du bord. Approximée par f(d) = sqrt(1 - d^2).

Pointue : f(d) = (1 - d)^n avec n > 2. Crée une pointe fine.

Linéaire : f(d) = 1 - d, où d est la distance normalisée par rapport au centre (0 au centre, 1 au bord).

Constante : f(d) = 1 pour d < 1, avec une coupure nette à la limite du pinceau.

Carré inverse : compromis entre les profils lisse et sphérique, pour une sensation naturelle rappelant l'argile.

Dans tous les cas, d = distance_to_center / brush_radius, limitée à l'intervalle [0, 1]. La valeur d'atténuation multiplie l'intensité du pinceau pour produire la modification réelle du champ en chaque point.

Espace d'atténuation

Blender distingue l'atténuation sphérique, dont la distance est calculée dans l'espace 3D du monde, et l'atténuation projetée, dont la distance est calculée dans l'espace 2D de l'écran. Avec l'atténuation projetée, deux points qui paraissent proches à l'écran sont affectés de la même manière, même s'ils se trouvent à des profondeurs très différentes dans l'espace du monde. Pour la sculpture de terrain, l'atténuation dans l'espace 3D du monde est généralement plus intuitive.

Opérations principales des pinceaux

Élever/Abaisser (déplacement) : ajoute ou soustrait une valeur au champ scalaire dans le rayon du pinceau, pondérée par l'atténuation. Pour les cartes de hauteur : height[i] += strength * falloff(d). Pour les SDF : sdf[i] -= strength * falloff(d) (la soustraction rend la matière plus solide et élève la surface).

Lisser (laplacien) : remplace chaque valeur par la moyenne de ses voisines, pondérée par l'atténuation. Cette opération efface les détails et réduit le bruit. Le filtre laplacien échantillonne un petit noyau (3x3 pour les cartes de hauteur, 3x3x3 pour les volumes) et effectue une interpolation vers la moyenne. Le lissage HC (Humphrey's Classes) est une variante qui préserve mieux le volume qu'un simple filtre laplacien.

Aplatir : définit la valeur du champ sur une hauteur cible, ou sur une distance dans l'espace SDF, avec un mélange déterminé par l'atténuation. La cible est généralement échantillonnée au centre du pinceau au début du trait, puis maintenue constante. Cela permet de créer des plateaux.

Pincer/Gonfler : déplace les sommets vers la normale de la surface ou dans la direction opposée. Dans l'espace SDF, cela revient à effectuer un déplacement dans la direction du gradient.

Saisir : déplace une région du champ comme si vous tiriez sur de l'argile. Le vecteur de déplacement correspond au mouvement de la souris projeté dans l'espace du monde, puis appliqué aux valeurs du champ dans le rayon.

Bruit : ajoute du bruit procédural au champ dans le rayon du pinceau. Utile pour rendre les surfaces lisses plus rugueuses.

Tampon : applique une image 2D en niveaux de gris comme déplacement, en la projetant sur la surface située sous le curseur. L'outil Landscape d'Unreal Engine prend en charge cette technique pour les pinceaux de terrain.

Tessellation adaptative

sculpt-3D (outil de sculpture pour navigateur basé sur React et Three.js) implémente la tessellation adaptative : à mesure que le pinceau se déplace sur le maillage, les triangles proches de son centre sont subdivisés afin de fournir davantage de sommets pour la déformation. Cela évite le problème de « l'étirement low-poly », lorsqu'un maillage grossier est déformé par la sculpture. La subdivision utilise une séparation symétrique afin d'obtenir des triangles de qualité uniforme.

Pour les systèmes volumétriques, la tessellation adaptative n'est pas nécessaire de la même manière, car le maillage est régénéré à partir du champ. À la place, vous pouvez augmenter localement la résolution des voxels près des modifications à l'aide d'octrees adaptatifs pour obtenir le même effet.

Sculpture SDF : l'approche de Dreams

Dreams de Media Molecule (PS4, 2020) est le système de sculpture intégré à un jeu le plus ambitieux jamais commercialisé. Alex Evans a présenté son approche technique au SIGGRAPH 2015.

Représentation

Dreams stocke la géométrie sous la forme d'une fonction SDF composée dans des blocs de textures volumiques fp16 de 83^3. Chaque sculpture est une liste de 1 à 100 000 « modifications », chacune correspondant à une opération CSG (ajout, soustraction, couleur) associée à une forme primitive (sphère, cube, cylindre, cône, ellipsoïde, tore, etc.) et à un mode de fusion. Les modes de fusion utilisent des fonctions de maximum doux et de minimum doux. Une fusion « douce » produit des transitions arrondies entre les primitives (comme de l’argile pressée ensemble). Une fusion « dure » produit des découpes booléennes nettes. Le rayon de fusion est réglable par l’utilisateur.

Rendu

Dreams n’extrait pas de maillage triangulaire. Il effectue plutôt le rendu directement à partir du SDF à l’aide d’un moteur de rendu personnalisé par nuage de points (« flecks »). Chaque fleck est un minuscule disque orienté selon la normale à la surface. Le SDF est échantillonné pour repérer les surfaces, puis les flecks y sont répartis. Cela élimine entièrement le goulet d’étranglement de l’extraction de maillage, mais nécessite un moteur de rendu personnalisé.

Pour un monde Three.js/WebGL, cette approche n’est pas directement applicable. Il faudrait extraire des maillages. En revanche, le concept de liste d’opérations CSG est très pertinent pour l’annulation/rétablissement et la synchronisation réseau.

Moteur SDF dynamique de Mike Turitzin (2026)

Un moteur de jeu actuellement développé par Mike Turitzin utilise des SDF dynamiques comme représentation principale. Le moteur prend en charge :

Modifications détaillées pendant le jeu : ajout et suppression de matière de manière lisse ou avec des arêtes vives. Modifications non destructives, comme le déplacement de trous ou la création de tunnels temporaires qui disparaissent derrière le joueur.

Cartes et atlas de briques pour une mise en cache éparse. Au lieu de stocker tout le champ SDF dans une grille 3D dense, le champ est divisé en « briques » (de petites tuiles 3D). Seules les briques contenant la frontière de la surface sont allouées. Cela réduit considérablement l’utilisation de la mémoire dans les scènes constituées principalement d’espace vide ou plein.

Clipmaps géométriques (Losasso & Hoppe, SIGGRAPH 2004) pour les niveaux de détail. Des grilles régulières imbriquées, de résolutions croissantes, entourent la position de la caméra. La grille la plus interne offre la résolution la plus fine ; les grilles extérieures sont progressivement plus grossières. Cela réduit considérablement l’utilisation de la mémoire tout en permettant de prendre en charge de vastes espaces. Les clipmaps sont mises à jour progressivement à mesure que la caméra se déplace, ce qui les rend efficaces pour le chargement en continu de mondes ouverts.

La physique et les collisions fonctionnent directement à partir du SDF. Le tracé de sphères (ray marching utilisant la distance du SDF comme taille de pas) permet un lancer de rayons efficace. La détection des collisions utilise le gradient du SDF comme normale à la surface et la valeur de distance comme profondeur de pénétration.

Teardown : destruction voxelique à grande échelle

Teardown (Voxagon) représente l’autre extrémité du spectre : chaque objet du monde est un volume voxelique pouvant être détruit morceau par morceau.

Architecture

Les objets sont stockés sous forme de grilles de voxels à espacement régulier. Le moteur n’utilise ni Marching Cubes ni SDF pour le rendu. Il effectue plutôt un lancer de rayons directement sur les voxels à l’aide d’un algorithme DDA (Digital Differential Analyzer) modifié dans les fragment shaders, reposant sur OpenGL 3.3. Les mipmaps forment une structure d’octree dense qui accélère le parcours des espaces vides lors de l’intersection des rayons.

Pour chaque objet, le moteur rastérise sa boîte englobante orientée (OBB) et trace un rayon à travers celle-ci pour trouver les intersections avec les voxels. Seules les faces arrière de l’OBB sont rendues, ce qui permet à la caméra de pénétrer dans le volume englobant.

Synchronisation de la destruction (multijoueur)

La mise à jour multijoueur de Teardown de mars 2026 utilise une approche semi-déterministe. La destruction structurelle (perçage de trous, changement de propriété, reconnexion de liaisons) est gérée au moyen de calculs entiers à virgule fixe sur un flux réseau fiable. Tous les clients exécutent les mêmes commandes déterministes et aboutissent au même état du monde. Les changements non structurels (débris, particules) utilisent une synchronisation d’état non fiable.

C’est un enseignement important pour notre monde multijoueur : les modifications du terrain doivent être déterministes. Si le joueur A sculpte une montagne, tous les clients doivent produire le même maillage à partir des mêmes données de champ. Les commandes de modification (position, rayon et intensité de la brosse, type d’opération) doivent constituer les données faisant autorité, et non le maillage obtenu.

ALICE-SDF : compression et arbres CSG

ALICE-SDF (Adaptive Lightweight Implicit Compression Engine, v1.3.0, mars 2026) fournit une implémentation en Rust de données spatiales fondées sur les SDF, avec une compression 10 à 1 000 fois supérieure à celle des maillages polygonaux. Il prend en charge :

126 blocs de construction : 72 primitives, 24 opérations, 7 transformations et 23 modificateurs. Des opérations de fusion douce (union, soustraction, intersection), ainsi que des fusions par chanfrein et par marches pour les biseaux à arêtes vives et les transitions CSG en escalier.

Diff/patch d’arbres CSG pour l’annulation/rétablissement et la synchronisation réseau. C’est la fonctionnalité essentielle pour la sculpture multijoueur : au lieu d’envoyer l’état complet du champ, on envoie la différence structurelle entre deux arbres CSG. Le client applique le correctif pour reconstruire le nouvel état. Cette méthode utilise bien moins de bande passante que la compression différentielle de données voxel brutes.

Optimisation des arbres CSG, notamment la suppression des transformations identité, la fusion des transformations imbriquées et la rétrogradation des modificateurs. Cela permet de conserver un arbre compact à mesure que les modifications s’accumulent.

Génération de maillage avec Marching Cubes et Dual Contouring. La détection des collisions physiques fonctionne directement à partir du SDF.

La prise en charge de WebAssembly permet l’intégration dans le navigateur. Le moteur est écrit en Rust avec des liaisons WASM, ce qui en fait une option réaliste pour une application Three.js.

Calcul WebGPU pour le terrain

Depuis fin 2025, WebGPU est disponible dans tous les principaux navigateurs. Chrome 113+, Edge 113+, Firefox 141+ et Safari 26+ l’intègrent tous avec activation par défaut. Cela rend possibles des pipelines de compute shaders auparavant réservés au GPU.

Performances

Grâce à une parallélisation massive, les compute shaders WebGPU génèrent le terrain environ 100 fois plus vite que les méthodes sur CPU. Le GPU exécute des milliers de calculs simultanément, et la génération de terrain est presque entièrement parallélisable (chaque sommet ou voxel est indépendant).

Le travail est organisé en trois niveaux : niveau Dispatch (répartition de la charge de travail sur le GPU), niveau Workgroup (mémoire partagée au sein d’une unité de traitement) et niveau Thread (calculs individuels). WGSL (WebGPU Shading Language) est le langage de shaders utilisé.

Pipeline de sculpture de terrain en temps réel

Un pipeline de sculpture WebGPU se présenterait ainsi :

  1. Application de la brosse (compute shader) : mise à jour des valeurs du champ scalaire dans le rayon de la brosse. Chaque thread traite un voxel. Il lit les paramètres de la brosse (position, rayon, intensité, type d’atténuation, opération) depuis un tampon uniforme et applique la modification.

  2. Extraction du maillage (compute shader) : exécution de Surface Nets ou de Marching Cubes sur la région modifiée. L’éditeur SDF WebGPU de Nijhoff met en œuvre cette opération sous forme de pipeline en plusieurs étapes : partitionnement de l’espace en 16 384 cellules, subdivision des cellules fondée sur un octree, puis extraction de la surface.

  3. Mise à jour du tampon de sommets (côté GPU) : écriture des sommets extraits directement dans un tampon de rendu, sans aller-retour par la mémoire du CPU.

  4. Calcul des normales (compute shader) : calcul des normales des sommets à partir du maillage ou du gradient du SDF.

  5. Rendu (pipeline standard) : dessin du maillage avec des matériaux PBR standard.

Les étapes 1 à 4 peuvent toutes s’exécuter sur le GPU sans qu’aucune donnée ne revienne vers JavaScript. Le CPU doit seulement envoyer les paramètres de la brosse à chaque image.

L’éditeur SDF WebGPU

L’éditeur SDF WebGPU de Reinder Nijhoff (janvier 2026) illustre cette approche dans Chrome. Il prend en charge six primitives (cône, cylindre, capsule, tore, boîte, sphère), trois opérations de fusion (union, soustraction, intersection) avec une fusion douce configurable, ainsi que des graphes de scène hiérarchiques. Chaque primitive occupe 112 octets dans un tampon GPU unique.

Le pipeline de rendu utilise 1 024 shadow maps pour l’occlusion ambiante et l’anticrénelage temporel. Il fonctionne à une fréquence d’images interactive sur les GPU haut de gamme.

Sculpture par heightmap : la voie la plus simple

Si la prise en charge des grottes et des surplombs n’est pas nécessaire, la sculpture par heightmap évite tout le pipeline volumétrique. C’est ainsi que la plupart des jeux commercialisés gèrent la modification du terrain.

Modèle d’implémentation à l’exécution

Unity Runtime Terrain (JohannHotzel, janvier 2026) illustre le modèle standard :

  1. Lancer un rayon depuis la caméra à travers la position de la souris afin de trouver le point d’impact sur le terrain.
  2. Convertir le point d’impact en coordonnées de heightmap.
  3. Appliquer la brosse aux valeurs voisines de la heightmap, pondérées par l’atténuation.
  4. Mettre à jour le maillage en définissant les positions Y des sommets à partir de la heightmap modifiée.
  5. Reconstruire le collider physique afin qu’il corresponde au nouveau maillage.

Pour notre terrain Three.js, les étapes 1 à 4 s’intègrent directement à l’architecture existante. La classe Chunk stocke déjà les heightmaps et construit des maillages à partir de celles-ci. L’ajout de la sculpture nécessiterait :

  • Un système de lancer de rayons sur les maillages des chunks (Three.js Raycaster)
  • Des fonctions d’application de brosse modifiant les valeurs de chunk.heightmap
  • La mise à jour des sommets du maillage (définition des positions Y, recalcul des normales)
  • Le recalcul de la splat map pour la région concernée (afin que le mélange des textures reflète la nouvelle pente ou hauteur)
  • La diffusion réseau de la modification (position, rayon et intensité de la brosse, opération) vers les autres clients par l’intermédiaire du protocole WebSocket existant

L’approche par clipmap

Landow.dev décrit une « clipmap itinérante » pour les terrains fondés sur une heightmap : un maillage unique à densité de subdivision variable qui suit le joueur. Au lieu de diviser la heightmap en plusieurs maillages ayant différents niveaux de détail (comme nous le faisons actuellement), la clipmap est un maillage continu, dense près de la caméra et plus grossier sur les bords.

Cela élimine entièrement le raccordement entre niveaux de détail. Le maillage comporte simplement davantage de triangles là où ils sont nécessaires et moins là où ils ne le sont pas. En contrepartie, la sculpture exige de mettre à jour un seul grand maillage plutôt que des chunks individuels, ce qui peut être coûteux pour les modifications importantes.

Heightmaps SDF non destructives

Landow.dev décrit également une technique dans laquelle la heightmap elle-même est générée à partir d’une composition de SDF. Des instances de formes (sphères, boîtes, fonctions de bruit) sont composées dans un compute shader à l’aide d’opérations CSG, puis la sortie est échantillonnée sous forme de heightmap. Cela permet une modification non destructive (chaque instance de forme peut être déplacée ou supprimée à tout moment), tout en conservant la simplicité du rendu par heightmap.

Il s’agit d’une solution hybride convaincante : la représentation des données est volumétrique (arbre CSG de SDF), mais le chemin de rendu utilise un maillage de heightmap standard. On bénéficie ainsi de l’annulation/rétablissement et des opérations de modification adaptées au réseau du côté SDF, ainsi que d’un rendu et d’une physique simples du côté de la heightmap. La limitation demeure : aucune grotte ni aucun surplomb.

Octrees de voxels épars pour les grands mondes

Les grilles 3D denses ne passent pas à l’échelle. Un monde mesurant 1 km de côté avec une résolution de 0,5 m nécessiterait 8 milliards de voxels. Les octrees de voxels épars (SVO) résolvent ce problème en subdivisant récursivement l’espace et en n’allouant du stockage qu’aux octants contenant la frontière de la surface.

Un SVO fournit naturellement des niveaux de détail hiérarchiques : la profondeur de l’arbre en chaque point détermine la résolution effective. Près du joueur, l’arbre est entièrement développé (niveau de détail maximal). Au loin, il est tronqué à un niveau plus grossier.

Pour le rendu, les SVO peuvent être directement parcourus par lancer de rayons (aucune extraction de maillage n’est nécessaire). Un ray marcher sur GPU intersecte les rayons avec des boîtes alignées sur les axes à chaque niveau de l’arbre, en ignorant entièrement les sous-arbres vides. Cela élimine les problèmes de suraffichage du rendu par chunks et évite les artefacts du maillage glouton.

Le constructeur de SVO fondé sur Vulkan d’AdamYuan affiche des performances remarquables : 19 ms de temps de construction pour Crytek Sponza à une résolution de 2^10 sur une GTX 1660 Ti.

Pour la sculpture, la modification des SVO est efficace : seuls les nœuds feuilles situés dans le rayon de la brosse doivent être mis à jour, et la structure arborescente gère naturellement les résolutions variables. L’ajout de détails là où le joueur sculpte (en subdivisant les nœuds pour obtenir une résolution supérieure) et leur suppression là où il lisse (en fusionnant les nœuds pour revenir à une résolution inférieure) découlent directement de la structure de données.

La difficulté du déploiement dans le navigateur tient au fait que WebGL ne prend pas en charge les compute shaders. WebGPU le permet, mais les algorithmes de construction et de parcours des SVO sont complexes à implémenter en WGSL.

Synchronisation réseau pour la sculpture multijoueur

Notre monde prend déjà en charge le multijoueur au moyen des Cloudflare Durable Objects (world-chunk-do.ts). L’ajout de la sculpture implique de synchroniser les modifications du terrain entre tous les clients connectés.

Compression différentielle

L’envoi de données voxel brutes est coûteux. Une étude menée en 2024 par l’université d’Oulu a obtenu une amélioration de 2 à 8 fois de la charge utile en combinant un encodage différentiel avec la compression DEFLATE, pour ramener les mises à jour voxel à moins d’un octet par voxel. Le codec SDEC utilise un encodage différentiel compacté au niveau des bits et produit des paquets moyens de 259 octets, contre 1 114 octets avec une sérialisation générique.

Synchronisation fondée sur les opérations (recommandée)

Au lieu de synchroniser l’état du champ, synchronisez les opérations. Chaque action de sculpture devient un message :

typescript
interface TerrainEditMsg {
  t: MsgType.TerrainEdit
  brush: {
    position: [number, number, number]
    radius: number
    strength: number
    falloff: 'smooth' | 'linear' | 'sharp' | 'constant'
    operation: 'raise' | 'lower' | 'smooth' | 'flatten' | 'noise'
    targetHeight?: number
  }
}

Le serveur diffuse ce message à tous les clients, et chaque client applique la même opération de brosse déterministe à ses données de terrain locales. C’est la même approche que celle utilisée par Teardown pour la destruction structurelle : des commandes déterministes sur un flux fiable.

Le système de diff/patch d’arbres CSG d’ALICE-SDF va plus loin : au lieu de représenter des coups de brosse individuels, la différence représente la modification structurelle de l’ensemble de l’arbre CSG. Cela permet une annulation ou un rétablissement efficace sur le réseau (en envoyant le correctif inverse), et les clients rejoignant tardivement peuvent reconstruire l’état complet du monde en rejouant le journal des opérations.

Priorité et limitation de fréquence

Les modifications du terrain proches des joueurs connectés doivent être hautement prioritaires (diffusion immédiate). Les modifications éloignées de tout joueur peuvent être regroupées et envoyées moins fréquemment. Le réseau voxelique d’Enshrouded utilise ce modèle : mises à jour à 60 Hz pour le terrain proche des joueurs et à 10 Hz pour les régions en arrière-plan.

La compression ZSTD sur le réseau réduit jusqu’à 60 % la taille des paquets contenant des messages de mise à jour du terrain.

Sculpture collaborative : modifications simultanées

Lorsque plusieurs joueurs sculptent simultanément la même région, il faut résoudre les conflits. cSculpt (CNR Visual Computing Lab, 2016) a résolu ce problème grâce à un algorithme de fusion multirésolution. Chaque modification est représentée à plusieurs échelles, et les modifications simultanées qui se chevauchent sont fusionnées en mélangeant leurs représentations multirésolutions. Pour nos besoins, une approche plus simple suffit : la dernière écriture l’emporte, selon l’ordre établi par le serveur. Le Durable Object horodate chaque modification et les diffuse dans l’ordre. Tous les clients appliquent les modifications dans la même séquence. Comme les coups de pinceau sont petits, localisés et additifs/soustractifs, le résultat visuel de modifications simultanées légèrement réordonnées est généralement impossible à distinguer de l’ordre « correct ».

INST-Sculpt : édition neuronale de SDF (frontière de la recherche)

INST-Sculpt (arxiv 2502.02891, février 2025) permet de modifier des SDF neuronaux à l’aide de traits. Les utilisateurs dessinent des traits sur la surface, et le système déforme le champ neuronal sous-jacent dans des voisinages tubulaires autour du tracé. Des profils de pinceau personnalisés (sections transversales configurables) contrôlent la forme de la déformation.

Cette approche est intéressante pour les terrains générés par IA : si le monde de base est représenté sous forme de SDF neuronal (un petit réseau neuronal qui associe des coordonnées 3D à une distance signée), la sculpture modifie les poids du réseau plutôt que des données de voxels explicites. La représentation est extrêmement compacte (quelques Mo pour un monde entier), mais son évaluation est plus coûteuse que celle d’une table de correspondance.

Cette technologie en est encore au stade de la recherche. Le coût d’inférence des SDF neuronaux sur du matériel grand public est aujourd’hui trop élevé pour une utilisation en temps réel dans les jeux. Elle mérite néanmoins d’être surveillée, en particulier à mesure que les capacités des shaders WebGPU progressent et que l’inférence des modèles s’accélère.

World Creator 2026.3 : l’état de l’art commercial du terrain

World Creator (BiteTheBytes, mars 2026) représente l’état de l’art commercial des outils de création de terrains. La version 2026.3 a ajouté la génération de terrains sur GPU avec adaptation automatique du terrain (le terrain épouse les objets placés), la distribution d’objets centrée sur la caméra pour optimiser les LOD, ainsi que l’importation de données d’élévation réelles (GeoTIFF, HGT, DTED).

Depuis, World Creator 2026.4 (28 avril 2026) a ajouté les expressions mathématiques dans les champs numériques, la fusion des normales du terrain pour intégrer les objets principaux à la surface, la prise en charge complète des décalcomanies et une mise à l’échelle selon la VRAM, qui adapte le nombre maximal d’objets à la mémoire GPU disponible. BiteTheBytes a également publié une Community Edition gratuite, dotée de toutes les fonctionnalités mais sans possibilité d’exportation, ce qui en fait de fait une version d’essai illimitée.

Leur approche utilise le calcul GPU pour toutes les opérations sur le terrain : simulation de l’érosion, creusement de rivières, peinture de textures. Les outils de pinceau sont accélérés par le GPU et fournissent un retour en temps réel dans la vue. Cela correspond au pipeline de calcul WebGPU décrit ci-dessus, exécuté sur des GPU de bureau.

Ce que nous avons déjà construit : 24 prototypes exploratoires et un monde en production

Le répertoire world/spikes/ contient 24 prototypes autonomes. Il ne s’agit pas de simples démos. Ils constituent un pipeline progressif de R&D dans lequel chaque prototype a résolu un problème précis, mesuré ses performances par rapport à un objectif et orienté le suivant. Le système de sculpture s’appuie sur chacun d’eux, et pas seulement sur les derniers prototypes volumétriques.

Le terrain heightmap de production (world/client/)

Le monde en ligne utilise un système de heightmaps découpées en chunks dans Three.js WebGL :

  • noise.ts génère la hauteur du terrain à l’aide d’un bruit de valeur FBM (5 octaves pour les collines, 4 pour les crêtes et 3 pour les microdétails), avec une fonction déterministe terrainHeight(wx, wz)
  • chunk.ts construit des maillages PlaneGeometry à partir de heightmaps Float32Array, avec 3 niveaux de LOD (32/8/4 segments par chunk de 64 unités), et place des arbres/panneaux instanciés à l’aide d’un placement aléatoire initialisé et de colliders propres à chaque objet
  • chunk-manager.ts charge progressivement les chunks en anneaux autour du joueur (rayon 1 au LOD0, rayon 3 au LOD1 et rayon 6 au LOD2), raccorde les bords par interpolation linéaire dans stitchEdge(), et fournit getHeight(), getNormal() et resolveCollisions() à la couche physique
  • terrain-material.ts effectue une fusion de textures à 4 couches basée sur une splat map (herbe/roche/sable/terre) au moyen de MeshStandardMaterial.onBeforeCompile, avec des poids déterminés par la pente et la hauteur, ainsi qu’une fusion des normal maps de chaque couche
  • character-controller.ts échantillonne la hauteur du terrain à chaque frame pour gérer la gravité, le contact avec le sol et le rejet des pentes (cosinus maximal d’une pente de 50 degrés). La sculpture doit transmettre instantanément les hauteurs modifiées à ce système, sans quoi le joueur traverse le terrain modifié
  • placement.ts dispose déjà d’un Raycaster qui détecte les maillages des chunks pour l’outil de placement d’objets. L’outil de pinceau doit suivre exactement ce modèle plutôt que de recréer le raycasting à partir de zéro
  • protocol.ts définit des messages encodés en MessagePack pour la synchronisation multijoueur via le Durable Object world-chunk-do.ts, qui gère actuellement les messages PlayerState, PlaceObject, RemoveObject et Snapshot. Les modifications du terrain nécessiteront un nouveau type de message
  • world-chunk-do.ts (Cloudflare Worker) conserve les objets placés dans le stockage du Durable Object et les diffuse aux joueurs connectés toutes les 50 ms. Il ne gère pas encore les modifications du terrain

Prototypes 01 à 11 : la couche fondamentale

Ces prototypes ont validé les systèmes fondamentaux dont dépendra la sculpture. Les ignorer reviendrait à passer à côté de contraintes que le système de sculpture doit respecter.

Prototype 01 (terrain + instanciation) : Le premier prototype de terrain dans Three.js. Il a établi le modèle PlaneGeometry + heightmap et le placement d’objets instanciés que chunk.ts utilise toujours.

Prototype 02 (worker physique Rapier) : Rapier 3D s’exécute dans un Web Worker avec un collider ColliderDesc.heightfield(). Ce prototype a permis de créer un contrôleur de personnage cinématique avec franchissement automatique des obstacles, limites de pente et alignement sur le sol. Il a démontré que la physique peut s’exécuter hors du thread principal à partir d’un champ de hauteur. Si nous sculptons le terrain, le champ de hauteur physique doit être reconstruit ou remplacé par un collider trimesh pour les chunks MC.

Prototype 05 (comportements LLM) : Sans lien direct avec le terrain, mais il a établi le schéma JSON des comportements des objets de jeu. C’est pertinent, car les éléments de terrain sculptés pourraient déclencher des comportements (par exemple, une rivière creusée pourrait générer des effets aquatiques).

Prototype 06 (chargement progressif des chunks) : Premier système de chargement/remplacement des chunks à mesure que le joueur se déplace. Il a établi le modèle utilisé par chunk-manager.ts : des régions colorées qui se chargent et se déchargent. La sculpture doit préserver l’état des modifications lorsque les chunks sont déchargés puis rechargés.

Prototype 07 (végétation GPU à partir de cartes de densité) : Herbe et arbres instanciés, placés à l’aide de cartes de densité qui échantillonnent la hauteur et la pente du terrain. La sculpture invalide le placement de la végétation : si la hauteur du terrain change, les arbres peuvent se retrouver en lévitation ou enterrés. La carte de densité doit être régénérée pour les chunks modifiés.

Prototype 08 (coût du shader de matériau du terrain) : Mesure des performances de la projection triplanaire, des normal maps et de la fusion à 4 couches. Le coût exact en millisecondes de chaque fonctionnalité a été mesuré. Le prototype a montré que la projection triplanaire + les normales + 4 couches restent dans le budget à plus de 45 FPS. Ce budget est important pour le terrain sculpté : si nous ajoutons une 5e couche pour la « terre remuée » ou modifions la fusion des surfaces creusées, nous savons exactement de quelle marge nous disposons.

Prototype 09 (budget des ombres CSM) : Ombres en cascade avec 3 cascades d’une résolution de 1024^2. Le coût des ombres a été mesuré à environ 1,5 ms. Le terrain sculpté modifie les shadow maps, mais leur coût reste constant, quelle que soit la forme du terrain.

Prototype 10 (clipmaps géométriques + géomorphing) : Anneaux de clipmap imbriqués avec géomorphing entre les niveaux de LOD afin d’éliminer les apparitions soudaines. Le nombre constant de triangles garantit un coût GPU prévisible. Le géomorphing est important pour la sculpture : lorsque le joueur sculpte près d’une limite de LOD, la transition entre les niveaux de LOD doit refléter la modification. Si celle-ci n’existe que dans l’anneau haute résolution, la cible du géomorphing est incorrecte.

Prototype 11 (chargement progressif des chunks de heightmap) : Chargement progressif plus avancé des chunks, avec une grille visuelle indiquant les états chargé/en cours de chargement/déchargé pour chaque niveau de LOD. Ce prototype a établi le budget de chargement : nombre maximal de chunks chargés par frame et priorité accordée aux chunks qui nécessitent un LOD supérieur. La sculpture ajoute un nouveau signal de priorité : les chunks que le joueur est en train de modifier ne doivent jamais être déchargés.

Prototypes 12 à 14 : intégration de WebGPU et Three.js

Prototype 12 (Marching Cubes WebGPU) : Le premier prototype volumétrique. Quatre chunks SDF de 64^3 avec des grottes sphériques animées, entièrement exécutés sur le GPU. Il utilise directement WebGPU : pipelines de calcul pour l’évaluation du SDF, extraction MC avec la table de cas de Twinklebear (256 configurations de 16 entrées chacune), compteur atomique de sommets et dessin indirect. L’objectif de performances était de moins de 4 ms par chunk et de moins de 12 ms pour les 4. Cela a confirmé que MC sur GPU est suffisamment rapide pour remailler le terrain en temps réel dans le navigateur. Tous les prototypes volumétriques suivants réutilisent la table de cas MC et les shaders WGSL définis ici.

Prototype 13 (réinitialisation de la base à partir du prototype 12) : Portage du chemin de rendu WebGPU brut du prototype 12 afin qu’il s’exécute dans le WebGPURenderer de Three.js, en accédant directement au device du backend. Le pipeline de rendu utilise toujours WebGPU directement (drawIndirect avec une structure Vertex vec4+vec4). Cela a démontré que les calculs personnalisés et le rendu de scène Three.js peuvent coexister sur le même périphérique GPU.

Prototype 14 (consolidation incrémentale de Three.js WebGPU) : Remplacement du pipeline de rendu brut par des StorageBufferAttribute Three.js pour les positions et les normales. Le calcul MC écrit directement dans ces buffers résidant sur le GPU. Le buffer drawIndirect contrôle le nombre de sommets dessinés par Three.js. C’est le modèle utilisé par tous les prototypes suivants : le calcul reste en WebGPU brut, tandis que le rendu passe par le graphe de scène Three.js. La version de Three.js utilisée dans ces prototypes est passée de 0.170.0 à 0.172.0 à mesure que le backend WebGPU se stabilisait.

Prototypes 15 à 17 : raccordement des LOD avec Transvoxel

Prototype 15 (structure de raccord Transvoxel) : Ajout de l’architecture en trois zones : chunk MC (centre volumétrique), bande de transition (raccord entre la limite MC et la heightmap) et anneau de terrain (heightmap environnante). Les trois utilisent la même passe de matériau. À ce stade, la bande de transition est un maillage temporaire, et non de véritables cellules Transvoxel.

Prototype 16 (face +X Transvoxel avec heightmap partagée) : Deux avancées essentielles dans un même prototype. Premièrement, remplacement du plan de terrain plat du SDF par une heightmap Perlin partagée : un Float32Array de 257x257 envoyé au GPU sous forme de buffer de stockage, puis échantillonné par interpolation bilinéaire dans le shader de calcul SDF. La surface MC et le maillage de la heightmap reposent désormais sur la même source de vérité. Deuxièmement, implémentation de véritables cellules de transition Transvoxel pour la face +X en récupérant sur GitHub les tables de données de référence d’Eric Lengyel (transitionCellClass, transitionVertexData, transitionCellData) ainsi que le paquet npm transvoxel-data. Le CPU évalue des cellules de transition à 9 échantillons (512 configurations, 73 classes d’équivalence), place les sommets en interpolant les valeurs SDF aux points de la grille et gère l’inversion de l’ordre des sommets pour les cas symétriques.

Prototype 17 (double MC avec LOD 1x/2x) : Deux chunks MC côte à côte à des résolutions différentes. Haute résolution : 62 cellules avec cell_scale=1.0. Basse résolution : 31 cellules avec cell_scale=2.0. Le shader MC a reçu les uniformes cell_scale et grid_points. Introduction de transition_shrink : les sommets de la limite face-0 du chunk basse résolution sont décalés vers l’intérieur de 15 % de cell_scale, créant un mince espace que les cellules de transition Transvoxel remplissent sans z-fighting. C’est le modèle de LOD dont le système de production a besoin : chunks proches en pleine résolution, chunks éloignés à demi-résolution et Transvoxel à chaque limite.

Prototypes 18 à 21 : cas limites de Transvoxel et accélération GPU

Chacun de ces quatre prototypes a résolu un cas d’échec précis de l’implémentation Transvoxel. Les regrouper masquerait les différences entre ces problèmes.

Prototype 18 (raccord de heightmap 2:1) : Application de Transvoxel à une limite composée uniquement de heightmaps, où la résolution d’un côté est deux fois supérieure à celle de l’autre. Aucun MC n’intervient. Le raccord entre un chunk de 62 cellules et un chunk de 31 cellules est généré à partir des tables de transition Transvoxel, avec une réduction de 15 % de la face basse résolution. Cela a confirmé que Transvoxel fonctionne pour le cas composé uniquement de heightmaps, et pas seulement pour MC.

Prototype 19 (grille d’angle 64/32/32/16) : Le cas de raccordement le plus difficile : quatre chunks de résolutions différentes se rejoignant en un point d’angle (64, 32, 32 et 16 cellules). Le système de raccord doit générer des cellules de transition le long de quatre arêtes (A-B, A-C, B-D, C-D), avec un ordre des sommets correct dans chaque direction. Ce prototype a démontré que les tables Transvoxel gèrent l’angle multirésolution sans logique spéciale personnalisée.

Prototype 20 (angle Transvoxel sur GPU) : Déplacement de la génération des cellules de transition Transvoxel vers le GPU pour la disposition d’angle 64/32/32/16. Le CPU constituait un goulot d’étranglement lorsque les cellules de transition étaient régénérées à chaque frame pour un terrain animé. Le calcul GPU génère les sommets du raccord dans la même passe que l’extraction MC.

Prototype 21 (angle MC + Transvoxel sur GPU) : Combinaison de l’extraction MC entièrement sur GPU et de la génération des raccords Transvoxel sur GPU dans une seule séquence de dispatchs de calcul. Les chunks MC et les quatre raccords sont tous générés sur le GPU, avec des nombres de sommets gérés par des compteurs atomiques et un rendu effectué avec drawIndirect. Il s’agit du pipeline GPU complet pour un terrain volumétrique multirésolution avec des transitions de LOD sans raccord visible.

Prototypes 22 à 24 : l’architecture hybride

Prototype 22 (politique hybride MC/heightmap) : Le prototype architectural essentiel. Par défaut, les chunks sont des heightmaps. Lorsque la sphère de déformation animée croise l’AABB d’un chunk, celui-ci passe en mode MC. Les autres restent des maillages de heightmap statiques. Disposition : chunks de 64, 32/32 et 16 cellules à différentes résolutions. Les raccords Transvoxel gèrent chaque limite, y compris les transitions entre MC et heightmap. Le prototype suit à chaque frame le nombre de chunks MC par rapport au nombre de chunks HM, ainsi que les dépassements de capacité des sommets.

Prototype 23 (modes de chunks pilotés par une politique) : Chargé sous forme de correctif par-dessus le prototype 22. Ajout d’une hystérésis basée sur la distance à la caméra (les chunks ne basculent pas sans cesse entre les modes lorsque la caméra se trouve près d’un seuil) et d’un masque de modifications (les chunks déformés restent en mode MC même si la source de déformation s’éloigne). C’est le comportement de « modification persistante » dont la sculpture a besoin : dès qu’un joueur creuse une grotte, ce chunk reste définitivement volumétrique.

Prototype 24 (politique + anneaux de clipmap) : Le prototype le plus avancé. Il combine le système de politique en champ proche du prototype 23 avec les anneaux de clipmap géométriques en champ lointain du prototype 10. Mise à niveau vers Three.js 0.183.1. Le champ proche utilise l’approche hybride HM/MC avec des raccords Transvoxel aux résolutions 64/32/16. Le champ lointain utilise des anneaux de clipmap à centre statique qui suivent la caméra. Il s’agit de l’architecture complète de rendu du terrain : sculpture volumétrique par chunks là où elle est nécessaire, et terrain clipmap peu coûteux partout ailleurs.

Pourquoi Marching Cubes plutôt que Surface Nets

La section de recherche externe de ce guide présente Surface Nets comme le meilleur candidat pour la sculpture de terrain dans le navigateur. Pourtant, tous les prototypes du pipeline utilisent Marching Cubes. Ce n’est pas un hasard.

Le principal avantage de MC est son parallélisme trivial : chaque cube est totalement indépendant. Les shaders de calcul WGSL des prototypes 12 à 24 exécutent un thread par cube, sans aucune communication entre les cellules. Des compteurs atomiques gèrent l’allocation des sommets. Cette approche correspond parfaitement aux groupes de travail du GPU.

Surface Nets place un sommet dans chaque cellule contenant une surface, puis relie les cellules voisines. Cette connectivité entre voisins crée une dépendance entre les cellules. Le crate fast-surface-nets la gère sur le CPU grâce à un ordre d’itération soigneusement conçu. Sur le GPU, elle nécessite soit une approche en deux passes — trouver les sommets, puis les relier — soit de la mémoire partagée au sein des groupes de travail. Les deux sont possibles avec WebGPU, mais ajoutent de la complexité.

La recommandation pratique est donc de conserver Marching Cubes pour le pipeline de sculpture. Il a fait ses preuves dans notre base de code, les shaders WGSL existent et ont été évalués, et le système de raccords Transvoxel repose sur le placement des sommets de MC le long des arêtes. Surface Nets mérite d’être réévalué si l’aliasing de MC sur les données binaires devient un problème visible. Cependant, pour un terrain SDF dont les valeurs forment des gradients réguliers, MC produit des résultats propres.

Architecture pratique pour la sculpture

La série de prototypes a résolu le pipeline de rendu. Il reste à mettre en place le système de brosses, la propagation des effets dans les systèmes du jeu et la synchronisation multijoueur. Voici le plan, fondé sur l’ensemble des prototypes.

Phase 1 : sculpture de heightmap (changements minimaux, portée maximale)

Ajoutez des outils de brosse qui modifient les heightmaps des chunks dans le code de production world/client/. Cette approche fonctionne avec le moteur de rendu WebGL existant et ne nécessite pas WebGPU.

Entrée de la brosse : suivez le modèle de PlacementTool dans placement.ts. Il utilise déjà un Raycaster qui intersecte chunkManager.getChunkMeshes() et suit un maillage fantôme au point d’impact. Un TerrainBrushTool effectuerait le même raycast, mais modifierait la heightmap du chunk au lieu de placer un objet. Le gestionnaire World.onMouseDown effectue déjà la répartition selon l’état de l’outil.

Modification du chunk (Chunk.applyBrush) : convertissez la position de la brosse dans le monde en coordonnées de la grille de la heightmap. Pour chaque point de la grille situé dans le rayon de la brosse, calculez le déplacement pondéré par l’atténuation, puis ajoutez-le à la valeur de la heightmap ou soustrayez-le. Mettez ensuite le maillage à jour : définissez les positions Y des sommets à partir de la heightmap modifiée, recalculez les normales par différences centrales — selon le même modèle terrainHeight(wx +/- eps, wz) déjà utilisé aux lignes 155 à 158 de chunk.ts — et régénérez la splat map de la région affectée avec createSplatMap() dans terrain-material.ts, afin de mettre à jour le mélange des textures fondé sur la pente.

Contrôleur du personnage : CharacterController.update() appelle getHeight() à chaque frame pour maintenir le personnage au sol. ChunkManager.getHeight() délègue à Chunk.sampleHeight(), qui lit le tableau Float32Array heightmap du chunk. Puisque nous modifions directement ce tableau, le contrôleur du personnage prend en compte le changement dès la frame suivante, sans branchement supplémentaire.

Invalidation des objets : les instances d’arbres dans chunk.ts sont placées en échantillonnant terrainHeight() lors de leur génération. Après une sculpture, les arbres de la zone affectée peuvent se retrouver à une hauteur incorrecte. La phase 1 peut reporter ce problème — les arbres flotteront légèrement après de petites modifications. La phase 2 nécessite une méthode chunk.invalidateObjects() qui rééchantillonne les hauteurs et reconstruit les matrices d’instances. Il en va de même pour les colliders utilisés dans resolveCollisions().

Physique Rapier (si elle est intégrée) : le prototype 02 a démontré le bon fonctionnement des colliders de heightfield. Si Rapier est actif, le collider de heightfield doit être reconstruit ou corrigé pour le chunk modifié. ColliderDesc.heightfield() de Rapier accepte un Float32Array linéaire, il suffit donc de le remplacer.

Synchronisation réseau : ajoutez MsgType.TerrainEdit = 10 à protocol.ts :

typescript
interface TerrainEditMsg {
  t: MsgType.TerrainEdit
  cx: number
  cz: number
  brush: {
    wx: number
    wz: number
    radius: number
    strength: number
    falloff: number
    operation: number
  }
}

Le WorldChunkDO diffuse ce message à tous les clients et l’ajoute à un journal de modifications propre au chunk, stocké dans le stockage du Durable Object. Les clients qui rejoignent la partie plus tard reçoivent le journal de modifications dans le message Snapshot et le rejouent pour reconstruire l’état du terrain. Tous les clients appliquent la même fonction de brosse déterministe et convergent donc vers la même heightmap.

Déchargement et rechargement des chunks : les prototypes 06 et 11 ont établi le modèle de streaming. Lorsqu’un chunk est déchargé puis rechargé, son journal de modifications doit être rejoué sur la heightmap procédurale de base. Le journal est stocké côté serveur dans le Durable Object et inclus dans le message Snapshot.

Phase 2 : sculpture volumétrique avec l’architecture des prototypes 22 à 24

Intégrez le pipeline du prototype 24 au monde de production. Lorsqu’un joueur sculpte sous la surface — en creusant une grotte ou un tunnel — le chunk affecté passe du mode heightmap au mode MC.

Migration du moteur de rendu vers WebGPU : les prototypes 13 et 14 ont démontré que le WebGPURenderer de Three.js peut exécuter des calculs personnalisés tout en conservant le graphe de scène. Le monde de production passe de WebGLRenderer à WebGPURenderer et utilise StorageBufferAttribute pour les chunks MC. Si WebGPU n’est pas disponible, utilisez le mode de repli de la phase 1, limité aux heightmaps.

Allocation du SDF par chunk : suivez le modèle hybride du prototype 22. Chaque chunk commence sous forme de heightmap. Au premier coup de brosse volumétrique, allouez un Float32Array de 64^3, initialisez-le en échantillonnant la heightmap — la valeur SDF de chaque point est world.y - heightmap_value — puis passez au rendu MC. Le système de règles du prototype 23 garantit que le chunk reste ensuite définitivement en mode MC, conformément au comportement de « modification persistante » du masque d’édition.

Heightmap partagée dans le shader SDF : utilisez la fonction height_at() du prototype 16. Téléversez la heightmap du chunk dans un buffer de stockage GPU. Le shader de calcul SDF évalue max(height_sdf, edit_sdf), où height_sdf = world.y - height_at(world.xz) et où edit_sdf contient les modifications apportées par la brosse. Les chunks MC et les chunks de heightmap partagent ainsi la même référence à leurs limites.

Raccords Transvoxel : utilisez la pile complète des prototypes 15 à 21. Les limites entre MC et heightmap emploient des cellules de transition avec un espace de rétraction. Les limites entre chunks MC de résolutions différentes utilisent le modèle à double niveau de détail du prototype 17. Le cas particulier du prototype 19 gère les intersections à quatre branches. Le calcul GPU du prototype 21 génère toute la géométrie des raccords au cours de la même exécution.

Champ lointain par clipmap : utilisez les anneaux de clipmap du prototype 24 pour le terrain situé hors de la portée de sculpture. La sculpture ne modifie jamais ces anneaux, qui échantillonnent la heightmap procédurale de base.

Géomorphing : le géomorphing du prototype 10 élimine les apparitions brusques lors des transitions entre niveaux de détail. Pour les chunks modifiés, la cible du géomorphing doit inclure les modifications. Si un chunk est en MC au LOD0 et que son voisin au LOD1 est une heightmap, le géomorphing effectue une interpolation entre les deux représentations. Cela nécessite d’échantillonner le journal de modifications même aux niveaux de détail inférieurs.

Budget des matériaux : le prototype 08 a mesuré plus de 45 FPS avec quatre couches triplanaires et des normales. Les chunks MC ont besoin du même matériau. La splat map peut être générée à partir du gradient SDF — les fortes pentes donnent de la roche, les zones planes de l’herbe — plutôt qu’à partir de la pente de la heightmap. Cette approche reste dans la limite des quatre couches.

Invalidation de la végétation : la végétation fondée sur la carte de densité du prototype 07 dépend de la hauteur et de la pente du terrain. Lorsqu’un chunk passe en mode MC, les instances d’arbres doivent être régénérées en échantillonnant la surface SDF. Les arbres situés sur des surplombs ou dans des grottes doivent être éliminés. Les matrices du maillage instancié de chunk.ts sont reconstruites à partir de la nouvelle surface.

Phase 3 : arbres de modifications CSG pour l’annulation, le rétablissement et la synchronisation réseau

Remplacez les mutations directes du SDF par un arbre d’opérations CSG. Chaque coup de brosse ajoute une primitive — sphère, capsule ou boîte — associée à une opération — ajout, soustraction ou fusion douce. Le SDF est recalculé à partir de l’arbre.

Avantages :

  • Non destructif : toute modification peut être retirée de l’arbre pour être annulée
  • Efficace sur le réseau : diffusez l’opération CSG plutôt que les valeurs brutes du champ
  • Déterministe : tous les clients construisent le même SDF à partir de la même séquence d’opérations
  • Le système de différences et de correctifs d’arbres CSG d’ALICE-SDF assure une synchronisation économe en bande passante, ainsi que l’annulation et le rétablissement sur le réseau

Stockage dans le Durable Object : l’arbre de modifications de chaque chunk remplace le journal linéaire de la phase 1. Le WorldChunkDO stocke la structure de l’arbre CSG, et non les deltas bruts de la heightmap. Les messages Snapshot incluent l’arbre, que les clients rejoignant la partie plus tard évaluent pour produire leur SDF local.

Phase 4 : sculpture collaborative

Ajoutez la prise en charge des modifications simultanées avec un rejeu des opérations ordonné par le serveur. Le Durable Object horodate chaque modification et les diffuse dans l’ordre. Les clients qui rejoignent la partie plus tard reçoivent le journal des opérations et reconstruisent l’état du monde. Le type de message Snapshot existant est étendu afin d’inclure l’historique des modifications du terrain pour chaque chunk.

Comme les coups de brosse sont petits, localisés et additifs ou soustractifs, le résultat visuel d’un léger réordonnancement des modifications simultanées est généralement impossible à distinguer de l’ordre « correct ». Une stratégie où la dernière écriture l’emporte, selon l’ordre du serveur, est suffisante. La fonction tick() du Durable Object — qui s’exécute actuellement toutes les 50 ms pour l’état des joueurs — ajoute les modifications du terrain à diffuser dans la même boucle.

Références principales

Algorithmes :

  • Lorensen & Cline, « Marching Cubes » (1987)
  • Eric Lengyel, « Transvoxel Algorithm » (2009), transvoxel.org
  • Losasso & Hoppe, « Geometry Clipmaps » (SIGGRAPH 2004)
  • « A High-Performance SurfaceNets Discrete Isocontouring Algorithm » (arxiv 2401.14906, 2024)
  • MCHex (arxiv 2511.02064, 2025)

Implémentations :

  • bevy-sculpter v0.18.0 (Rust, Surface Nets + brosses SDF)
  • fast-surface-nets (Rust, 20 millions de triangles/s)
  • ALICE-SDF v1.3.0 (Rust + WASM, différences et correctifs d’arbres CSG)
  • WebGPU SDF Editor (Nijhoff, janvier 2026)
  • SculptingPro (API de sculpture à l’exécution pour Unity)
  • TerraBrush (GDExtension de sculpture de terrain pour Godot)

Jeux :

  • Dreams (Media Molecule, SDF + rendu par nuage de points, SIGGRAPH 2015)
  • Teardown (Voxagon, ray tracing DDA sur voxels, destruction multijoueur déterministe)
  • Moteur SDF de Mike Turitzin (brick maps + clipmaps géométriques, janvier 2026)

Réseau :

  • « Optimizing payload size for voxel state synchronization » (Oulu, 2024)
  • Multijoueur de Teardown (synchronisation semi-déterministe de la destruction, mars 2026)
  • cSculpt (sculpture collaborative de maillages avec fusion multirésolution)
Essayez tout de suiteSculptez moins, jouez plus vite

Créez un jeu centré sur le terrain sans toucher à une seule brosse.

Créer gratuitement →C'est gratuit, ça tourne dans votre navigateur, rien à installer.