Skip to content

3D-Pinseltechniken und Weltenformung im Spiel

Wir möchten, dass Spieler die Welt formen können. Nicht Prefabs auf einem Raster platzieren. Nicht Blöcke ein- und ausschalten. Sondern das Terrain tatsächlich umgestalten: Flüsse graben, Berge anheben, Felshänge glätten und Höhlen ausheben. Also das, was ZBrush und der Sculpt-Modus von Blender für Künstler leisten – nur mit 60 fps in einem Multiplayer-Browserspiel.

Das ist eine anspruchsvolle technische Aufgabe, die Datenrepräsentation, Mesh-Extraktion, GPU-Computing, Pinselmathematik und Netzwerksynchronisierung umfasst. Dieser Leitfaden dokumentiert alles, was wir herausgefunden haben.

Die zwei Welten der Terrainrepräsentation

Jedes Formungssystem beginnt mit der Entscheidung, wie die Terraindaten gespeichert werden. Diese Wahl bestimmt, welche Arten von Bearbeitungen möglich sind, wie schnell sie ausgeführt werden und wie viel Speicher sie benötigen.

Heightmaps

Eine Heightmap speichert für jeden Rasterpunkt einen Höhenwert. Man kann sie sich als Graustufenbild vorstellen, bei dem die Helligkeit der Höhe entspricht. Unser aktuelles Terrain in world/client funktioniert genau so: noise.ts erzeugt Höhen mittels FBM-Wertrauschen, und jeder Chunk speichert eine Heightmap als Float32Array, die auf eine PlaneGeometry projiziert wird.

Heightmaps sind schnell. Eine Abfrage erfordert nur einen einzigen Array-Zugriff mit bilinearer Interpolation. LOD ist trivial, weil lediglich die Rasterauflösung reduziert werden muss. Splat-basiertes Textur-Blending lässt sich direkt auf das UV-Raster abbilden. Physikkollisionen reduzieren sich auf eine Höhenabfrage.

Die Einschränkung liegt in der Topologie. Eine Heightmap kann pro (x, z)-Koordinate nur eine Höhe darstellen. Keine Höhlen. Keine Überhänge. Keine Bögen. Keine Tunnel. Wenn ein Spieler eine Klippe formt, die sich über sich selbst zurückwölbt, kann eine Heightmap sie nicht speichern. Für Terrain, das hauptsächlich aus sanften Hügeln und Bergen besteht, ist das ausreichend. Für freie Formung, bei der Spieler in den Boden graben können, ist es eine Sackgasse.

Volumetrisch (skalare 3D-Felder)

Die Alternative besteht darin, an jedem Punkt im 3D-Raum einen Wert zu speichern. Ist der Wert innerhalb festen Materials negativ und außerhalb positiv (oder umgekehrt), handelt es sich um ein Signed Distance Field (SDF). Ist der Wert lediglich eine Dichte – oberhalb eines bestimmten Schwellenwerts fest, darunter leer –, handelt es sich um ein Dichtefeld.

Volumetrische Repräsentationen können jede Topologie abbilden: Höhlen, Überhänge, schwebende Inseln und Tunnel durch Berge. Die Nachteile sind Speicherbedarf und Komplexität. Ein 256^3-Raster mit 32-Bit-Gleitkommazahlen benötigt 64 MB. Ein 512^3-Raster benötigt 512 MB. Und das gilt nur für einen einzigen Chunk. Damit dies praktikabel wird, sind dünn besetzte Datenstrukturen wie Octrees oder Brick Maps erforderlich.

Auch die Mesh-Extraktion ist nicht trivial. Es reicht nicht, einfach die Y-Positionen der Vertices festzulegen. Man benötigt einen Algorithmus, der das skalare Feld einliest und ein Dreiecks-Mesh erzeugt, das die Oberfläche dort approximiert, wo das Feld den Wert null schneidet.

Mesh-Extraktion: Marching Cubes, Surface Nets und Dual Contouring

Marching Cubes

Marching Cubes ist der älteste und am weitesten verbreitete Algorithmus zur Extraktion von Isoflächen. Er wurde 1987 von Lorensen und Cline veröffentlicht und untersucht jeden Würfel im Voxelraster, wobei jede Ecke einen skalaren Wert besitzt. Liegen einige Ecken innerhalb der Oberfläche (negativ) und andere außerhalb (positiv), wird in diesem Würfel ein Teilstück eines Dreiecks-Meshs platziert.

Jeder Würfel hat 8 Ecken, von denen jede entweder innen oder außen liegt. Daraus ergeben sich 256 mögliche Konfigurationen (2^8). Durch Symmetrie lassen sich diese auf 15 eindeutige Fälle reduzieren. Eine Lookup-Tabelle ordnet jedem Fall eine Gruppe von Dreiecken zu. Die Schnittpunkte auf den Kanten werden durch lineare Interpolation entlang der Kanten ermittelt, an denen sich das Vorzeichen ändert.

Aktuelle GPU-Implementierungen haben Marching Cubes schnell genug für Echtzeitformung gemacht. Eine UE5-Implementierung aus dem Jahr 2025 weist jedem GPU-Thread einen Würfel zu und verarbeitet so Tausende gleichzeitig. Die zentrale Erkenntnis besteht darin, dass die Triangulierung jedes Würfels unabhängig von seinen Nachbarn ist, wodurch sich der Algorithmus außerordentlich gut parallelisieren lässt.

MCHex (arxiv 2511.02064, 2025) erweitert Marching Cubes um die Erzeugung adaptiver hexaedrischer Meshes mit garantiert positiven Jacobi-Werten und verbessert so die Randapproximation für Simulations-Meshes.

rupMC erreicht mit heterogenen CPU-/GPU-Architekturen eine um ein Vielfaches höhere Leistung als serielle Implementierungen und ist viermal schneller als parallele DMC-Varianten.

Die wichtigste Einschränkung: Marching Cubes hat Probleme mit scharfen Merkmalen. Eine 90-Grad-Kante wird zu einer glatten Kurve abgerundet. Für die Terrainformung ist das üblicherweise akzeptabel, weil natürliches Gelände größtenteils weich geformt ist. Für architektonische Strukturen stellt es jedoch ein Problem dar.

Surface Nets

Surface Nets ist eine neuere Familie von Algorithmen, die aus diskreten skalaren Feldern glattere Meshes erzeugt. Statt Vertices wie Marching Cubes auf Würfelkanten zu platzieren, setzt Surface Nets einen Vertex in jeden Würfel, der die Oberfläche enthält, und verbindet anschließend benachbarte Vertices zu Quads.

Das Ergebnis ist von Natur aus glatter. Eine Veröffentlichung aus dem Jahr 2024 (arxiv 2401.14906) demonstrierte eine hochleistungsfähige parallele Surface-Nets-Implementierung, die ein bis zwei Größenordnungen schneller als sequenzielle Algorithmen arbeitet. Das Rust-Crate fast-surface-nets erzeugt auf einem einzelnen 2,5-GHz-Kern mithilfe kleiner Lookup-Tabellen und SIMD-Beschleunigung ungefähr 20 Millionen Dreiecke pro Sekunde.

bevy-sculpter (v0.18.0, Januar 2026) nutzt Surface Nets als primäre Meshing-Strategie. Das Crate bietet SDF-basierte volumetrische Formung mit vier Pinseltypen: hartes CSG (sofortiges Hinzufügen/Entfernen), weich und kontinuierlich (für gehaltene Eingaben), Weichzeichnen (Oberflächenglättung) und Abflachen (auf Zielhöhe setzen). Außerdem umfasst es eine SDF-Redistanzierung mittels Fast Sweeping Method, um nach Bearbeitungen die korrekten Eigenschaften des Signed Distance Fields wiederherzustellen.

Surface Nets bilden einen guten Mittelweg zwischen Marching Cubes – einfach und schnell, erzeugt bei binären Daten jedoch Meshes mit Aliasing – und Dual Contouring, das Merkmale bewahrt, aber komplex ist.

Dual Contouring

Dual Contouring bewahrt scharfe Merkmale, die Marching Cubes und Surface Nets nicht erhalten können. Dazu verwendet es nicht nur das Vorzeichen des Felds an jeder Ecke, sondern auch den Gradienten beziehungsweise die Normale an den Kantenschnittpunkten. Durch Minimierung einer QEF (Quadratic Error Function) wird der Vertex innerhalb jeder Zelle an der Position platziert, die alle Bedingungen der Kantenschnittpunkte am besten erfüllt.

Das Ergebnis: Scharfe Kanten und Ecken bleiben im extrahierten Mesh erhalten. Ein Würfel mit senkrecht zueinander stehenden Flächen bleibt ein Würfel.

Der Nachteil ist die Komplexität. Die QEF-Lösung weist Abhängigkeiten zwischen Zellen auf, die eine GPU-Parallelisierung erschweren. Sie kann nicht-mannigfaltige Meshes erzeugen, also Kanten, die von mehr als zwei Polygonen gemeinsam genutzt werden. Zudem ist die Implementierung aufwendiger als bei Marching Cubes, obwohl Johannes Jendersie anmerkt, dass eine funktionierende Dual-Contouring-Implementierung ungefähr 200 Codezeilen umfasst, gegenüber mehr als 500 für eine robuste Marching-Cubes-Implementierung.

