What it does
The optics are per-channel extinction against the scene's own depth: red, green and blue each fade at their own rate
with distance, so far things shift in colour rather than simply turning blue, and in-scattered murk builds over the
same distance. Only the underwater part of each view ray is absorbed, so looking up at the surface from below shows
the waves rather than a wall of murk. Four named waters ship, reef, ocean, pool and murk, and they are bright
by default on purpose, since water true to physical tables reads as instant murk in a game; murk keeps that heavy
feel, and clarity scales any of them toward crystal clear.
The waterline is decided per view ray at the camera, not by the height of what is seen, so a half-in, half-out shot
splits cleanly and the shallows seen from above are left alone. Going under has hysteresis, so a camera bobbing
exactly at the line never strobes the effect, and the state runs on the simulation clock, so submerged and depth
answer correctly in a headless test. While submerged, the pack hands the scene its fog, darker the deeper you go, so
the translucent meshes, the beams and the particles agree with the water; on surfacing the dry fog comes back.
Caustics are baked once and published for render-3d's mesh shader, which lays them on the seabed in world space, so
they stay on the sand instead of sliding like a decal on the lens, and show from a boat above too. The light shafts
are pale world-anchored quads in a ring round the camera, leaning down the refracted sun. They fade in and out in
brightness but never sway, shorten into the shallows when you give the pack the seabed, and fade as the camera goes
deep. Composed beside god-rays-3d, the sun's shafts above also dim with depth and take the water's colour.
Features
- Colour lost with distance — per-channel extinction, red first and blue last, with in-scattered murk.
- Four named waters —
reef,ocean,poolandmurk, swapped live withsetPreset, and aclaritydial. - A clean waterline — decided per view ray, with a bright meniscus band at the crossing and hysteresis so it never strobes.
- Moving surface —
surfaceAttakes a wave function such aswater-3d'sheightAt, or a flatlevelwithout one. - World-space caustics — tile size, crawl speed and the depth at which they fade, all set.
- Light shafts — a ring of up to 640 pale beams, with strength, flicker, distance, depth and spacing, shortened by
groundAtover the seabed. - The water's fog — the scene's fog follows the water while submerged, unless
driveFogis off. - Storm dial — desaturates the murk toward grey-green and closes it in.
- Depth and diagnostics —
submerged,depthand adiagnostics()snapshot, plus pure optics functions for headless use and tests.
Good for
- Diving and swimming games — a depth gauge from
depth, and murk that closes in as the dive goes deeper. - Boat and sea games — falling overboard or ducking under a swell is the moment the effect arrives.
- Open worlds with lakes and coasts — the player can wade in, and the waves overhead set the waterline.
- Pools and reefs — bright, clear water with strong caustics on the floor.
- Murky rivers and dark water —
murk, short sight lines and the storm dial.
Make it yours
The options set the water: a preset, a sea level, the extinction per channel for the view and for sunlight from
above, the murk colour and how far you can see, clarity, caustics, a small screen wobble, a vignette, the meniscus,
the hysteresis, the storm dial, every property of the shafts, and whether it drives the fog. Most are also live on
game.underwater, so the water can change during play.
Two very different uses: a snorkelling game on the bright reef preset with full caustics and shafts, the surface
following the waves; and a deep dive in the murk preset that lets the murk close in as the depth grows and raises
the storm dial, so the light goes and the shafts fade out.
A quick look
A surface from water-3d, the seabed for the shafts, and murk that thickens with the dive:
const bed = (x: number, z: number): number => game.terrain.heightAt(x, z);
const sea = game.water.build({ preset: 'sea', ground: bed });
game.underwater.surfaceAt((x, z) => sea.heightAt(x, z));
game.underwater.groundAt(bed);
game.step('dive', () => {
game.underwater.clarity = Math.min(1, 0.6 + game.underwater.depth / 40); // 1 is the water as authored
});
Works with
render-3d— required; it supplies the depth and the sun, and its mesh shader draws the caustics.core-3dbrings it.water-3d— the surface seen from above and below; hand itsheightAttosurfaceAt.terrain-3d— a seabed to hand togroundAt.god-rays-3d— sun shafts that die with depth and take the water's colour.sky-3dandskymax— their fog gives way to the water's while you are under.watermax— a simulated ocean with its own underwater pass; compose one or the other, never both.post-looksandbackdrop-environments— a lighter, stylised underwater sway or backdrop with no optics.
Good to know
- It is a pass over the finished frame: a distant object under a shallow overhang is absorbed as if it sat at the camera's depth, and tall waves far from the camera do not bend the waterline.
- The surface itself, and the view of the sky through it from below, are
water-3d's to draw; bubbles, particles and muffled sound belong to other packs. - The shafts are pale quads, not volumetric light, so they are not cast from geometry.
- While submerged it overwrites
render-3d's fog every step, unlessdriveFogis off, and puts the dry values back on surfacing. - It does not compose with
watermax.






