Procedural Eroded Terrain in Three.js – Theory, Techniques & Field Notes

Three.js Hydraulic Erosion

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

  1. Procedural Pixels — Terrain hack: the fastest erosion algorithm ever (Hatchling’s slab filter)
    https://www.proceduralpixels.com/blog/terrain-hack-fastest-erosion-algorithm-ever
  2. Sebastian Lague — Hydraulic-Erosion (droplet method, C#/Unity, the most-copied reference implementation)
    https://github.com/SebLague/Hydraulic-Erosion
  3. Sebastian Lague — playable erosion demo
    https://sebastian.itch.io/hydraulic-erosion
  4. 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
  5. 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/
  6. 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
  7. 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/
  8. 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/
  9. 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/
  10. Shadertoy MtGcWh — heightmap erosion approximated entirely in a shader
    https://www.shadertoy.com/view/MtGcWh
  11. 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
  12. 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)

  1. ds2-graphics-spec-for-threejs.md — DS2/Decima full-pipeline feature spec mapped to Three.js.
  2. 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):

ParamTypicalEffect
pInertia0.050 = follows gradient exactly (jittery); high = long smooth paths
pCapacity4overall erosion depth
pMinSlope0.01prevents zero-capacity stalls on flats
pErode0.3fraction of free capacity taken per step (softness of carving)
pDeposit0.3fraction of surplus dropped per step (fan building)
pEvaporation0.01–0.02droplet lifetime
pGravity4acceleration downhill
pRadius2–4erosion brush; the anti-spike parameter
lifetime30–64 stepscap 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:

  1. Rain / source adds water.
  2. Flux update: f += Δt·A·(g·Δh)/l toward each lower neighbor; scale so total outflow ≤ water present.
  3. Water depth update from net flux; derive a velocity field from flux differences.
  4. Erosion/deposition: capacity C = Kc · sin(tilt) · |v|; compare with s, exchange with b.
  5. Advect sediment (semi-Lagrangian: sample s at p − v·Δt).
  6. 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:

  1. Every cell picks its steepest-descent neighbor (receiver).
  2. Accumulate drainage area A down the receiver graph (order cells by height — an O(N) counting sort suffices).
  3. Incise with the stream-power law, implicitly (unconditionally stable): h_i ← (h_i + F·h_rcv) / (1 + F), where F = K·Δt·A^m / dist.
  4. 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):

  • F scales with A^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

GoalReach for
Global mountain anatomy, ridges, drainageStream power (4.3)
Gully texture, alluvial fans, wear/deposit dataDroplets (4.1)
Real-time animated water + erosionPipe model (4.2) on GPU
Scree aprons, spike cleanupThermal (4.4)
Soft aged hills, fast smoothingSlab filter (4.5)
Fractured crag characterNot 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.
  • WebGPURenderer falls back to WebGL2 automatically when navigator.gpu is 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])

  1. Reduced palette lets lighting shine. Near-flat, low-saturation albedo; the perceived richness comes from sun + sky + AO, not albedo noise.
  2. 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.)
  3. Layered vistas lead the eye; tonal steps toward the sky separate the ridgelines.
  4. 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).
  5. Restraint in post. Effects support; they never dominate.

9 · Converged Parameter Cheat Sheet (the demo’s defaults)

StageParameterValue
Worldsize / sim / mesh / tex1400 u / 512² / 1024² / 2048²
Sculptbase hillspow(fbm01(4·p, 5 oct), 1.5) · 30
Sculptmassifspow(ridged(3.2·p, 5 oct), 1.35) · 205 · mount
Sculptterrace step / blend14 u / ≤ 0.75·mask
Fluvial (macro)iters / K·Δt / m / uplift55 / 0.12 / 0.5 / 0.95
Fluvial (fine)iters / K·Δt / A-cap12 / 0.05 / 250
Dropletscount / inertia / capacity / erode / deposit / radius~130k / 0.05 / 4 / 0.3 / 0.3 / 3
Droplet gateerode only abovesmooth(40°→50°)
Thermaltalus~65° (spike guard only)
Rillsacross / along freq, amp230 & 540 / 26 & 60, 2.8 + 1.1
Cragsridged·4.5 + Voronoi plates ±1.6 − cracks 2.4gate smooth(35°→50°)
Overhang foldamplitude / gate≤ ~4 u / smooth(45°→69°)
Polishblur weight / windowsmooth(27°→7°)·0.75, wash ≥ 0.92 / radius 1–2 px, noise-varied (PerlinGaussian)
Grain3 octaves, luminance±2.2% / ±1.6% / ±1.2%, slope-scaled
Lightsun el/az, fog near–far30° / 216°, 260–1550 u

10 · Debugging War Stories (read before you lose an afternoon)

  1. 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.
  2. 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).
  3. Surface your errors. Wrap generation in try/catch that writes the message into the UI, plus window.onerror / unhandledrejection handlers. “ERROR: v is not defined” is a 30-second fix; a silent empty world is an hour of guessing.
  4. TDZ trap: typeof x does not protect against a const x declared later in the same module — it throws. Declare mutable refs before any early-running function uses them.
  5. Silent string replacements: scripted edits (str.replace) no-op when the target drifted. Assert match counts after every automated edit.
  6. Cache confusion: “I see no changes” is sometimes a stale file. Rule it out first.

11 · Where to Go Next

  1. 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].
  2. Density-field terrain + marching cubes ([11]) — true overhangs, arches, caves; triplanar PBR splatting replaces the UV bake.
  3. LOD / clipmap tiles — the honest route to more polygons and bigger worlds.
  4. Hardness / strata maps ([6]) — layered rock resistance makes terraces and ledges emerge from the simulation instead of the sculpt.
  5. Tiling PBR detail sets — triplanar rock/soil/sand textures blended by the simulation masks, replacing the single baked albedo for close-up fidelity.

Leave a Reply

Your email address will not be published. Required fields are marked *