Cubical Marching Squares (CMS) wurde als Mittelweg vorgeschlagen: unabhängig zwischen Zellen und damit GPU-freundlich, aber dennoch in der Lage, bestimmte Merkmale zu bewahren.

Welcher Ansatz ist der richtige?

Für spielerorientierte Terrainformung im Browser gilt:

Surface Nets ist der stärkste Kandidat. Es erzeugt glatte Meshes und damit natürlich wirkendes Terrain, ohne die Aliasing-Artefakte von Marching Cubes bei binären Daten. Es ist schnell genug für erneutes Meshing in Echtzeit und einfacher zu implementieren als Dual Contouring.

Marching Cubes bleibt eine solide Wahl, wenn GPU-Parallelität Priorität hat – jeder Würfel ist unabhängig – oder wenn möglichst breite Bibliotheksunterstützung erforderlich ist. Der WebGPU SDF Editor von Reinder Nijhoff (Januar 2026) implementiert in seiner Extraktionspipeline sowohl Marching Cubes als auch Surface Nets und läuft vollständig auf der GPU.

Dual Contouring eignet sich am besten für Fälle, in denen architektonische Präzision wichtiger als Leistung ist. Für die Echtzeitformung von Terrain in einem Browser ist es nicht ideal.

Der Transvoxel-Algorithmus: LOD-Übergänge ohne Risse

Wenn Voxel-Terrain mit unterschiedlichen Auflösungen vermesht wird – LOD0 in Spielernähe, LOD2 in größerer Entfernung –, entstehen an den Übergängen Risse. Bei Heightmaps ist das ein einfaches Problem: Die Kanten-Vertices werden so interpoliert, dass sie zum niedriger aufgelösten Nachbarn passen. Unser aktuelles chunk.ts macht in stitchEdge() genau das.

Bei volumetrischem Terrain ist das Problem erheblich schwieriger. Ein Höhleneingang auf LOD0 kann am Rand 30 Dreiecke erzeugen. Derselbe Bereich kann auf LOD1 8 Dreiecke mit einer völlig anderen Topologie erzeugen. Es gibt keine einfache Möglichkeit, linear zwischen ihnen zu interpolieren.

Eric Lengyels Transvoxel-Algorithmus (2009) löst dieses Problem mit „Übergangszellen“. An der Grenze zwischen zwei LOD-Stufen berücksichtigt der Algorithmus 9 hochauflösende Samples statt 8 Würfelecken. Daraus ergeben sich 512 mögliche Konfigurationen, die in 73 Äquivalenzklassen fallen. Jede Klasse wird einem vordefinierten Dreiecksmuster zugeordnet, das die Lücke zwischen den beiden Auflösungen lückenlos schließt.

Der Algorithmus arbeitet mit lokalen Voxeldaten, sodass die erneute Triangulierung eines veränderten Bereichs schnell ist. Das ist für Echtzeitformung entscheidend: Bearbeitet ein Spieler Terrain nahe einer LOD-Grenze, müssen nur die Übergangszellen neu aufgebaut werden.

Eine Rust-Implementierung ist als transvoxel-Crate verfügbar. Die ursprünglichen Lookup-Tabellen sind unter transvoxel.org erhältlich.

Pinselmathematik

Ein Formungspinsel ist eine Funktion, die skalare Feldwerte innerhalb eines Radius um einen Zielpunkt verändert. Die Mathematik ist in allen Implementierungen erstaunlich ähnlich – von Blender über Unreal Engine bis hin zu Laufzeitsystemen in Spielen.

Abklingfunktionen

Das Abklingprofil des Pinsels bestimmt, wie die Bearbeitungsstärke vom Zentrum zum Rand hin abnimmt. Blender 5.1 definiert diese Standardprofile:

Weich: f(d) = 3d^2 - 2d^3 (Hermite-Interpolation, derselbe Smoothstep wie in unserer noise.ts)

Kugel: Stark im Zentrum mit steilem Abfall nahe dem Rand. Approximiert als f(d) = sqrt(1 - d^2).

Spitz: f(d) = (1 - d)^n mit n > 2. Erzeugt eine feine Spitze.

Linear: f(d) = 1 - d, wobei d der normalisierte Abstand vom Zentrum ist (0 im Zentrum, 1 am Rand).

Konstant: f(d) = 1 für d < 1, mit hartem Abbruch an der Pinselgrenze.

Invers quadratisch: Eine Mischung aus weich und kugelförmig für ein natürliches, „tonartiges“ Gefühl.

In allen Fällen gilt d = distance_to_center / brush_radius, begrenzt auf [0, 1]. Der Abklingwert wird mit der Pinselstärke multipliziert, um die tatsächliche Feldänderung an jedem Punkt zu bestimmen.

Abklingraum

Blender unterscheidet zwischen kugelförmigem Abklingen – der Abstand wird im dreidimensionalen Weltraum berechnet – und projiziertem Abklingen – der Abstand wird im zweidimensionalen Bildschirmraum berechnet. Projiziertes Abklingen bedeutet, dass zwei Punkte, die auf dem Bildschirm nahe beieinander erscheinen, gleich stark beeinflusst werden, selbst wenn sie sich im Weltraum in sehr unterschiedlichen Tiefen befinden. Für die Terrainformung ist Abklingen im dreidimensionalen Weltraum normalerweise intuitiver.

Grundlegende Pinseloperationen

Anheben/Absenken (Verschiebung): Innerhalb des Pinselradius wird ein mit dem Abklingprofil gewichteter Wert zum skalaren Feld addiert oder davon subtrahiert. Für Heightmaps: height[i] += strength * falloff(d). Für SDFs: sdf[i] -= strength * falloff(d) (Subtrahieren macht das Material fester und hebt die Oberfläche an).

