NatureGL Waterv1.1.0

Guides

Buoyancy and wakes

The CPU reads the same wave field the GPU draws, so objects float on the waves you see. Buoyancy moves them up and down and tilts them. Wakes turn their motion into foam, a Kelvin V and a trail that stays behind.

The demo's flagship under way at 7 m/s. addWake measures its speed from its motion: contact foam hugs the hull, the Kelvin V spreads behind it, and the trail stays in the persistent foam field.

#Sample the surface

js
const y = water.sampleHeight(x, z);        // surface height in metres
const n = water.sampleNormal(x, z);        // THREE.Vector3, from ±0.5 m differences
water.isUnderwater(camera);                // or any Object3D

sampleHeight includes the swell, shoaling and shore breakers. It inverts the waves' horizontal shift in 4 steps, so it returns the height at (x, z) rather than at the point that started there.

TierWhere the height comes fromCost
Gerstner (low, medium)Evaluates the same Gerstner sum as the shaderAbout 5 × waves sin/cos per call
FFT (high, ultra)Bilinear read of the swell cascade, read back from the GPU asynchronouslyA texture lookup, but 1–3 frames old

On FFT tiers floating objects follow the swell but not the small chop, which lives in the detail cascades.

#Float an object

js
// a buoy: one probe, spring-damper heave, tilts toward the local normal
water.buoyancy.addObject(buoy, { draft: 0.2 });

// a boat: four probes (bow, stern, port, starboard) → heave, pitch and roll
water.buoyancy.addObject(boat, { size: { length: 12, beam: 4 }, draft: 0.4 });

// any hull shape: your own probe offsets, in local (x, z), bow along +Z
water.buoyancy.addObject(raft, { probes: [[-2, 3], [2, 3], [-2, -3], [2, -3]] });
OptionDefault
probes[[0, 0]]Local (x, z) sample offsets. The bow is along local +Z
size—{ length, beam }: a shortcut for four probes
draft0How far the object's origin sits below the mean surface. Negative lifts it
heightSmoothing2.5Smoothing rate for the height, per second
rotationSmoothingheightSmoothingSmoothing rate for pitch and roll (×1.6 for single-probe tilt)
springtrueSingle probe: spring-damper heave instead of plain smoothing
tiltSample0.8Single probe: distance for the normal sample, in metres
rollJitter0.012Idle roll in radians, so nothing sits perfectly still

With one probe the object bobs on a spring and leans with the local slope. With several, a least-squares plane through the sampled heights gives heave, pitch and roll, each smoothed.

js
const handle = water.buoyancy.addObject(crate, { draft: 0.1 });
handle.enabled = false;               // pause it (e.g. while it's carried)
water.buoyancy.removeObject(crate);   // or pass the handle

#Add a wake

js
water.addWake(ship, { length: 26, beam: 9.5 });

// each frame, move the ship yourself: the wake measures the motion
ship.position.x += Math.sin(ship.rotation.y) * speed * dt;
ship.position.z += Math.cos(ship.rotation.y) * speed * dt;
OptionDefault
length10Hull length in metres
beamlength / 3Hull width
headingOffset0Radians added to the yaw if the model's bow isn't along local +Z
useVelocityHeadingfalsePoint the wake along the direction of motion instead of the yaw

Every tracked hull gets white water along its sides. Once it moves, it adds a Kelvin V with 19.5° arms and stamps the persistent foam field, so a trail stays behind it. The handle's speed reports the measured speed in m/s. Teleports of more than 80 m/s are ignored, so resetting a boat doesn't read as a burst of speed.

Up to MAX_HULLS (8) wakes are drawn. removeWake(objOrHandle) frees a slot.

#Floating without the WaterSystem

Buoyancy works with anything that has sampleHeight(x, z), for example a flat test surface in a unit test:

js
import { Buoyancy } from 'naturegl-water';

const flat = { sampleHeight: () => 0 };
const buoyancy = new Buoyancy(flat);
buoyancy.addObject(mesh, { draft: 0.3 });
buoyancy.update(dt);