Realistic water in the browser · a comparison bench

WATER LAB

One question, several answers: what is the best way to put convincing water on a web page? Each exhibit is a complete, self-contained take — a different simulation method, a different renderer, a different set of things it gets wrong. They share nothing but this page, on purpose, so no exhibit is bent to fit another's architecture.

Live

Exhibit A

Analytic buoyancy

Jolt Physics in WASM. There is no fluid — every floating body is handed its own water plane each step, rebuilt from a shared wave field, and Jolt integrates the displaced volume against it. Splashes are the one signal that runs back from the solver into the surface.

Simulates
Rigid bodies only. The water is geometry.
Costs
Nine bodies, ~0.1 ms/step. Runs anywhere WASM does.
Fails at
Pouring, sloshing, breaking waves, anything that changes the water's shape.
Exhibit B

WebGPU MLS-MPM

A real particle fluid, solved in compute shaders. 100k material points scatter onto a background grid every substep, the grid resolves pressure and viscosity, and they gather what it decided and move. Nothing here is a height field, so a wave can fold over itself and a sheet can tear into spray — the two things exhibit A cannot do at any price.

Simulates
The fluid itself. The grid is scratch, rebuilt from nothing 300 times a second.
Costs
100k particles, 64³ grid, seven substeps. ~14 ms/frame at 2560×1600 on Apple silicon; needs WebGPU.
Fails at
Looking like water. It is 100k spheres, and it reads as one — which is what exhibit C is for.
Exhibit C

Screen-space fluid rendering

Exhibit B's particles, drawn as a surface instead of as spheres — and no mesh is built anywhere. The fluid exists only as a depth buffer, smoothed by a narrow-range filter until it can be differentiated into normals, with a separate thickness pass for absorption. Every pass runs in image space, so the cost is the same whether the water is one body or ten thousand droplets.

Simulates
Nothing. It is a renderer; the solver is exhibit B's, unmodified and unaware.
Costs
Five screen-resolution passes. ~6 ms on top of the solve, at 1600×1000.
Fails at
Isolated droplets. A screen-space surface cannot represent one, so thin films are faded out rather than drawn wrong.
Exhibit D

FFT ocean

No simulation at all. An inverse FFT of a Phillips spectrum, evaluated per frame — there is no previous frame to advance from, so the surface fifteen minutes out costs exactly what the surface now costs and is reached without visiting anything in between. Still the best-looking open water there is, and the cheapest.

Simulates
Nothing. It is a transform of a random field with the statistics of wind-driven sea.
Costs
128² grid, two inverse transforms, ~2.7 ms/frame on one CPU core.
Fails at
Boundaries. Pools, shores, and anything that displaces water.
Exhibit E

Open water

Exhibit D's sea with exhibit A's solver floating on it. A only ever asks the water two questions — how high are you here, and which way are you tilted — and D answers both far better than the four sines A was built on. Neither exhibit was altered to make it fit: the join inverts D's horizontal displacement, and converges precisely because D already forbids its own grid from folding.

Simulates
Rigid bodies, on a surface that is a transform rather than a simulation.
Costs
One 128² transform plus eight bodies. ~2 ms/frame, WebGL 2, no WebGPU needed.
Fails at
Splashes. The sea moves the bodies; the bodies cannot move the sea.
Exhibit F

Crates in the water

The coupling exhibit B's grid was chosen for. Box3D owns the crates, the MPM grid owns the water, and neither solver knows the other exists — sixteen floats per body cross one way each frame and six cross back. Buoyancy is not modelled anywhere. Every node a crate covers books its momentum change against that crate, and a momentum exchange summed over a crate's surface is a pressure integral, which is what buoyancy is.

Simulates
Both, separately. Three identical crates at 340, 700 and 1150 kg/m³ settle in that order.
Costs
Exhibit B plus about half a millisecond. The reaction returns through a mapped buffer, one frame late.
Fails at
Small bodies. The grid resolves a crate to four or five cells, so its boundary is thick relative to itself and the densest crate hovers where it ought to rest.
Exhibit G

Rain impact

Thousands of measured raindrop trajectories strike a shallow film evolved by WebGPU compute over rough asphalt. Drop diameters follow Marshall–Palmer, fall speeds follow Gunn–Kinzer, and each collision feeds a gravity–capillary shallow-water solve before it becomes a dimple, crown, secondary spray and interacting wave field.

Simulates
Drop flight, impact regime, crown growth, secondary droplets and an interacting thin-film velocity field.
Costs
640×400 film grid, four substeps, up to 3,600 drops and 4,200 splash droplets. WebGPU plus WebGL 2.
Fails at
Full interface resolution. It preserves the measured scaling laws, not the air sheet between water surfaces.
Exhibit H

Sharp-edge overflow

Water is fed onto a clean square glass plate. A conservative capillary film carries it across the top, contact-angle physics pins it at the sharp rim, and the mass that spills becomes pendant menisci, thinning ligaments and detached three-dimensional drops under gravity.

Simulates
Thin-film mass and momentum, wetting, edge storage, necking, aerodynamic fall and capillary relaxation.
Costs
A 192² WebGPU film plus fixed pools for four rims and 256 detached drops.
Fails at
Fully resolved air–water topology. Breakup conserves the solver volume but uses a reduced edge model.

Tuning

Studio

Ocean tuning

An isolated copy of the FFT ocean for tuning atmosphere, reflection, scatter, glitter and foam. The live exhibits keep their locked production defaults; studio controls update uniforms without adding passes, draws or per-frame scene work.

Guards
One scene pass, two draws, one 25-instance surface mesh.
Exports
Copy the current look as a compact JSON preset.
Does not change
The FFT spectrum, wave geometry, buoyancy sampler or production pages.

On the bench

Nothing, for the moment — every experiment on the bench has moved to the floor.