Glätten (Laplacian): Jeder Wert wird durch den gewichteten Mittelwert seiner Nachbarn ersetzt. Dadurch werden Details entfernt und Rauschen reduziert. Der Laplace-Filter tastet einen kleinen Kernel ab – 3x3 für Heightmaps, 3x3x3 für Volumen – und überblendet zum Mittelwert. HC-Glättung (Humphrey's Classes) ist eine Variante, die das Volumen besser als eine reine Laplace-Glättung bewahrt.

Abflachen: Der Feldwert wird auf eine Zielhöhe oder einen Abstand im SDF-Raum gesetzt und anhand des Abklingprofils überblendet. Das Ziel wird normalerweise zu Beginn des Pinselstrichs im Pinselzentrum abgetastet und anschließend konstant gehalten. Dadurch entstehen flache Plateaus.

Zusammenziehen/Aufblasen: Vertices werden zur Oberflächennormalen hin oder von ihr weg verschoben. Im SDF-Raum entspricht dies einer Verschiebung entlang der Gradientenrichtung.

Ziehen: Ein Bereich des Felds wird verschoben, als würde man Ton ziehen. Der Verschiebungsvektor entspricht der in den Weltraum projizierten Mausbewegung und wird auf Feldwerte innerhalb des Radius angewendet.

Rauschen: Fügt dem Feld innerhalb des Pinselradius prozedurales Rauschen hinzu. Nützlich, um glatte Oberflächen aufzurauen.

Stempel: Wendet ein 2D-Graustufenbild als Verschiebung an und projiziert es auf die Oberfläche unter dem Cursor. Das Landscape-Werkzeug von Unreal Engine unterstützt dies für Terrainpinsel.

Adaptive Tessellation

sculpt-3D (Browser-Formung mit React + Three.js) implementiert adaptive Tessellation: Während sich der Pinsel über das Mesh bewegt, werden Dreiecke nahe dem Pinselzentrum unterteilt, um mehr Vertices für die Verformung bereitzustellen. Dadurch wird das Problem der „Low-Poly-Streckung“ vermieden, bei dem ein grobes Mesh durch die Formung verzerrt wird. Die Unterteilung nutzt symmetrisches Splitting für eine gleichmäßige Dreiecksqualität.

Bei volumetrischen Systemen wird adaptive Tessellation nicht auf dieselbe Weise benötigt, da das Mesh aus dem Feld neu erzeugt wird. Stattdessen kann die Voxelauflösung lokal in der Nähe von Bearbeitungen erhöht werden – etwa durch adaptive Octrees –, um denselben Effekt zu erzielen.

SDF-Formung: Der Ansatz von Dreams

Dreams von Media Molecule (PS4, 2020) ist das ambitionierteste jemals veröffentlichte Formungssystem innerhalb eines Spiels. Alex Evans stellte den technischen Ansatz auf der SIGGRAPH 2015 vor.

Repräsentation

Dreams speichert Geometrie als zusammengesetzte SDF-Funktion in 83^3 großen fp16-Volumentexturblöcken. Jede Skulptur besteht aus einer Liste von 1 bis 100.000 „Bearbeitungen“, wobei jede Bearbeitung eine CSG-Operation – Hinzufügen, Subtrahieren oder Einfärben – mit einer primitiven Form wie Kugel, Würfel, Zylinder, Kegel, Ellipsoid oder Torus sowie einem Überblendmodus ist. Mischmodi verwenden Soft-Max- und Soft-Min-Funktionen. Eine „weiche“ Mischung erzeugt abgerundete Übergänge zwischen Primitiven (wie zusammengedrückter Ton). Eine „harte“ Mischung erzeugt scharfe boolesche Schnitte. Der Mischradius kann vom Benutzer gesteuert werden.

Rendering

Dreams extrahiert kein Dreiecksnetz. Stattdessen rendert es mithilfe eines benutzerdefinierten Punktwolken-Renderers („Flecks“) direkt aus dem SDF. Jeder Fleck ist eine winzige Scheibe, die entlang der Oberflächennormalen ausgerichtet ist. Das SDF wird abgetastet, um Oberflächen zu finden, auf denen anschließend Flecks verteilt werden. Dadurch entfällt der Engpass der Netzextraktion vollständig, allerdings ist ein benutzerdefinierter Renderer erforderlich.

Für eine Three.js-/WebGL-Welt ist dieser Ansatz nicht direkt anwendbar. Wir müssten Netze extrahieren. Das Konzept einer CSG-Bearbeitungsliste ist jedoch für Rückgängig/Wiederholen und die Netzwerksynchronisierung äußerst relevant.

Mike Turitzins dynamische SDF-Engine (2026)

Eine derzeit von Mike Turitzin entwickelte Game-Engine verwendet dynamische SDFs als zentrale Repräsentation. Die Engine unterstützt:

Detaillierte Änderungen während des Spielablaufs: Materie kann weich oder mit scharfen Kanten hinzugefügt und entfernt werden. Möglich sind auch nichtdestruktive Änderungen wie das Verschieben von Löchern oder das Erstellen temporärer Tunnel, die hinter dem Spieler verschwinden.

Brick Maps und Brick-Atlanten für Sparse-Caching. Anstatt das gesamte SDF-Feld in einem dichten 3D-Raster zu speichern, wird das Feld in „Bricks“ (kleine 3D-Kacheln) unterteilt. Nur Bricks, die eine Oberflächengrenze enthalten, werden zugewiesen. Dadurch sinkt der Speicherbedarf für Szenen, die überwiegend aus leerem oder massivem Raum bestehen, drastisch.

Geometrie-Clipmaps (Losasso & Hoppe, SIGGRAPH 2004) für LOD. Verschachtelte regelmäßige Raster mit zunehmender Auflösung umgeben die Kameraposition. Das innerste Raster hat die feinste Auflösung; äußere Raster werden zunehmend gröber. Dies reduziert den Speicherbedarf drastisch und unterstützt gleichzeitig riesige Räume. Clipmaps werden schrittweise aktualisiert, wenn sich die Kamera bewegt, wodurch sie sich effizient für das Streaming offener Welten eignen.

Physik und Kollisionen arbeiten direkt mit dem SDF. Sphere-Tracing (Ray Marching mit der SDF-Distanz als Schrittweite) ermöglicht effizientes Raycasting. Die Kollisionserkennung verwendet den SDF-Gradienten als Oberflächennormale und den Distanzwert als Eindringtiefe.

Teardown: Voxelzerstörung in großem Maßstab

Teardown (Voxagon) repräsentiert das andere Ende des Spektrums: Jedes Objekt in der Welt ist ein Voxelvolumen, das Stück für Stück zerstört werden kann.

Architektur

Objekte werden als Voxelraster mit regelmäßigen Abständen gespeichert. Die Engine verwendet weder Marching Cubes noch SDFs für das Rendering. Stattdessen führt sie mithilfe eines modifizierten DDA-Algorithmus (Digital Differential Analyzer) in Fragment-Shadern direktes Raytracing der Voxel durch, basierend auf OpenGL 3.3. Mipmaps bilden eine dichte Octree-Struktur, um beim Schnittpunkttest von Strahlen leeren Raum schneller zu durchqueren.

Für jedes Objekt rastert die Engine dessen ausgerichteten Begrenzungsquader (OBB) und verfolgt einen Strahl durch ihn, um Voxelschnittpunkte zu finden. Es werden nur die Rückseiten des OBB gerendert, sodass die Kamera in das Begrenzungsvolumen eindringen kann.

Synchronisierung der Zerstörung (Mehrspieler)

Das Mehrspieler-Update von Teardown vom März 2026 verwendet einen semideterministischen Ansatz. Strukturelle Zerstörung (Löcher schneiden, Besitzverhältnisse ändern, Verbindungen wiederherstellen) wird mithilfe von Festkomma-Ganzzahlarithmetik über einen zuverlässigen Netzwerkstream verarbeitet. Alle Clients führen dieselben deterministischen Befehle aus und gelangen zum selben Weltzustand. Nichtstrukturelle Änderungen (Trümmer, Partikel) verwenden eine unzuverlässige Zustandssynchronisierung.

Dies ist eine wichtige Erkenntnis für unsere Mehrspielerwelt: Geländebearbeitungen müssen deterministisch sein. Wenn Spieler A einen Berg formt, müssen alle Clients aus denselben Felddaten dasselbe Netz erzeugen. Die Bearbeitungsbefehle (Pinselposition, Radius, Stärke, Operationstyp) sollten die maßgeblichen Daten sein, nicht das daraus resultierende Netz.

ALICE-SDF: Komprimierung und CSG-Bäume

ALICE-SDF (Adaptive Lightweight Implicit Compression Engine, v1.3.0 vom März 2026) bietet eine Rust-Implementierung SDF-basierter räumlicher Daten mit einer gegenüber Polygonnetzen 10- bis 1000-fachen Komprimierung. Unterstützt werden:

126 Bausteine: 72 Primitive, 24 Operationen, 7 Transformationen und 23 Modifikatoren. Weiche Mischoperationen (Vereinigung, Subtraktion, Schnittmenge) sowie Fasen- und Treppenmischungen für Abschrägungen mit harten Kanten und abgestufte CSG-Übergänge.

Diff/Patch für CSG-Bäume für Rückgängig/Wiederholen und Netzwerksynchronisierung. Dies ist die Schlüsselfunktion für das gemeinsame Formen im Mehrspielermodus: Anstatt den gesamten Feldzustand zu senden, übermittelt man die strukturelle Differenz zwischen zwei CSG-Bäumen. Der Client wendet den Patch an, um den neuen Zustand zu rekonstruieren. Dies ist wesentlich bandbreiteneffizienter als die Delta-Komprimierung roher Voxeldaten.

Optimierung von CSG-Bäumen, einschließlich der Entfernung von Identitätstransformationen, der Zusammenführung verschachtelter Transformationen und der Herabstufung von Modifikatoren. So bleibt der Baum kompakt, während sich Bearbeitungen ansammeln.

Netzgenerierung sowohl über Marching Cubes als auch über Dual Contouring. Die physikalische Kollisionserkennung arbeitet direkt mit dem SDF.

WebAssembly-Unterstützung ermöglicht die Integration in Browser. Die Engine ist in Rust geschrieben und verfügt über WASM-Bindings, wodurch sie eine realistische Option für eine Three.js-Anwendung darstellt.

WebGPU-Compute für Gelände

WebGPU ist seit Ende 2025 in allen großen Browsern verfügbar. Chrome 113+, Edge 113+, Firefox 141+ und Safari 26+ liefern es standardmäßig aktiviert aus. Dadurch werden Compute-Shader-Pipelines möglich, die zuvor ausschließlich der GPU vorbehalten waren.

Leistung

WebGPU-Compute-Shader erzeugen Gelände durch massive Parallelisierung ungefähr 100-mal schneller als CPU-Methoden. Die GPU führt Tausende Berechnungen gleichzeitig aus, und die Geländegenerierung ist nahezu vollständig parallelisierbar (jeder Vertex bzw. jedes Voxel ist unabhängig).

Die Arbeit ist in drei Ebenen organisiert: Dispatch-Ebene (Verteilung der Arbeitslast auf der GPU), Workgroup-Ebene (gemeinsam genutzter Speicher innerhalb einer Verarbeitungseinheit) und Thread-Ebene (einzelne Berechnungen). WGSL (WebGPU Shading Language) ist die Shader-Sprache.

Pipeline für das Formen von Gelände in Echtzeit

Eine WebGPU-Pipeline zum Formen würde folgendermaßen aussehen:

  1. Pinselanwendung (Compute-Shader): Aktualisiert die Werte des Skalarfelds innerhalb des Pinselradius. Jeder Thread verarbeitet ein Voxel. Er liest die Pinselparameter (Position, Radius, Stärke, Abfalltyp, Operation) aus einem Uniform-Puffer und wendet die Änderung an.

  2. Netzextraktion (Compute-Shader): Führt Surface Nets oder Marching Cubes in der geänderten Region aus. Der WebGPU SDF Editor von Nijhoff implementiert dies als mehrstufige Pipeline: räumliche Unterteilung in 16.384 Zellen, Octree-basierte Zellteilung und anschließende Oberflächenextraktion.

  3. Aktualisierung des Vertex-Puffers (GPU-seitig): Schreibt die extrahierten Vertices direkt in einen Renderpuffer, ohne den Umweg über den CPU-Speicher.

  4. Normalenberechnung (Compute-Shader): Berechnet die Vertexnormalen aus dem Netz oder dem SDF-Gradienten.

  5. Rendering (Standardpipeline): Zeichnet das Netz mit standardmäßigen PBR-Materialien.

Die Schritte 1–4 können vollständig auf der GPU ausgeführt werden, ohne dass Daten an JavaScript zurückgegeben werden. Die CPU muss lediglich in jedem Frame die Pinselparameter senden.

Der WebGPU SDF Editor

Reinder Nijhoffs WebGPU SDF Editor (Januar 2026) demonstriert diesen Ansatz in Chrome. Er unterstützt sechs Primitive (Kegel, Zylinder, Kapsel, Torus, Quader, Kugel), drei Mischoperationen (Vereinigung, Subtraktion, Schnittmenge) mit konfigurierbarer weicher Mischung sowie hierarchische Szenengraphen. Jedes Primitiv belegt 112 Byte in einem einzelnen GPU-Puffer.

Die Rendering-Pipeline verwendet 1.024 Shadow-Maps für Ambient Occlusion und temporales Anti-Aliasing. Auf leistungsstarken GPUs läuft sie mit interaktiven Frameraten.

Heightmap-Formung: Der einfachere Weg

Wenn keine Höhlen und Überhänge unterstützt werden müssen, lässt sich durch Heightmap-Formung die gesamte volumetrische Pipeline vermeiden. So handhaben die meisten veröffentlichten Spiele die Geländebearbeitung.

Implementierungsmuster zur Laufzeit

Unity Runtime Terrain (JohannHotzel, Januar 2026) demonstriert das Standardmuster:

  1. Raycast von der Kamera durch die Mausposition, um den Trefferpunkt auf dem Gelände zu bestimmen.
  2. Abbildung des Trefferpunkts auf Heightmap-Koordinaten.
  3. Anwendung des Pinsels auf benachbarte Heightmap-Werte, gewichtet nach Abfall.
  4. Aktualisierung des Netzes, indem die Y-Positionen der Vertices anhand der geänderten Heightmap gesetzt werden.
  5. Neuerstellung des Physik-Colliders, damit er dem neuen Netz entspricht.

Für unser Three.js-Gelände lassen sich die Schritte 1–4 direkt auf die vorhandene Architektur übertragen. Die Klasse Chunk speichert bereits Heightmaps und erzeugt daraus Netze. Für das Hinzufügen von Formungsfunktionen wären folgende Komponenten erforderlich:

  • Ein Raycasting-System für Chunk-Netze (Three.js Raycaster)
  • Funktionen zur Pinselanwendung, die Werte von chunk.heightmap ändern
  • Aktualisierungen der Netz-Vertices (Y-Positionen setzen, Normalen neu berechnen)
  • Neuberechnung der Splat-Map für die betroffene Region (damit die Texturmischung die neue Neigung bzw. Höhe widerspiegelt)
  • Netzwerkübertragung der Bearbeitung (Pinselposition, Radius, Stärke, Operation) an andere Clients über das vorhandene WebSocket-Protokoll

Der Clipmap-Ansatz

Landow.dev beschreibt eine „wandernde Clipmap“ für Heightmap-Gelände: ein einzelnes Netz mit variabler Unterteilungsdichte, das dem Spieler folgt. Anstatt die Heightmap in separate Netze mit unterschiedlichen LOD-Stufen zu unterteilen (wie wir es derzeit tun), ist die Clipmap ein kontinuierliches Netz, das in Kameranähe dicht und an den Rändern grob ist.

Dadurch entfällt das LOD-Stitching vollständig. Das Netz besitzt einfach dort mehr Dreiecke, wo sie benötigt werden, und dort weniger, wo sie nicht gebraucht werden. Der Nachteil besteht darin, dass beim Formen ein einziges großes Netz statt einzelner Chunks aktualisiert werden muss, was bei umfangreichen Bearbeitungen kostspielig sein kann.

Nichtdestruktive SDF-Heightmaps

Landow.dev beschreibt außerdem eine Technik, bei der die Heightmap selbst aus einer SDF-Komposition erzeugt wird. Forminstanzen (Kugeln, Quader, Rauschfunktionen) werden in einem Compute-Shader mithilfe von CSG-Operationen zusammengesetzt, und die Ausgabe wird als Heightmap abgetastet. Dies ermöglicht nichtdestruktive Bearbeitung (jede Forminstanz kann jederzeit verschoben oder gelöscht werden), während die Einfachheit des Heightmap-Renderings erhalten bleibt.

Dies ist ein überzeugender Hybridansatz: Die Datenrepräsentation ist volumetrisch (SDF-CSG-Baum), der Rendering-Pfad verwendet jedoch ein gewöhnliches Heightmap-Netz. Von der SDF-Seite erhält man Rückgängig/Wiederholen und netzwerkfreundliche Bearbeitungsoperationen, von der Heightmap-Seite einfaches Rendering und unkomplizierte Physik. Die Einschränkung bleibt bestehen: keine Höhlen oder Überhänge.

Sparse Voxel Octrees für große Welten

Dichte 3D-Raster sind nicht skalierbar. Eine Welt mit einer Seitenlänge von jeweils 1 km und einer Auflösung von 0,5 m würde 8 Milliarden Voxel benötigen. Sparse Voxel Octrees (SVOs) lösen dieses Problem, indem sie den Raum rekursiv unterteilen und Speicher nur für Oktanten zuweisen, die eine Oberflächengrenze enthalten.

Ein SVO bietet von Natur aus hierarchisches LOD: Die Baumtiefe an einem beliebigen Punkt bestimmt die effektive Auflösung. In der Nähe des Spielers ist der Baum vollständig erweitert (maximale Detailstufe). In großer Entfernung wird er auf einer gröberen Ebene abgeschnitten.

Für das Rendering können SVOs direkt per Raytracing dargestellt werden (eine Netzextraktion ist nicht erforderlich). Ein GPU-Ray-Marcher schneidet Strahlen auf jeder Baumebene mit achsenparallelen Quadern und überspringt dabei leere Unterbäume vollständig. Dadurch werden die Overdraw-Probleme des Chunk-basierten Renderings beseitigt und Artefakte des Greedy Meshings vermieden.

Der Vulkan-basierte SVO-Builder von AdamYuan demonstriert eine beachtliche Leistung: 19 ms Erstellungszeit für Crytek Sponza bei einer Auflösung von 2^10 auf einer GTX 1660 Ti.

Für die Formung lassen sich SVOs effizient ändern: Nur die Blattknoten innerhalb des Pinselradius müssen aktualisiert werden, und die Baumstruktur verarbeitet unterschiedliche Auflösungen auf natürliche Weise. Das Hinzufügen von Details an Stellen, an denen der Spieler formt (Aufteilen von Knoten für eine höhere Auflösung), und das Entfernen von Details an Stellen, an denen er glättet (Zusammenführen von Knoten für eine niedrigere Auflösung), ergibt sich unmittelbar aus der Datenstruktur.

Die Herausforderung bei der Bereitstellung im Browser besteht darin, dass WebGL keine Compute-Shader unterstützt. WebGPU unterstützt sie zwar, doch die Algorithmen zur Konstruktion und Traversierung von SVOs sind in WGSL komplex zu implementieren.

Netzwerksynchronisierung für das Formen im Mehrspielermodus

Unsere Welt unterstützt bereits Mehrspielerfunktionen über Cloudflare Durable Objects (world-chunk-do.ts). Das Hinzufügen von Formungsfunktionen bedeutet, Geländeänderungen zwischen allen verbundenen Clients zu synchronisieren.

Delta-Komprimierung

Das Senden roher Voxeldaten ist kostspielig. Eine Studie der Universität Oulu aus dem Jahr 2024 erzielte durch die Kombination von Delta-Codierung und DEFLATE-Komprimierung eine 2- bis 8-fache Verbesserung der Nutzdatengröße und komprimierte Voxelaktualisierungen auf weniger als ein Byte pro Voxel. Der SDEC-Codec demonstriert eine bitgepackte Delta-Codierung, die durchschnittliche Pakete von 259 Byte erzeugt, gegenüber 1.114 Byte bei generischer Serialisierung.

Operationsbasierte Synchronisierung (empfohlen)

Anstatt den Feldzustand zu synchronisieren, werden Operationen synchronisiert. Jede Formungsaktion wird zu einer Nachricht:

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
  }
}

