NatureGL Riverv1.0.0

Guides

Rivers and lakes

A river is a ribbon built along a spline, and a lake is a flat surface inside a circle or a polygon. Both carry their own flow, which drives the ripples, the foam, the caustics and anything floating on them.

The demo's main run, filmed along the path of its Glide tour. Width, depth and speed change along the river, and the ripples and foam move with the local current.

#Draw a river

addRiver() takes a centre line of THREE.Vector3 points. The y of each point is the water level there, not the bed. The system fits a centripetal Catmull-Rom curve through the points and resamples it by arc length, so the spacing of your points doesn't matter.

js
const river = water.addRiver({
  name: 'creek',
  path: [
    new THREE.Vector3(-20, 1.2, -40),
    new THREE.Vector3(-8, 0.8, -18),
    new THREE.Vector3(4, 0.4, 0),
    new THREE.Vector3(2, 0, 24),
  ],
  width: 6,          // full width in metres
  depth: 1.5,        // depth at the centre line (used by carveTerrain)
  flowSpeed: 1.5,    // surface speed at the centre, at the mean width (m/s)
});

A path needs at least two points. The call returns a River and marks the system dirty. The mesh is built on the next update(), so you can add many bodies in a row at no extra cost.

#Width and depth along the path

Pass widths or depths instead of the constant to give one value per path point. Values between points are blended smoothly.

js
const pts = [0, 10, 20, 30, 40].map(z => new THREE.Vector3(Math.sin(z * 0.1) * 4, 0, z));
water.addRiver({
  path: pts,
  widths: [9, 6, 4, 6, 10],       // a narrows in the middle
  depths: [2.0, 1.4, 0.6, 1.4, 2.2],
});

#How fast the water runs

  • flowSpeed is the centre-line speed at the river's mean width.
  • With conserveFlow: true (the default) speed is inversely proportional to the width, which keeps the discharge constant. In the example above, the 4 m narrows run more than twice as fast as the 10 m pool at the end.
  • Speed falls off toward the banks, to about 65 % at the edge.
  • Where the path descends, the ribbon gets a turbulence value from the slope. Rapids get extra ripple roughness and foam.
  • water.flow in the preset multiplies every speed at once, which is what the demo's Flow slider changes.

#Ends, fades and overlaps

OptionDefaultUse it to
extend0.3 × half widthRun the ribbon past the first and last point, as one number or [start, end]. Use [0, 0] to stop exactly at a waterfall lip
fade'auto'Fade the ribbon's alpha in and out over [in, out] metres. 'auto' fades an end that lies inside a lake and leaves the others hard
margin0.4Extend the ribbon past each bank, as a fraction of the half width. The terrain hides the extra, and the water never shows a hard edge

Rivers draw over lakes where they meet, so a river that ends inside a lake fades into it. Two rivers overlapping at the same level z-fight. End one in a lake, or overlap them only where their levels differ.

#Add a lake

A lake is calm, flat water at one level. Give it a circle or an outline.

js
water.addLake({
  center: new THREE.Vector3(0, 3, -24),   // y is the level
  radius: 9,
  depth: 2.5,
});

drift (default (0.03, 0.05) m/s) moves the surface texture slowly and pushes floaters. calm (default 1) removes the standing-wave detail that rivers have. Lakes also get softer normals and less foam by default (normalScale: 0.55, foamScale: 0.6).

The demo's upper lake at y = 3: a circle of radius 10.5 m. The brook fades into it at the far end and the outlet leaves toward the lip in the foreground.

#Chain bodies into a water system

The demo valley is built from five bodies in this order:

#A brook into the lake

A descending river whose last point lies inside the lake, so its end fades automatically.

#The lake

addLake({ center, radius: 10.5, depth: 3.2, drift }) at y = 3.

#An outlet to the lip

A short river at the lake's level with extend: [0, 0], starting inside the lake and ending at the waterfall's top.

#The cascade

addWaterfall() from the lip at y = 3 down to the pool at y = 0. See Waterfalls.

#The main run

A long river starting at the plunge point, at level 0.

#Change or remove a body

Bodies are built from their options once. To change a river's shape, remove it and add it again, then carve the terrain again if you use carveTerrain().

js
water.remove(river);
const river2 = water.addRiver({ ...options, widths: newWidths });

#Ribbon attributes

If you write your own shader against the ribbon, these are its vertex attributes:

AttributeContents
aFlow(across m, along m, speed, abs(lateral) / halfWidth): flow-space coordinates in metres
aDir(dir.x, dir.z, alpha): local flow direction and the end fade
aTurbBed slope mapped to 0 – 1, which drives the rapids

Lakes carry the same attributes, with the flow space aligned to their drift.