
A complete reference for building Death-Stranding-class eroded terrain in the browser, distilled from the sources below and from the iterative build of the ds2-terrain.html demo (Three.js r185, WebGPU, vanilla JS).
1 · Source Index
Every external link referenced in this project:
Erosion algorithms & implementations
- Procedural Pixels — Terrain hack: the fastest erosion algorithm ever (Hatchling’s slab filter)
https://www.proceduralpixels.com/blog/terrain-hack-fastest-erosion-algorithm-ever - Sebastian Lague — Hydraulic-Erosion (droplet method, C#/Unity, the most-copied reference implementation)
https://github.com/SebLague/Hydraulic-Erosion - Sebastian Lague — playable erosion demo
https://sebastian.itch.io/hydraulic-erosion - Hans Theobald Beyer — Implementation of a method for hydraulic erosion (bachelor thesis; the formal spec Lague’s code follows — the definitive droplet-parameter reference)
https://www.firespark.de/resources/downloads/implementation%20of%20a%20methode%20for%20hydraulic%20erosion.pdf - ranmantaru (Jákó Balázs) — Water erosion on heightmap terrain (independent droplet formulation with practical anti-artifact advice)
https://ranmantaru.com/blog/2011/10/08/water-erosion-on-heightmap-terrain/ - LanLou123 — Webgl-Erosion (interactive GPU shallow-water pipe model in WebGL: flux fields, sediment advection, hardness maps, thermal pass)
https://github.com/LanLou123/Webgl-Erosion - GameDev.net blog — Real-time hydraulic erosion using compute shaders (pipe model ported to OpenGL compute; ping-pong texture architecture)
https://web.archive.org/web/20250725173657/https://www.gamedev.net/blogs/entry/2277785-real-time-hydraulic-erosion-using-compute-shaders-opengl/ - GameDev.net forum — Hydraulic erosion on terrain height map (practitioner Q&A on droplet artifacts)
https://gamedev.net/forums/topic/710850-hydraulic-erosion-on-terrain-height-map/ - huw-man — Interactive Erosion Simulator on GPU (grid-based GPU erosion with interactive rain/brush)
https://huw-man.github.io/Interactive-Erosion-Simulator-on-GPU/ - Shadertoy
MtGcWh— heightmap erosion approximated entirely in a shader
https://www.shadertoy.com/view/MtGcWh - GPU Gems 3, Chapter 1 — Generating Complex Procedural Terrains Using the GPU (density-field terrain + marching cubes: the path to true overhangs and caves)
https://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-1-generating-complex-procedural-terrains-using-gpu - Chris Kempke — Unity Terrain Generation: Implementing Erosion (result-first “reverse shearing” via Voronoi region drops, spatially-varying PerlinGaussian blur, a field taxonomy of canyons, chunked-world seam warnings)
https://terrain.chriskempke.com/erosion-implementation/
Project documents (user-supplied)
ds2-graphics-spec-for-threejs.md— DS2/Decima full-pipeline feature spec mapped to Three.js.ds2-river-environment-spec-for-threejs.md— river/terrain/art-direction deep dive (stepped rivers, flowmaps, reduced palette, vista layering).
Academic anchors worth knowing (referenced by the sources above)
- Mei, Decaudin, Hu — Fast Hydraulic Erosion Simulation and Visualization on GPU (2007): the pipe model.
- Braun & Willett — A very efficient O(n), implicit and parallel method to solve the stream power equation (2013): fluvial incision.
- Musgrave, Kolb, Mace — The Synthesis and Rendering of Eroded Fractal Terrains (1989): thermal + hydraulic on fractals, the founding paper.
2 · The Big Picture
2.1 One-sentence philosophy
Realistic terrain is not one algorithm — it is a stack of cheap operations, each responsible for one octave of realism: shape → simulate → detail → texture → light.
2.2 The pipeline that worked
sculpt (fBM + ridged + masks + pow-shaping + terraces) 512²
→ stream-power fluvial erosion + uplift 512² macro drainage anatomy
→ droplet hydraulic erosion (records wear/deposit maps) 512² gullies + texture data
→ thermal relaxation (talus-angle gated) 512²
→ wash settling (masked blur = sediment deposit) 512²
→ upsample + downslope-aligned rill striations 1024²
→ SECOND stream-power pass, drainage-area capped 1024² fine gully network
→ Voronoi crack/plate crags (post-smoothing, steep-only) 1024²
→ flatness-weighted polish (smooth flats, detailed steeps)1024²
→ bake albedo + micro-normal textures 2048²
→ mesh + lateral fold displacement (pseudo-overhangs) 1024² grid ≈ 2.1M tris
Key structural insight: simulate coarse, detail fine, texture finer. Each stage runs at the cheapest resolution that can represent its feature size.
3 · Terrain Composition (before any erosion)
3.1 fBM — the workhorse
Sum noise octaves, halving amplitude and doubling frequency. Gives hills + bumps + detail in one call. Amplitude spectrum matters more than the noise flavor: real ground is ~1/f — if a frequency band is too loud relative to its wavelength, the terrain looks “cartoony” (see §6.1).
3.2 Ridged multifractal — mountains
n = (1 − |noise|)², octaves weighted by the previous octave’s value. Produces sharp crests and connected ridge systems. Raise to a power (pow(r, 1.35)) to sharpen peaks further.
3.3 pow() shaping — controlling the distribution
h = pow(fbm01, k): with k > 1, most terrain flattens toward plains while peaks survive. This single line changes the feel of a world more than any noise swap. Caution learned the hard way: Math.pow(negative, fractional) is NaN in JS — clamp inputs, because “fbm01” can dip fractionally below 0 (simplex isn’t perfectly bounded).
3.4 Domain warping
Sample noise at coordinates offset by other noise: h(p + 0.15·warp(p)). Ridgelines meander, grids disappear. Cheap, always worth it.
3.5 Voronoi (cellular) noise — fractured rock
For each point: distance to nearest random feature point (F1), second nearest (F2), and the cell’s own hash. Three uses:
- F2 − F1 → 0 marks cell borders → carve them = crack seams / joint lines.
- Per-cell random offset → blocky displaced plates = faceted rock faces.
- F1 alone → cratered / cellular ground. Smooth noise cannot produce these; this is what makes rock read as fractured rather than lumpy.
3.6 Masks compose everything
A winding centerline function defines the valley; smoothstep distance masks derive: wash mask, mountain mask, uplift mask, grass/rock/sand rules. Every stage reads the same masks so the composition stays coherent. Dither mask thresholds with medium-frequency noise — uniform smoothstep borders look airbrushed.
3.7 Terracing
Quantize height into steps and blend by a mask: shelf = (floor(h/s) + smoothstep(frac)) · s. Instant sedimentary cliff bands.
4 · The Erosion Algorithm Compendium
Five families. Know all of them; combine at least two.
4.1 Droplet / particle hydraulic erosion (Lagrangian)
Sources: Beyer thesis [4], Lague [2][3], ranmantaru [5], forum [8]
Idea. Simulate tens of thousands of independent water droplets. Each spawns randomly, rolls downhill with inertia, picks up sediment when fast and under-loaded, deposits when slowing or over-loaded, evaporates, dies.
Core loop (per droplet, per step):
dir = dir·inertia − gradient·(1 − inertia)
capacity = max(−Δh, minSlope) · speed · water · pCapacity
if sediment > capacity or moving uphill → deposit (bilinear to 4 cells)
else → erode min((capacity − sediment)·pErode, −Δh), spread over a radius brush
speed = √(speed² − Δh·gravity)
water *= (1 − pEvaporation)
The parameters that matter (Beyer’s names):
| Param | Typical | Effect |
|---|---|---|
pInertia | 0.05 | 0 = follows gradient exactly (jittery); high = long smooth paths |
pCapacity | 4 | overall erosion depth |
pMinSlope | 0.01 | prevents zero-capacity stalls on flats |
pErode | 0.3 | fraction of free capacity taken per step (softness of carving) |
pDeposit | 0.3 | fraction of surplus dropped per step (fan building) |
pEvaporation | 0.01–0.02 | droplet lifetime |
pGravity | 4 | acceleration downhill |
pRadius | 2–4 | erosion brush; the anti-spike parameter |
| lifetime | 30–64 steps | cap runaway droplets |
Tricks & pitfalls (harvested across [2][4][5][8]):
- Never erode more than −Δh (the drop to the next position) — otherwise you dig holes and spikes behind the droplet. This single rule removes most artifacts.
- Erode with a radius brush, deposit bilinearly. Point-erosion makes needle canyons one texel wide; a weighted disc (weights ∝ 1 − d/r, normalized) sculpts believable gullies.
- Bilinear-interpolate height and gradient at the droplet’s fractional position; grid-snapped reads produce axis-aligned scarring.
- Deposition is not a bug: it builds the alluvial fans and smooth valley fills that sell realism.
- Droplet counts: ~30k–150k for a 512² map. Diminishing returns after that; go up a resolution instead.
- It parallelizes trivially (droplets are independent) → ideal for a compute-shader port, with atomics or brush-splatting to resolve write conflicts.
Character: local, stochastic scratching + fans. Great texture, weak global structure — it will not organize a mountain into a coherent drainage basin. Pair it with §4.3.
4.2 Grid / pipe-model shallow water (Eulerian)
Sources: Mei et al. via LanLou123 [6], compute-shader blog [7], huw-man [9]
Idea. Keep per-cell fields: terrain height b, water depth d, suspended sediment s, and four outflow fluxes to neighbors (the “virtual pipes”). Each frame:
- Rain / source adds water.
- Flux update:
f += Δt·A·(g·Δh)/ltoward each lower neighbor; scale so total outflow ≤ water present. - Water depth update from net flux; derive a velocity field from flux differences.
- Erosion/deposition: capacity
C = Kc · sin(tilt) · |v|; compare withs, exchange withb. - Advect sediment (semi-Lagrangian: sample
satp − v·Δt). - Evaporate.
Why it’s interesting:
- Runs entirely as texture passes → real-time on GPU, interactive rain brushes, visible flowing water while it erodes.
- Produces meandering channels, pooling, and lake filling that droplets can’t.
- Extensions seen in [6]: per-cell hardness map (rock erodes slower than soil — layered strata emerge free), thermal pass interleaved, terrain advection.
Pitfalls:
- Needs small Δt or it explodes (flux limiter is mandatory).
- Sediment advection wants a MacCormack/semi-Lagrangian step or it diffuses to mush.
- On WebGPU/TSL this maps to ping-pong storage textures — the natural “next level” for this demo.
Character: the most physical; medium-scale realism and animation. More code, more tuning.
4.3 Stream-power fluvial incision (the structure-maker)
Source: Braun & Willett 2013; used by Gaea-class tools
Idea. Skip simulating water movement; solve its long-term effect. Per iteration:
- Every cell picks its steepest-descent neighbor (receiver).
- Accumulate drainage area A down the receiver graph (order cells by height — an O(N) counting sort suffices).
- Incise with the stream-power law, implicitly (unconditionally stable):
h_i ← (h_i + F·h_rcv) / (1 + F), whereF = K·Δt·A^m / dist. - Add tectonic uplift on a mask so peaks survive.
Why this is the one to reach for first: it produces the anatomy of eroded mountains — dendritic gully networks, knife-edge ridges between catchments, graded valley profiles — in ~50 iterations at interactive cost. Droplet and pipe methods texture a mountain; stream power organizes it.
Hard-won calibration lessons (from this project):
Fscales withA^m. At high resolution A is enormous (√10⁶ ≈ 1000) — a constant that is “gentle” at 512² will base-level your entire map at 1024². For detail-scale passes, clamp A (A ← min(A, ~250)) so the pass carves local gullies but cannot drain the world.- Without uplift, everything decays toward the outlet elevation. Uplift mask ∝ initial height is the simplest stable choice.
- Re-impose authored floors (roads, washes) afterward — drainage will converge on them.
- Two-scale application works beautifully: full pass at sim resolution for anatomy, capped light pass at mesh resolution for fine gullies.
4.4 Thermal erosion (talus relaxation)
Sources: Musgrave 1989; interleaved in [6][7]
If a cell is steeper than the talus angle relative to a neighbor, move a fraction of the excess downhill. A few Gauss-Seidel iterations. Two uses:
- Classic: talus ≈ 30–40° → scree aprons at cliff feet.
- As a guard: set talus to your maximum allowed steepness (we used 65°) so it only sands off numerical spikes without softening intentional cliffs.
4.5 The slab filter (“fastest erosion ever”)
Source: Procedural Pixels [1]
Treat the heightmap as stacked threshold slabs; per texel per layer, compare window distance to the nearest above-threshold vs below-threshold sample and remap through a clamped ramp; sum layers. Effect: slopes get capped at the window’s implied talus angle, bumps melt into soil-creep forms. Multiple passes at partial opacity look better than one full pass.
- CPU optimization (from this project): walk the window once in increasing-distance order, building monotone (distance, running-max) / (distance, running-min) envelopes; each layer is then two binary searches, and only layers inside the window’s local height range matter. Turns an O(texels·layers·window²) shader into a ~1 s CPU pass.
- Know what it is: a smoothing operator. It reads as soil creep and aging — it will actively fight you if the goal is craggy fractured cliffs. We ultimately removed it in favor of stream power + crags; keep it in the toolbox for soft, old, weathered landscapes.
4.6 Result-first “reverse” sculpting (shearing without simulation)
Source: Kempke [12]
The classic shearing/crack-recursion algorithm (drop a point, spread cracks downhill, recurse) needs on the order of 10⁵ iterations before a 512² slope stops looking like a field of pits — tens of seconds for a mediocre result. Kempke’s reframe: erode in reverse. You already know the end state, so construct it directly:
pick Voronoi regions on the slope
for a chosen fraction of them:
push the whole region down to the lowest height it touches
add a little noise, smooth a bit
Instant sheared cliffs and stepped benches, controlled by two intuitive knobs (region size, drop fraction) — and as a bonus it leaves natural “paths” up the mountain between the drops. Two refinements worth stealing on their own:
- Debris put-back. Restore a percentage of the removed height inside each dropped region, simulating rubble accumulation — kills the machine-flat terrace floors.
- PerlinGaussian. A blur whose window radius varies across the surface by a noise field. Uniform-radius smoothing rounds every edge identically — the exact “too regular AND too irregular” tell of CG terrain; letting the radius wander makes some cliff edges stay knife-sharp while others weather soft. One extra line inside any blur loop.
Same philosophy as the Voronoi plate/crag pass in this project: trade physical honesty for direct art control when generation must be fast and unattended.
4.7 Choosing / combining
| Goal | Reach for |
|---|---|
| Global mountain anatomy, ridges, drainage | Stream power (4.3) |
| Gully texture, alluvial fans, wear/deposit data | Droplets (4.1) |
| Real-time animated water + erosion | Pipe model (4.2) on GPU |
| Scree aprons, spike cleanup | Thermal (4.4) |
| Soft aged hills, fast smoothing | Slab filter (4.5) |
| Fractured crag character | Not erosion — Voronoi + ridged detail (§3.5), applied after smoothing passes |
5 · Erosion-Data-Driven Texturing
The single biggest anti-“cartoony” upgrade: materials must follow the simulation, not decorate it. Record during erosion:
- Wear map — total rock removed per cell (droplet erode events). → fresh, lighter rock; stronger strata; no lichen patina; extra micro-normal roughness.
- Deposition map — total sediment dropped per cell. → sand/soil placement that lands exactly on fans and channel floors, where physics put it.
- Flow / drainage map — stream-power’s final A (log-scaled). → damp dark streaks down gullies; sediment tint where channels flatten.
Normalize each with a blur + tanh(x / (k·mean)) to get soft 0–1 masks. Then material rules become: slope + height + wear + deposit + flow + dithered noise thresholds. Hand-tuned smoothsteps alone always look painted; simulation masks always look inevitable.
6 · Detail & Realism Tricks
6.1 Respect the 1/f spectrum
Every “cartoony noise” complaint in this project traced to a band of noise whose amplitude was too large for its wavelength (2 m blobs at ±5% luminance; 2 m normal bumps at 90× gain). Rule: as frequency doubles, amplitude should roughly halve. Three quiet octaves beat one loud one.
6.2 Anisotropic rills
Erosion detail is directional. Build striations by sampling noise in a rotated frame: high frequency across the downslope direction, low frequency along it (fbm(across·230, along·26)), amplitude gated by steepness. Steep faces get dense parallel gouges running downhill — the signature drainage texture of real cliffs.
6.3 The steepness ramp
Make every detail source a monotone function of slope, and add an explicit inverse: a flatness-weighted blur that actively polishes gentle ground. The eye then reads one continuous law — glassy wash → soft hills → textured slopes → carved rock — instead of uniform noise everywhere. (And it matches the reference: DS2’s meadows and roads are nearly featureless; all visual noise lives on the peaks.)
6.4 Order of operations for crags
Smoothing passes (slab, blur, thermal) run first; sharp additive detail (ridged noise, Voronoi plates/cracks) runs last, gated to steep faces. Reversed order = the smoothers eat your crags, and you’ll stare at a “no visible change” build wondering why.
6.5 Pseudo-overhangs on a heightmap
A heightmap cannot encode overhangs (one height per XZ). Two escapes:
- Lateral fold (cheap, used here): after height displacement, push steep vertices a few meters along the downhill horizontal, noise-modulated. Cliff bands bulge over their talus; where steepness transitions sharply the surface passes vertical.
- Density field + marching cubes (GPU Gems 3 [11], the real answer): define terrain as a 3D density function (fBM in 3D, warped), extract the isosurface. True overhangs, arches, caves — at the cost of a completely different geometry pipeline and texturing scheme (triplanar becomes mandatory).
6.6 Two kinds of canyons (a field taxonomy)
From Kempke’s cross-country observation [12], useful as authored stamps over stream-power output: separator canyons run at roughly constant depth between features (they’re what splits a massif into ridges; depth loosely tracks length), while drainage canyons live on slopes — born shallow, deepening and widening downhill, then fading out at the bottom. Counterintuitively, the deepest carving happens on soft material and gentler gradients, not on the steepest hard rock — material hardness beats slope. Also his scale warning, confirmed by this project: erosion techniques don’t transfer across scales (droplets are meaningless globally; global canyon nets are too coarse locally) — run each at its own resolution.
6.7 Sediment floors are flat
A wash/valley floor is not “low terrain” — it is a deposit: material accumulated until it leveled. Model it that way: masked blur toward flatness + suppress every detail source inside the mask + re-impose the floor after any erosion pass (drainage will converge on it and try to trench it).
7 · Three.js / WebGPU Practical Notes
7.1 Renderer bring-up
- Order: create renderer → append canvas →
await renderer.init()→ size it. - Guard against 0×0: embeds/iframes report zero size at load; WebGPU hard-fails creating a 0-extent swapchain/depth texture (unlike WebGL). Clamp to ≥1×1, watch with a ResizeObserver, and skip rendering while degenerate.
WebGPURendererfalls back to WebGL2 automatically whennavigator.gpuis absent — detect and tell the user.
7.2 Geometry budget
- 1024² grid ≈ 2.1M triangles: fine on desktop WebGPU even with a 4096 shadow map.
- 2048² ≈ 8.4M triangles: killed the session (≈230 MB of buffers + double-drawn for shadows). More polygons ≠ more erosion — a denser grid is just smoother; carved detail must come from simulation at that resolution. If you truly need the density, implement LOD/clipmap tiles instead of one giant mesh.
computeVertexNormals()on millions of vertices takes seconds — schedule it inside a progress phase.
7.3 Bake, don’t paint vertices
Per-vertex colors at 512² (~2.7 m/vertex) blur all material boundaries. Bake albedo and micro-normal DataTextures at 2–4× mesh resolution. Micro-normals should carry only high-frequency grain (macro shading from mesh normals) — then even a tangent-sign mistake is invisible. Set SRGBColorSpace on albedo, mipmaps + anisotropy on both.
7.4 Killing specularity
“Roughness 1” on MeshStandardMaterial still leaves a broad GGX grazing sheen. For truly matte terrain use MeshLambert(Node)Material — no specular term exists. This also implements the art-direction rule (§8.1): flat albedo, let lighting do the work.
7.5 Keep the main thread alive
Chunk every heavy loop with await new Promise(r => setTimeout(r)) every N rows / droplets, and drive a progress bar. A frozen tab is indistinguishable from a crashed one.
7.6 The GPU upgrade path
Everything in §4 maps to WebGPU compute via TSL: heightmap in a storage texture, droplets as a particle buffer (brush-splat with atomics), pipe model as ping-pong passes ([6][7] are literally this architecture), stream power is the awkward one (global ordering) — keep it on CPU or use iterative Jacobi-style approximations.
7.7 Atmosphere on a budget (no post-processing)
- Fog (
near ≈ 0.17·far) tinted to the sky color for aerial perspective. - Gradient sky dome (canvas texture,
BackSide,fog:false,depthWrite:false). - 3–5 vista ridge rings: displaced-top open cylinders in flat haze tones, fog-exempt — the “layered ridgelines receding to the horizon” composition from the DS2 spec.
- A huge ground disc under everything so the horizon reads as land.
- AgX tone mapping for the desaturated filmic wash; ACES as fallback.
8 · Art Direction (distilled from the DS2 specs [13][14])
- Reduced palette lets lighting shine. Near-flat, low-saturation albedo; the perceived richness comes from sun + sky + AO, not albedo noise.
- Erosion tells the story. Shapes must imply history: carved outer banks, crumbled rims, fans below gullies. (This is why simulation-driven masks beat hand-painted ones.)
- Layered vistas lead the eye; tonal steps toward the sky separate the ridgelines.
- Rivers are staircases, not ramps — pools joined by short tumbles, never a long inclined ribbon (rule to honor when water gets added; ≤ ~3° continuous surface slope).
- Restraint in post. Effects support; they never dominate.
9 · Converged Parameter Cheat Sheet (the demo’s defaults)
| Stage | Parameter | Value |
|---|---|---|
| World | size / sim / mesh / tex | 1400 u / 512² / 1024² / 2048² |
| Sculpt | base hills | pow(fbm01(4·p, 5 oct), 1.5) · 30 |
| Sculpt | massifs | pow(ridged(3.2·p, 5 oct), 1.35) · 205 · mount |
| Sculpt | terrace step / blend | 14 u / ≤ 0.75·mask |
| Fluvial (macro) | iters / K·Δt / m / uplift | 55 / 0.12 / 0.5 / 0.95 |
| Fluvial (fine) | iters / K·Δt / A-cap | 12 / 0.05 / 250 |
| Droplets | count / inertia / capacity / erode / deposit / radius | ~130k / 0.05 / 4 / 0.3 / 0.3 / 3 |
| Droplet gate | erode only above | smooth(40°→50°) |
| Thermal | talus | ~65° (spike guard only) |
| Rills | across / along freq, amp | 230 & 540 / 26 & 60, 2.8 + 1.1 |
| Crags | ridged·4.5 + Voronoi plates ±1.6 − cracks 2.4 | gate smooth(35°→50°) |
| Overhang fold | amplitude / gate | ≤ ~4 u / smooth(45°→69°) |
| Polish | blur weight / window | smooth(27°→7°)·0.75, wash ≥ 0.92 / radius 1–2 px, noise-varied (PerlinGaussian) |
| Grain | 3 octaves, luminance | ±2.2% / ±1.6% / ±1.2%, slope-scaled |
| Light | sun el/az, fog near–far | 30° / 216°, 260–1550 u |
10 · Debugging War Stories (read before you lose an afternoon)
- NaN poisons silently. One
Math.pow(−0.01, 1.5)→ NaN → spreads through every downstream pass → mesh, props, everything positioned at NaN → “I only see the skybox.” No exception is ever thrown. Range-check (isFinite, min/max) after every stage. - Validate outcomes, not syntax. A pipeline can parse, run, and quietly erode your mountains from 158 m to 78 m (unbalanced K·Δt vs uplift; uncapped A at high res). The fix that found it: run the verbatim generation source headless in Node and print per-stage height ranges. Hand-copied “replicas” of the code will hide exactly the bug you’re hunting (ours did — an undeclared variable lived only in the real file).
- Surface your errors. Wrap generation in try/catch that writes the message into the UI, plus
window.onerror/unhandledrejectionhandlers. “ERROR: v is not defined” is a 30-second fix; a silent empty world is an hour of guessing. - TDZ trap:
typeof xdoes not protect against aconst xdeclared later in the same module — it throws. Declare mutable refs before any early-running function uses them. - Silent string replacements: scripted edits (
str.replace) no-op when the target drifted. Assert match counts after every automated edit. - Cache confusion: “I see no changes” is sometimes a stale file. Rule it out first.
11 · Where to Go Next
- Pipe-model water on WebGPU compute ([6][7]) — animated rivers that erode live, with a rain brush; then implement the stepped-river + flowmap rules from spec [14].
- Density-field terrain + marching cubes ([11]) — true overhangs, arches, caves; triplanar PBR splatting replaces the UV bake.
- LOD / clipmap tiles — the honest route to more polygons and bigger worlds.
- Hardness / strata maps ([6]) — layered rock resistance makes terraces and ledges emerge from the simulation instead of the sculpt.
- Tiling PBR detail sets — triplanar rock/soil/sand textures blended by the simulation masks, replacing the single baked albedo for close-up fidelity.