Der Server überträgt diese Nachricht an alle Clients, und jeder Client wendet dieselbe deterministische Pinseloperation auf seine lokalen Geländedaten an. Dies ist derselbe Ansatz, den Teardown für strukturelle Zerstörung verwendet: deterministische Befehle über einen zuverlässigen Stream.

ALICE-SDFs Diff/Patch für CSG-Bäume führt diesen Ansatz noch weiter: Anstelle einzelner Pinselstriche repräsentiert die Differenz die strukturelle Änderung des gesamten CSG-Baums. Dies ermöglicht effizientes Rückgängig/Wiederholen über das Netzwerk (durch Senden des inversen Patches), und später beitretende Clients können den vollständigen Weltzustand durch erneutes Abspielen des Operationsprotokolls rekonstruieren.

Priorisierung und Drosselung

Geländebearbeitungen in der Nähe verbundener Spieler sollten hohe Priorität haben (sofortige Übertragung). Bearbeitungen, die weit von allen Spielern entfernt sind, können gebündelt und mit geringerer Frequenz gesendet werden. Das Voxel-Netzwerk von Enshrouded verwendet dieses Muster: Aktualisierungen mit 60 Hz für Gelände in Spielernähe und 10 Hz für Hintergrundregionen.

ZSTD-Komprimierung bei der Übertragung reduziert die Paketgröße von Geländeaktualisierungen um bis zu 60 %.

