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
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.
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.
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.
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.
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.
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.
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.
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
On the bench
Nothing, for the moment — every experiment on the bench has moved to the floor.