Guides
The look of the water
The colour of the water comes from how much of it the light passes through. Shallows show the bed, pools go deep turquoise, and a muddy spate turns opaque within centimetres. This page covers the dials that shape that, and the foam, reflections and glints on top.
loadPreset('glacial'), then 'muddy', then 'swamp' on the same stretch of river. Absorption, scattering, flow speed, foam and lighting all ease from one to the next.#Depth, absorption and clarity
For every water pixel, the shader measures how far the view ray travels through water, using the scene's depth buffer. It then applies Beer–Lambert absorption per colour channel and adds in-scattered light:
transmitted = bed × exp(−absorption / clarity × thickness)
in-scatter = scatterColor × (1 − exp(−thickness × scatterDensity / clarity))| Param | clear | What it does |
|---|---|---|
absorption | [0.42, 0.055, 0.075] | Linear-RGB absorption per metre. Red dies first, which gives the turquoise |
scatterColor | [0.01, 0.13, 0.125] | The colour light picks up inside the water |
scatterDensity | 0.28 | How quickly in-scatter builds up. glacial uses 0.95 for milky water and muddy 2.4 |
clarity | 1.1 | Divides both absorption and scattering. 2 is twice as clear, 0.5 half |

clarity at the demo slider's minimum and maximum. The rest of the clear preset is unchanged.The demo's HUD shows VIS, the distance at which the least-absorbed channel falls to 1 %: 4.6 / (min(absorption) / clarity).
#Refraction and the shoreline
The bed is read from the scene colour at an offset along the ripple normal. The offset grows with depth over the first metre, and samples that land in front of the water are rejected, so objects above the surface don't leak into it. refraction scales the offset (muddy uses 0.5, swamp 0.7, glacial 0.8).
Over the first few centimetres of depth the water fades into the unrefracted scene. That removes the hard line where the surface meets the terrain.
#Reflections
| Source | When |
|---|---|
| Sky | The built-in sky gradient, or the equirect envMap of an external sky |
| Tree line | A dark band near the horizon of the reflection, light.horizonColor at water.horizonHeight |
| Screen-space reflections | Banks, rocks and trees, marched on screen on high (20 steps) and ultra (36 steps) |
Fresnel decides how much reflection you see, and reflectivity scales it. swamp uses 1.15 for glassy, dark water. Where the reflection is almost invisible, the SSR march is skipped.
Sun glints are two specular lobes on the ripple normal, tinted by the sun colour and dimmed by cloud shadow when an external sky provides one.
#Ripples
The surface is flat per cross-section. All the detail is in the normal:
- a fine and a coarse ripple layer, advected along the river's flow space and stretched along the current in fast water
- on
mediumand above, a third, stationary layer of standing waves. It grows with speed and turbulence, and lakes suppress it withcalm - extra roughness around obstacles and in rapids
normalStrength scales it all. Each body also has a normalScale (lakes default to 0.55).
#Foam
Foam is sampled in flow space and stretched along the current, so it forms streaks that point downstream. It collects in five places:
| Source | Driven by |
|---|---|
| Shoreline lace | Very shallow water, wider in fast water |
| Bank shear | The outer 20 % of a river's width, stronger with speed. Lakes don't get it |
| Rapids | High speed × turbulence, plus the ribbon's slope |
| Obstacles | The ring, bow and wake of each obstacle |
| Plunge pools | Boiling churn and the downstream plume of each waterfall |
foam scales all of them, and each body has a foamScale.
foam: 0foam: 2.5#Changing it at runtime
water.setParam('water.clarity', 0.6); // eases over ~0.3 s
water.setParam('water.absorption', [0.8, 0.5, 0.4]);
water.params.water.foam = 1.8; // same thing, direct
water.loadPreset('glacial'); // everything at onceEvery value in params is eased toward on each update(), so a slider never pops. The complete list is on Preset parameters.