Gemeinsames Formen: Gleichzeitige Bearbeitungen

Wenn mehrere Spieler dieselbe Region gleichzeitig formen, ist eine Konfliktlösung erforderlich. cSculpt (CNR Visual Computing Lab, 2016) löste dies mit einem Zusammenführungsalgorithmus für mehrere Auflösungsstufen. Jede Bearbeitung wird auf mehreren Skalen repräsentiert, und gleichzeitig erfolgende, sich überschneidende Bearbeitungen werden durch Mischen ihrer Mehrfachauflösungsrepräsentationen zusammengeführt. Für unsere Zwecke genügt ein einfacherer Ansatz: Last-Write-Wins mit serverseitiger Reihenfolge. Das Durable Object versieht jede Bearbeitung mit einem Zeitstempel und überträgt sie in dieser Reihenfolge. Alle Clients wenden die Bearbeitungen in derselben Abfolge an. Da Pinselstriche klein, lokal begrenzt und additiv beziehungsweise subtraktiv sind, lässt sich das visuelle Ergebnis geringfügig anders angeordneter gleichzeitiger Bearbeitungen normalerweise nicht von der „korrekten“ Reihenfolge unterscheiden.

INST-Sculpt: Bearbeitung neuronaler SDFs (Forschungsfront)

INST-Sculpt (arxiv 2502.02891, Februar 2025) ermöglicht die strichbasierte Bearbeitung neuronaler SDFs. Nutzer zeichnen Striche auf die Oberfläche, und das System verformt das zugrunde liegende neuronale Feld entlang röhrenförmiger Bereiche um den Strichpfad. Benutzerdefinierte Pinselprofile (konfigurierbare Querschnitte) steuern die Form der Verformung.

Das ist für KI-generiertes Terrain interessant: Wenn die Basiswelt als neuronales SDF dargestellt wird (ein kleines neuronales Netz, das 3D-Koordinaten auf einen vorzeichenbehafteten Abstand abbildet), verändert das Sculpting die Gewichte des Netzes statt expliziter Voxeldaten. Die Darstellung ist äußerst kompakt (wenige MB für eine ganze Welt), aber die Auswertung ist aufwendiger als bei einer Lookup-Tabelle.

Diese Technologie befindet sich noch im Forschungsstadium. Der Inferenzaufwand neuronaler SDFs auf Consumer-Hardware ist derzeit zu hoch für den Echtzeiteinsatz in Spielen. Es lohnt sich jedoch, die Entwicklung zu beobachten, insbesondere da sich die Shader-Fähigkeiten von WebGPU verbessern und die Modellinferenz schneller wird.

World Creator 2026.3: Aktueller Stand kommerzieller Terrain-Werkzeuge

World Creator (BiteTheBytes, März 2026) repräsentiert den aktuellen Stand kommerzieller Werkzeuge zur Terrain-Erstellung. Version 2026.3 ergänzte GPU-basierte Terrain-Generierung mit automatischer Terrain-Anpassung (das Terrain passt sich platzierten Objekten an), kamerafokussierte Objektverteilung zur LOD-Optimierung und den Import realer Höhendaten (GeoTIFF, HGT, DTED).

Seitdem ergänzte World Creator 2026.4 (28. April 2026) mathematische Ausdrücke in numerischen Feldern, das Überblenden von Terrain-Normalen, um zentrale Objekte sauber in die Oberfläche einzubetten, vollständige Decal-Unterstützung und eine VRAM-Skalierung, die die maximale Objektanzahl an den verfügbaren GPU-Speicher anpasst. BiteTheBytes veröffentlichte außerdem eine kostenlose Community Edition, die sämtliche Funktionen bietet, aber keinen Export erlaubt – praktisch eine unbegrenzt nutzbare Testversion.

Der Ansatz nutzt GPU-Compute für sämtliche Terrain-Operationen: Erosionssimulation, das Ausheben von Flussläufen und das Bemalen von Texturen. Die Pinselwerkzeuge sind GPU-beschleunigt und geben Echtzeit-Feedback im Viewport. Das entspricht der oben beschriebenen WebGPU-Compute-Pipeline, die auf Desktop-GPUs ausgeführt wird.

Was wir bereits entwickelt haben: 24 Spikes und eine produktive Welt

Das Verzeichnis world/spikes/ enthält 24 eigenständige Prototypen. Dabei handelt es sich nicht um einfache Spielzeug-Demos. Sie bilden eine fortlaufende F&E-Pipeline, in der jeder Spike ein bestimmtes Problem löste, die Lösung anhand eines Zielwerts benchmarkte und Erkenntnisse für den nächsten Spike lieferte. Das Sculpting-System baut auf allen Spikes auf, nicht nur auf den späteren volumetrischen.

Das produktive Heightmap-Terrain (world/client/)

Die Live-Welt verwendet ein in Chunks aufgeteiltes Heightmap-System in Three.js WebGL:

  • noise.ts erzeugt die Terrain-Höhe per FBM-Value-Noise (5 Oktaven für Hügel, 4 für Gebirgskämme, 3 für Mikrodetails) mit einer deterministischen Funktion terrainHeight(wx, wz)
  • chunk.ts erzeugt PlaneGeometry-Meshes aus Float32Array-Heightmaps mit 3 LOD-Stufen (32/8/4 Segmente pro 64 Einheiten großem Chunk) und platziert instanzierte Bäume/Billboards mit Seed-basierter Zufallsplatzierung und Kollidern pro Objekt
  • chunk-manager.ts streamt Chunks ringförmig um den Spieler (Radius 1 bei LOD0, Radius 3 bei LOD1, Radius 6 bei LOD2), verbindet Kanten per linearer Interpolation in stitchEdge() und stellt getHeight(), getNormal() und resolveCollisions() für die Physikschicht bereit
  • terrain-material.ts führt über MeshStandardMaterial.onBeforeCompile eine Splat-basierte Texturüberblendung mit 4 Ebenen (Gras/Fels/Sand/Erde) und neigungs- sowie höhengesteuerten Gewichtungen durch, ergänzt um die Überblendung der Normal Map jeder Ebene
  • character-controller.ts tastet in jedem Frame die Terrain-Höhe für Schwerkraft, Bodenkontakt und die Zurückweisung zu steiler Hänge ab (maximale Neigung: Kosinus von 50 Grad). Das Sculpting muss diesem System veränderte Höhen sofort zuführen, sonst fällt der Spieler durch das bearbeitete Terrain
  • placement.ts verfügt bereits über einen Raycaster, der für das Werkzeug zur Objektplatzierung Chunk-Meshes trifft. Das Pinselwerkzeug sollte genau diesem Muster folgen, statt Raycasting von Grund auf neu zu implementieren
  • protocol.ts definiert MessagePack-kodierte Nachrichten für die Multiplayer-Synchronisierung über das Durable Object world-chunk-do.ts, das derzeit die Nachrichten PlayerState, PlaceObject, RemoveObject und Snapshot verarbeitet. Terrain-Bearbeitungen benötigen einen neuen Nachrichtentyp
  • world-chunk-do.ts (Cloudflare Worker) speichert platzierte Objekte im Durable-Object-Speicher und sendet sie in Intervallen von 50 ms an verbundene Spieler. Terrain-Veränderungen sind darin bislang nicht vorgesehen

Spikes 01–11: Die Basisschicht

Diese Spikes validierten die Kernsysteme, von denen das Sculpting abhängen wird. Wer sie überspringt, übersieht Randbedingungen, die das Sculpting-System berücksichtigen muss.

Spike 01 (Terrain + Instancing): Der erste Terrain-Prototyp in Three.js. Etablierte das Muster aus PlaneGeometry und Heightmap sowie die instanzierte Objektplatzierung, die chunk.ts noch immer verwendet.

Spike 02 (Rapier Physics Worker): Rapier 3D läuft in einem Web Worker mit einem ColliderDesc.heightfield()-Collider. Entwickelte einen kinematischen Character Controller mit automatischem Übersteigen von Hindernissen, Neigungsgrenzen und Snap-to-Ground. Dieser Spike bewies, dass die Physik außerhalb des Hauptthreads mit einem Höhenfeld arbeiten kann. Wenn wir das Terrain bearbeiten, muss das physikalische Höhenfeld neu erzeugt oder bei MC-Chunks durch einen Trimesh-Collider ersetzt werden.

