threejs-water: Water from the beach to orbit.
An ocean that is one draw call at every altitude, rivers that flow downhill, and lakes that fill to their spill level. Drop it on a threejs-biomes world, flat or planet, or use it on its own for a plain sea.
Fill the sea with one call.
Give it your camera, scene, renderer and world. The ocean takes its sea level and planet radius from the world, the rivers stream in around the camera, and both update themselves whenever the scene renders.
import { World } from 'threejs-biomes'
import { Ocean, Rivers } from 'threejs-water'
const world = new World({ seed: 'coast' })
const ocean = await Ocean.create({
camera, scene, renderer, // WebGPURenderer
world, sun,
waves: { amplitude: 0.8, direction: [0, 1] },
whitecaps: 0.6,
})
const rivers = await Rivers.create({ camera, scene, renderer, world, sun, maxDistance: 2000 })
// Buoyancy: depth below the waves and the surface normal.
const { depth, normal } = ocean.waterAt(boat.position.x, boat.position.y, boat.position.z)
import { Water } from 'threejs-water/react'
function Coast({ world, sun }) {
return (
<Water
world={world}
sun={sun}
ocean={{ waves: { amplitude: 0.8 }, deepColor: '#06283a' }}
rivers={{ maxDistance: 2000, flowSpeed: 1.4 }}
/>
)
}
Every kind of water.
A sea that covers everything below sea level, rivers along the valleys, and lakes where the ground holds water. Every feature below is live in the demo.
One ocean, every altitude
From an ant on the beach to orbit.
The sea is a camera-centred grid of concentric rings: dense under the camera, sparse at the horizon, rescaled with the camera's height. On a planet it is bent onto the sea sphere in the vertex shader and clamped at the horizon, and wave phases are computed in float64, so the water stays smooth millions of metres from the origin. One mesh, one draw call, at every altitude.
on the beach
from orbitGerstner waves
A wind sea you can tune.
Up to 16 Gerstner waves summed around a wind direction, with tunable amplitude, wavelength range, spread, choppiness and speed. Per-pixel wave normals and drifting ripples add detail, and fade out before they alias.
- amplitude
- wavelength
- direction
- spread
- choppiness
- speed

Light through water
Shallow to deep, clear to dark.
Depth-based absorption fades from shallowColor to deepColor, the bottom refracts through the surface, and Fresnel reflections pick up the sky or an environment map. A GGX glint follows the sun.

Foam
Shorelines, whitecaps and rapids.
Foam gathers where the water gets shallow at the shore, on the sharpest wave crests (whitecaps), and in river rapids where the flow gets fast. One foam setting scales all three.

Rivers that flow
Streamed along the world's rivers.
River surfaces are built in camera-centred tiles under a per-frame time budget. Flow follows the slope of the surface, so flow-mapped ripples and rapids foam move downstream, faster where it is steep.

Real drainage
Lakes at their spill level, rivers by catchment.
computeHydrology fills depressions (Priority-Flood+ε), routes flow downhill (D8), accumulates catchment area, and returns rivers that widen downstream and lakes at their spill level. riverModifiers carves the rivers into the world and Lakes draws them.

Queries for gameplay
Buoyancy, swimming and splashes.
ocean.waterAt(x, y, z) returns the wavy surface, depth and normal under a point. waterAt(world, x, z) says whether it is sea or river, with level, depth and flow velocity. Pure functions, no GPU readback.
three.js demoGo jump in.
Fly from the beach to orbit in the live demos, or go straight to the API reference.