NatureGL Riverv1.0.0

Guides

Terrain and materials

The water only looks right when the ground under it has the right shape. carveTerrain() cuts every bed, basin and pool into your own height function, and patchMaterial() makes your rocks and ground wet, tinted and lit by caustics where the water touches them.

#Carve the terrain

carveTerrain(heightFn) takes any (x, z) => height function and returns a new one with every body's profile blended in:

js
const base = (x, z) => 0.6 + Math.max(0, Math.abs(x) - 5) * 0.2;   // your hills, noise, heightmap…
const height = water.carveTerrain(base);

const geo = new THREE.PlaneGeometry(90, 90, 220, 220).rotateX(-Math.PI / 2);
const p = geo.attributes.position;
for (let i = 0; i < p.count; i++) p.setY(i, height(p.getX(i), p.getZ(i)));
geo.computeVertexNormals();

Use the returned function for everything that sits on the ground, too: rocks, trees, grass and the camera's floor.

#What each body cuts

BodyProfileTuned by
RiverA parabolic bed down to depth at the centre line, with a small step at the bank (0.25 m, or 20 % of the depth if shallower). Outside the bank the ground rises at bankSlope per metre and steepens further outdepth / depths, bankSlope (0.32), bankWidth (1.2 half widths), endBlend
LakeA basin that reaches depth at shelf metres from the shore, and a bank rising at bankSlope per metredepth (2.5), shelf, bankSlope (0.3), bankWidth (6 m)
WaterfallA bowl at the landing pointplungeDepth, plungeRadius

Each profile is blended into your terrain with a weight that falls to zero bankWidth beyond the bank. Where two bodies overlap, the lowest result wins, so a river's end cuts into a plunge pool instead of filling it. Where your base terrain is lower than the water, the bank profile raises it, so water never floats over a hole.

#When to skip it

If your terrain is sculpted by hand or comes from a heightmap that already has the channels, don't carve. Author the rivers so their y sits a little above the bed you already have, and set depth to match. depth only matters for the carve.

#Patch your materials

js
const rock = new THREE.MeshStandardMaterial({ color: 0x9a9a94, roughness: 0.8 });
water.patchMaterial(rock);                  // returns the same material
scene.add(new THREE.Mesh(rockGeometry, rock));

patchMaterial() injects shader code into a built-in lit material. It reads the system's top-down water map, a render of every surface's level, flow and coverage, so each fragment knows whether it is under water and how deep.

EffectWhere
Wet bandA noisy capillary band above the waterline that laps with the ripples. It is darker, more saturated and glossier, with a tide line
Splash zoneWet ground around each waterfall plunge
Underwater tintA lighter wet tint and a faint algae film on submerged surfaces
Sun through waterDirect sunlight is attenuated along its path through the water column, so muddy water darkens the bed
CausticsDepth-focused, with dispersion that grows with depth. They animate faster in fast water and break up in rapids
Caustic reflectionsFaint moving light on faces just above the surface
caustics 1
caustics 0
caustics 0caustics 1
The demo's pebbled bed under the clear preset with water.caustics at 0 and at the preset's 1. The ground and the rocks are ordinary MeshStandardMaterials passed through patchMaterial().

#Which materials work

  • MeshStandardMaterial and MeshPhysicalMaterial get everything, including the glossier wet band.
  • MeshLambertMaterial and MeshPhongMaterial get the colour and caustic parts.
  • InstancedMesh works: the patch reads the instance matrix. The demo's boulders and pebbles are instanced.
  • An existing onBeforeCompile is chained, and the patch runs after your <color_fragment> edits. Patching the same material twice does nothing the second time.

The strength of the wet band follows water.wetness in the preset (the muddy preset raises it to 1.3). The caustics follow water.caustics, and they fade with depth through clarity and absorption.

#Water map precision

The water map covers the bounding box of all bodies plus 2 m, at the tier's waterMap resolution (512² on low, 1024² on medium and high, 2048² on ultra). A river system 200 m long gets texels of about 0.2 m on high. Water spread over several kilometres gets proportionally coarser wet bands and caustic masks. The map is re-rendered only when bodies change.