Spike 05 (LLM-Verhaltensweisen): Hat keinen direkten Bezug zum Terrain, etablierte aber das JSON-Verhaltensschema für Spielobjekte. Das ist relevant, weil modellierte Terrain-Merkmale Verhaltensweisen auslösen könnten (zum Beispiel erzeugt ein ausgehobener Fluss Wassereffekte).

Spike 06 (Chunk-Streaming): Das erste System zum Laden und Austauschen von Chunks mit dynamischem Nachladen, während sich der Spieler bewegt. Etablierte das von chunk-manager.ts verwendete Muster: farbige Regionen, die geladen und entladen werden. Das Sculpting muss den Bearbeitungszustand erhalten, wenn Chunks entladen und erneut geladen werden.

Spike 07 (GPU-Vegetation aus Dichtekarten): Instanziertes Gras und instanzierte Bäume, platziert über Dichtekarten, welche Terrain-Höhe und Neigung abtasten. Sculpting macht die Vegetationsplatzierung ungültig: Wenn sich die Terrain-Höhe ändert, können Bäume schweben oder im Boden versinken. Die Dichtekarte muss für bearbeitete Chunks neu erzeugt werden.

Spike 08 (Shader-Kosten des Terrain-Materials): Benchmarkte triplanare Projektion, Normal Maps und eine Überblendung mit 4 Ebenen. Erfasste die exakten Kosten jeder Funktion in Millisekunden. Das Ergebnis: Triplanar + Normalen + 4 Ebenen bleiben bei mindestens 45 FPS im vorgesehenen Budget. Dieses Budget ist für modelliertes Terrain wichtig: Wenn wir eine 5. Ebene für „bearbeitete Erde“ hinzufügen oder die Überblendung für ausgehobene Flächen ändern, wissen wir genau, wie viel Spielraum verfügbar ist.

Spike 09 (CSM-Schattenbudget): Cascaded Shadow Maps mit 3 Kaskaden bei einer Auflösung von 1024^2. Die gemessenen Schattenkosten lagen bei etwa 1,5 ms. Modelliertes Terrain verändert die Shadow Maps, die Kosten bleiben jedoch unabhängig von der Terrain-Form konstant.

Spike 10 (Geometry Clipmaps + Geomorphing): Verschachtelte Clipmap-Ringe mit Geomorphing zwischen den LOD-Stufen, um sichtbares Aufpoppen zu vermeiden. Die konstante Dreiecksanzahl sorgt für vorhersagbare GPU-Kosten. Geomorphing ist für das Sculpting relevant: Wenn der Spieler nahe einer LOD-Grenze modelliert, muss der Übergang zwischen den LOD-Stufen die Bearbeitung widerspiegeln. Existiert die Bearbeitung nur im hochauflösenden Ring, ist das Geomorph-Ziel falsch.

Spike 11 (Heightmap-Chunk-Streaming): Weiterentwickeltes Chunk-Streaming mit einem visuellen Raster, das für jede LOD-Stufe geladene, gerade ladende und nicht geladene Zustände darstellt. Definierte das Streaming-Budget: maximale Anzahl geladener Chunks pro Frame und Priorisierung von Chunks, die ein LOD-Upgrade benötigen. Sculpting führt ein neues Prioritätssignal ein: Chunks, die der Spieler aktiv bearbeitet, dürfen niemals entladen werden.

Spikes 12–14: Integration von WebGPU und Three.js

Spike 12 (WebGPU Marching Cubes): Der erste volumetrische Spike. Vier 64^3-SDF-Chunks mit animierten kugelförmigen Höhlen, die vollständig auf der GPU laufen. Verwendet direktes WebGPU: Compute-Pipelines für die SDF-Auswertung, MC-Extraktion mit der Twinklebear-Falltabelle (256 Konfigurationen mit jeweils 16 Einträgen), einen atomaren Vertex-Zähler und indirektes Zeichnen. Das Leistungsziel lag bei <4 ms pro Chunk und <12 ms für alle 4. Damit wurde validiert, dass GPU-MC schnell genug für Echtzeit-Neuvermeshing im Browser ist. Jeder nachfolgende volumetrische Spike verwendet die hier definierten MC-Falltabellen und WGSL-Shader erneut.

Spike 13 (Zurücksetzen auf die Basis von Spike 12): Portierte den direkten WebGPU-Zeichenpfad aus Spike 12 zur Ausführung innerhalb des WebGPURenderer von Three.js, wobei direkt auf das device des Backends zugegriffen wird. Die Render-Pipeline verwendet weiterhin direktes WebGPU (drawIndirect mit struct Vertex vec4+vec4). Dies bewies, dass benutzerdefinierte Compute-Vorgänge und das Rendern von Three.js-Szenen auf demselben GPU-Gerät koexistieren können.

Spike 14 (Inkrementelle Härtung von Three.js WebGPU): Ersetzte die direkte Render-Pipeline durch StorageBufferAttribute von Three.js für Positionen und Normalen. Der MC-Compute-Vorgang schreibt direkt in diese GPU-residenten Puffer. Der drawIndirect-Puffer steuert, wie viele Vertices Three.js zeichnet. Dieses Muster verwenden alle nachfolgenden Spikes: Compute bleibt direktes WebGPU, das Rendering läuft über den Scene Graph von Three.js. Mit der Stabilisierung des WebGPU-Backends wurde die in diesen Spikes verwendete Three.js-Version schrittweise von 0.170.0 auf 0.172.0 aktualisiert.

Spikes 15–17: Transvoxel-LOD-Verbindungen

Spike 15 (Transvoxel-Nahtgerüst): Ergänzte die Architektur mit drei Zonen: MC-Chunk (volumetrisches Zentrum), Übergangsstreifen (Naht zwischen MC-Grenze und Heightmap) und Terrain-Ring (umgebende Heightmap). Alle drei verwenden denselben Material-Pass. Der Übergangsstreifen ist in diesem Stadium ein Platzhalter-Mesh und besteht noch nicht aus echten Transvoxel-Zellen.

Spike 16 (+X-Fläche mit Transvoxel und gemeinsamer Heightmap): Zwei entscheidende Durchbrüche in einem Spike. Erstens wurde die flache Terrain-Ebene im SDF durch eine gemeinsame Perlin-Heightmap ersetzt: ein 257x257 großes Float32Array, das als Storage Buffer auf die GPU hochgeladen und im SDF-Compute-Shader per bilinearer Interpolation abgetastet wird. Die MC-Oberfläche und das Heightmap-Mesh basieren nun auf derselben Ground Truth. Zweitens wurden echte Transvoxel-Übergangszellen für die +X-Fläche implementiert. Dazu wurden Eric Lengyels Referenzdatentabellen von GitHub (transitionCellClass, transitionVertexData, transitionCellData) sowie das npm-Paket transvoxel-data abgerufen. Die CPU wertet Übergangszellen mit 9 Samples aus (512 Konfigurationen, 73 Äquivalenzklassen), platziert Vertices durch Interpolation der SDF-Werte an Rasterpunkten und behandelt die Umkehrung der Umlaufrichtung für gespiegelte Fälle.

Spike 17 (Duales MC-LOD mit 1x/2x): Zwei MC-Chunks unterschiedlicher Auflösung nebeneinander. Hohe Auflösung: 62 Zellen mit cell_scale=1.0. Niedrige Auflösung: 31 Zellen mit cell_scale=2.0. Der MC-Shader erhielt die Uniforms cell_scale und grid_points. Führte transition_shrink ein: Die Rand-Vertices an Fläche 0 des niedrig aufgelösten Chunks werden um 15 % von cell_scale nach innen gezogen. Dadurch entsteht eine schmale Lücke, die Transvoxel-Übergangszellen ohne Z-Fighting füllen können. Das ist das LOD-Modell, das das produktive System benötigt: nahe Chunks mit voller Auflösung, entfernte Chunks mit halber Auflösung und Transvoxel an jeder Grenze.

Spikes 18–21: Transvoxel-Sonderfälle und GPU-Beschleunigung

Diese vier Spikes lösten jeweils einen bestimmten Fehlerfall der Transvoxel-Implementierung. Eine gemeinsame Zusammenfassung würde die unterschiedlichen Probleme verschleiern.

Spike 18 (Heightmap-Naht mit 2:1-Verhältnis): Wandte Transvoxel auf eine reine Heightmap-Grenze an, bei der eine Seite die doppelte Auflösung der anderen besitzt. MC kommt dabei nicht zum Einsatz. Die Naht zwischen einem Heightmap-Chunk mit 62 Zellen und einem mit 31 Zellen wird anhand von Transvoxel-Übergangstabellen erzeugt, wobei die niedrig aufgelöste Fläche um 15 % verkleinert wird. Damit wurde validiert, dass Transvoxel auch im reinen Heightmap-Fall funktioniert und nicht nur mit MC.

Spike 19 (64/32/32/16-Eckraster): Der schwierigste Verbindungsfall: Vier Chunks mit unterschiedlichen Auflösungen treffen an einem Eckpunkt zusammen (64, 32, 32 und 16 Zellen). Das Nahtsystem muss entlang von vier Kanten (A-B, A-C, B-D, C-D) Übergangszellen mit der jeweils korrekten Umlaufrichtung erzeugen. Dieser Spike bewies, dass die Transvoxel-Tabellen die Ecke mit mehreren Auflösungen ohne benutzerdefinierte Falllogik verarbeiten können.

Spike 20 (GPU-Transvoxel-Ecke): Verlagerte die Erzeugung der Transvoxel-Übergangszellen für die 64/32/32/16-Eckanordnung auf die GPU. Die CPU wurde zum Engpass, wenn die Übergangszellen für animiertes Terrain in jedem Frame neu erzeugt wurden. GPU-Compute erzeugt die Naht-Vertices im selben Pass wie die MC-Extraktion.

Spike 21 (GPU-MC + Transvoxel-Ecke): Kombinierte die vollständige GPU-MC-Extraktion mit der GPU-Erzeugung von Transvoxel-Nähten in einer einzigen Abfolge von Compute-Dispatches. Sowohl die MC-Chunks als auch alle vier Nähte werden auf der GPU erzeugt, wobei atomare Zähler die Vertex-Anzahlen verwalten und drawIndirect zum Zeichnen verwendet wird. Dies ist die vollständige GPU-Pipeline für volumetrisches Terrain mit mehreren Auflösungen und nahtlosen LOD-Übergängen.

Spikes 22–24: Die Hybridarchitektur

Spike 22 (Hybridrichtlinie für MC/Heightmap): Der entscheidende Architektur-Spike. Chunks sind standardmäßig Heightmaps. Wenn die animierte Verformungskugel die AABB eines Chunks schneidet, wechselt dieser Chunk in den MC-Modus. Alle übrigen bleiben statische Heightmap-Meshes. Anordnung: Chunks mit 64, 32/32 und 16 Zellen in unterschiedlichen Auflösungen. Transvoxel-Nähte verarbeiten jede Grenze, einschließlich der Übergänge von MC zu Heightmap. Der Spike erfasst pro Frame die Anzahl der MC-Chunks gegenüber den HM-Chunks sowie Vertex-Überläufe.

Spike 23 (Richtliniengesteuerte Chunk-Modi): Wurde als Patch auf Spike 22 geladen. Ergänzte eine Hysterese auf Grundlage der Kameradistanz (Chunks flackern nicht zwischen Modi, wenn sich die Kamera nahe einem Schwellenwert befindet) und eine Bearbeitungsmaske (verformte Chunks bleiben im MC-Modus, selbst wenn sich die Verformungsquelle entfernt). Dies ist das für Sculpting benötigte Verhalten dauerhaft gespeicherter Bearbeitungen: Sobald ein Spieler eine Höhle gräbt, bleibt dieser Chunk für immer volumetrisch.

Spike 24 (Richtlinie + Clipmap-Ringe): Der am weitesten entwickelte Spike. Kombiniert das Nahfeld-Richtliniensystem aus Spike 23 mit den Fernfeld-Geometry-Clipmap-Ringen aus Spike 10. Aktualisiert auf Three.js 0.183.1. Das Nahfeld verwendet den HM/MC-Hybrid mit Transvoxel-Nähten bei einer Auflösung von 64/32/16. Das Fernfeld verwendet statische, der Kamera folgende Clipmap-Ringe mit festem Zentrum. Dies ist die vollständige Terrain-Rendering-Architektur: in Chunks aufgeteiltes volumetrisches Sculpting, wo es benötigt wird, und kostengünstiges Clipmap-Terrain überall sonst.

Warum Marching Cubes statt Surface Nets

Der Abschnitt zur externen Forschung in diesem Leitfaden empfiehlt Surface Nets als stärksten Kandidaten für Terrain-Sculpting im Browser. Doch jeder Spike in der Pipeline verwendet Marching Cubes. Das ist kein Zufall.

Der entscheidende Vorteil von MC ist seine geradezu ideale Parallelisierbarkeit: Jeder Würfel ist vollständig unabhängig. Die WGSL-Compute-Shader in den Spikes 12–24 starten einen Thread pro Würfel, ganz ohne Kommunikation zwischen den Zellen. Atomare Zähler übernehmen die Vertex-Zuweisung. Das lässt sich perfekt auf GPU-Workgroups abbilden.

Surface Nets platziert einen Vertex pro Zelle, die eine Oberfläche enthält, und verbindet anschließend benachbarte Zellen. Diese Nachbarschaftsverknüpfung erzeugt eine Abhängigkeit zwischen den Zellen. Das fast-surface-nets-Crate löst dies auf der CPU durch eine sorgfältig gewählte Iterationsreihenfolge. Auf der GPU ist dafür entweder ein Ansatz mit zwei Durchläufen erforderlich – zuerst Vertices finden, dann verbinden – oder gemeinsam genutzter Speicher innerhalb der Workgroups. Beides ist mit WebGPU möglich, erhöht aber die Komplexität.

Die praktische Empfehlung lautet daher: Für die Sculpting-Pipeline sollte Marching Cubes beibehalten werden. Es hat sich in unserer Codebasis bewährt, die WGSL-Shader sind vorhanden und durch Benchmarks geprüft, und das Transvoxel-Nahtsystem ist auf der kantenbasierten Vertex-Platzierung von MC aufgebaut. Surface Nets sollte erneut in Betracht gezogen werden, falls das Aliasing von MC bei binären Daten zu einem sichtbaren Problem wird. Bei SDF-Terrain mit weichen Werteverläufen liefert MC jedoch saubere Ergebnisse.

Praktische Architektur für Sculpting

Die Spike-Reihe hat die Rendering-Pipeline gelöst. Offen sind noch das Pinselsystem, die kaskadierenden Auswirkungen auf die Spielsysteme und die Mehrspieler-Synchronisierung. Hier ist der Plan, der auf allen bisherigen Spikes aufbaut.

Phase 1: Heightmap-Sculpting (minimale Änderungen, maximale Reichweite)

Füge dem Produktionscode unter world/client/ Pinselwerkzeuge hinzu, die die Chunk-Heightmaps verändern. Das funktioniert mit dem vorhandenen WebGL-Renderer und erfordert kein WebGPU.

Pinseleingabe: Folge dem Muster des PlacementTool in placement.ts. Es verfügt bereits über einen Raycaster, der chunkManager.getChunkMeshes() trifft und am Trefferpunkt ein Geister-Mesh positioniert. Ein TerrainBrushTool würde denselben Raycast verwenden, aber statt eines Objekts die Chunk-Heightmap verändern. Der Handler World.onMouseDown leitet Eingaben bereits abhängig vom Werkzeugstatus weiter.

Chunk-Änderung (Chunk.applyBrush): Übertrage die Weltposition des Pinsels auf die Rasterkoordinaten der Heightmap. Berechne für jeden Rasterpunkt innerhalb des Pinselradius die mit dem Abfall gewichtete Verschiebung und addiere sie zum Heightmap-Wert oder ziehe sie davon ab. Aktualisiere anschließend das Mesh: Setze die Y-Positionen der Vertices anhand der veränderten Heightmap, berechne die Normalen mittels zentraler Differenzen neu – nach demselben Muster terrainHeight(wx +/- eps, wz), das bereits in chunk.ts in den Zeilen 155–158 verwendet wird – und erzeuge die Splat-Map für den betroffenen Bereich über createSplatMap() in terrain-material.ts neu, damit die neigungsabhängige Texturüberblendung aktualisiert wird.

Charakter-Controller: CharacterController.update() ruft für die Bodenhaftung in jedem Frame getHeight() auf. ChunkManager.getHeight() delegiert an Chunk.sampleHeight(), das aus dem Float32Array der Chunk-heightmap liest. Da dieses Array direkt verändert wird, übernimmt der Charakter-Controller die Änderung bereits im nächsten Frame, ohne dass zusätzliche Verknüpfungen nötig sind.

Objektinvalidierung: Bauminstanzen in chunk.ts werden beim Erzeugen durch Abtasten von terrainHeight() platziert. Nach dem Sculpting können sich Bäume im betroffenen Bereich auf der falschen Höhe befinden. In Phase 1 kann dies zurückgestellt werden; bei kleinen Änderungen schweben Bäume dann geringfügig. Phase 2 benötigt eine Methode chunk.invalidateObjects(), die Höhen erneut abtastet und die Instanzmatrizen neu aufbaut. Dasselbe gilt für die in resolveCollisions() verwendeten Collider.

Rapier-Physik (falls integriert): Spike 02 hat gezeigt, dass Heightfield-Collider funktionieren. Wenn Rapier aktiv ist, muss der Heightfield-Collider des veränderten Chunks neu aufgebaut oder aktualisiert werden. ColliderDesc.heightfield() von Rapier erwartet ein flaches Float32Array, sodass er direkt ersetzt werden kann.

Netzwerksynchronisierung: Füge MsgType.TerrainEdit = 10 zu protocol.ts hinzu:

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

Das WorldChunkDO sendet diese Nachricht an alle Clients und hängt sie an ein pro Chunk geführtes Änderungsprotokoll an, das im Durable-Object-Speicher abgelegt wird. Später beitretende Clients erhalten das Änderungsprotokoll in der Snapshot-Nachricht und spielen es ab, um den Terrainzustand zu rekonstruieren. Alle Clients wenden dieselbe deterministische Pinselfunktion an und gelangen dadurch zur gleichen Heightmap.

Entladen und erneutes Laden von Chunks: Spike 06 und Spike 11 haben das Streaming-Muster etabliert. Wenn ein Chunk entladen und später erneut geladen wird, muss das Änderungsprotokoll dieses Chunks auf die prozedurale Basis-Heightmap angewendet werden. Das Änderungsprotokoll wird serverseitig im Durable Object gespeichert und in die Snapshot-Nachricht aufgenommen.

Phase 2: Volumetrisches Sculpting mit der Architektur aus den Spikes 22–24

Übertrage die Pipeline aus Spike 24 in die Produktionswelt. Wenn ein Spieler unterhalb der Oberfläche sculptet – etwa um eine Höhle auszuhöhlen oder einen Tunnel zu graben –, wechselt der betroffene Chunk vom Heightmap-Modus in den MC-Modus.

Migration des WebGPU-Renderers: Die Spikes 13–14 haben gezeigt, dass der WebGPURenderer von Three.js benutzerdefinierte Compute-Vorgänge parallel zum Szenengraphen ausführen kann. Die Produktionswelt wechselt für MC-Chunks von WebGLRenderer zu WebGPURenderer mit StorageBufferAttribute. Ist WebGPU nicht verfügbar, wird als Fallback ausschließlich der Heightmap-Pfad aus Phase 1 verwendet.

SDF-Zuweisung pro Chunk: Folge dem hybriden Muster aus Spike 22. Jeder Chunk beginnt als Heightmap. Beim ersten volumetrischen Pinselstrich wird ein 64^3 großes Float32Array angelegt und durch Abtasten der Heightmap initialisiert. Der SDF-Wert an jedem Punkt lautet world.y - heightmap_value. Anschließend wird auf MC-Rendering umgeschaltet. Das Richtliniensystem aus Spike 23 stellt sicher, dass der Chunk dauerhaft im MC-Modus bleibt – das Verhalten „dauerhafte Änderung“ aus der Edit-Maske.

Gemeinsam genutzte Heightmap im SDF-Shader: Verwende die Funktion height_at() aus Spike 16. Lade die Heightmap des Chunks in einen GPU-Storage-Buffer hoch. Der SDF-Compute-Shader wertet max(height_sdf, edit_sdf) aus, wobei height_sdf = world.y - height_at(world.xz) gilt und edit_sdf die Pinseländerungen enthält. MC- und Heightmap-Chunks verwenden an ihren Grenzen dieselbe maßgebliche Terrainform.

Transvoxel-Nähte: Verwende den vollständigen Stack aus den Spikes 15–21. Grenzen zwischen MC und Heightmap nutzen Übergangszellen mit der verkleinerten Lücke. Grenzen zwischen MC-Chunks mit unterschiedlichen Auflösungen verwenden das Dual-LOD-Muster aus Spike 17. Der Sonderfall aus Spike 19 behandelt Kreuzungen von vier Bereichen. Der GPU-Compute-Shader aus Spike 21 erzeugt die gesamte Nahtgeometrie im selben Dispatch.

Clipmap-Fernbereich: Verwende die Clipmap-Ringe aus Spike 24 für Terrain außerhalb der Sculpting-Reichweite. Das Sculpting verändert diese Ringe nie; sie tasten die prozedurale Basis-Heightmap ab.

Geomorphing: Das Geomorphing aus Spike 10 verhindert sichtbares Aufploppen an LOD-Übergängen. Bei bearbeiteten Chunks muss das Geomorph-Ziel die Änderung berücksichtigen. Wenn ein Chunk auf LOD0 MC verwendet und sein LOD1-Nachbar eine Heightmap, überblendet das Geomorphing zwischen den beiden Darstellungen. Dafür muss das Änderungsprotokoll auch auf niedrigeren LOD-Stufen ausgewertet werden.

Materialbudget: Spike 08 hat vierlagiges triplanares Mapping mit Normalen bei mehr als 45 FPS getestet. MC-Chunks benötigen dasselbe Material. Die Splat-Map kann statt aus der Heightmap-Neigung aus dem SDF-Gradienten erzeugt werden: steil entspricht Fels, flach entspricht Gras. Das bleibt innerhalb des Budgets von vier Ebenen.

Vegetationsinvalidierung: Die dichtekartenbasierte Vegetation aus Spike 07 hängt von Terrainhöhe und -neigung ab. Wenn ein Chunk in den MC-Modus wechselt, müssen die Bauminstanzen durch Abtasten der SDF-Oberfläche neu erzeugt werden. Bäume auf Überhängen oder innerhalb von Höhlen müssen entfernt werden. Die Instanced-Mesh-Matrizen aus chunk.ts werden anhand der neuen Oberfläche neu aufgebaut.

Phase 3: CSG-Änderungsbäume für Rückgängig/Wiederholen und Netzwerksynchronisierung

Ersetze direkte SDF-Veränderungen durch einen CSG-Operationsbaum. Jeder Pinselstrich fügt ein Primitiv – Kugel, Kapsel oder Quader – mit einer Operation hinzu: hinzufügen, abziehen oder weich überblenden. Das SDF wird aus dem Baum neu berechnet.

Vorteile:

  • Nicht destruktiv: Jede Änderung kann zum Rückgängigmachen aus dem Baum entfernt werden
  • Netzwerkeffizient: Übertrage die CSG-Operation statt der rohen Feldwerte
  • Deterministisch: Alle Clients erzeugen aus derselben Operationsreihenfolge dasselbe SDF
  • Das Diff/Patch-Verfahren für CSG-Bäume von ALICE-SDF ermöglicht bandbreiteneffiziente Synchronisierung sowie Rückgängig/Wiederholen über das Netzwerk

Durable-Object-Speicher: Der Änderungsbaum pro Chunk ersetzt das flache Änderungsprotokoll aus Phase 1. Das WorldChunkDO speichert die Struktur des CSG-Baums statt roher Heightmap-Deltas. Snapshot-Nachrichten enthalten den Baum, und später beitretende Clients werten ihn aus, um das lokale SDF zu erzeugen.

Phase 4: Gemeinsames Sculpting

Füge Unterstützung für gleichzeitige Änderungen hinzu, deren Operationen in serverseitig festgelegter Reihenfolge abgespielt werden. Das Durable Object versieht jede Änderung mit einem Zeitstempel und sendet sie der Reihe nach. Später beitretende Clients erhalten das Operationsprotokoll und rekonstruieren daraus den Weltzustand. Der vorhandene Nachrichtentyp Snapshot wird um den Verlauf der Terrainänderungen pro Chunk erweitert.

Da Pinselstriche klein, lokal begrenzt und additiv oder subtraktiv sind, ist das visuelle Ergebnis geringfügig anders angeordneter gleichzeitiger Änderungen normalerweise nicht vom „korrekten“ Ergebnis zu unterscheiden. „Letzte Änderung gewinnt“ mit serverseitiger Reihenfolge ist ausreichend. Die Funktion tick() des Durable Objects – die derzeit für den Spielerstatus alle 50 ms ausgeführt wird – nimmt Terrainänderungen in dieselbe Übertragungsschleife auf.

Wichtige Referenzen

Algorithmen:

  • 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)

Implementierungen:

  • bevy-sculpter v0.18.0 (Rust, Surface Nets + SDF-Pinsel)
  • fast-surface-nets (Rust, 20 Mio. Dreiecke/s)
  • ALICE-SDF v1.3.0 (Rust + WASM, Diff/Patch für CSG-Bäume)
  • WebGPU SDF Editor (Nijhoff, Januar 2026)
  • SculptingPro (Unity-API für Laufzeit-Sculpting)
  • TerraBrush (Godot-GDExtension für Terrain-Sculpting)

Spiele:

  • Dreams (Media Molecule, SDF + Punktwolken-Rendering, SIGGRAPH 2015)
  • Teardown (Voxagon, Voxel-DDA-Raytracing, deterministische Mehrspieler-Zerstörung)
  • Mike Turitzins SDF-Engine (Brick Maps + Geometry Clipmaps, Januar 2026)

Netzwerk:

  • „Optimizing payload size for voxel state synchronization“ (Oulu, 2024)
  • Teardown-Mehrspielermodus (semideterministische Synchronisierung von Zerstörung, März 2026)
  • cSculpt (gemeinsames Mesh-Sculpting mit Zusammenführung mehrerer Auflösungsstufen)
Probier es gleich ausWeniger sculpten, früher spielen

Erstelle ein terraingesteuertes Spiel, ohne einen Pinsel anzurühren.

Kostenlos erstellen →Kostenlos, läuft im Browser, keine Installation nötig.