slavingia/surfing: the ocean engine that turns water into infrastructure
A Three.js system that simulates swells once, reuses the same wave field for rendering and buoyancy, and falls back cleanly when WebGPU is absent.
- Surfing treats the ocean as a shared simulation, so the surface, buoyancy, and underwater effects all read from the same wave data.
- Its biggest technical move is reuse, because one GPU wave pipeline generates displacement and normals that multiple systems can consume.
- Clipmaps and nested geometry matter as much as the shader, because believable scale depends on detail where the camera is looking.
- WebGPU does the heavy lifting, but the WebGL fallback is what makes the engine feel like a product instead of a demo.
The ocean is not a material, it is a pipeline
Most water demos stop at shimmer. surfing goes further and treats the ocean like shared infrastructure, where one simulation feeds the visible surface, the motion of floating objects, and the effects that happen when the camera goes underwater.
That shift sounds small until you feel the consequences. When the same source of truth powers both look and motion, you stop patching over mismatches with hand-tuned hacks.
Why this exists
The repo layout reads like a system meant to be embedded. Waves live in one folder, surface rendering in another, buoyancy in a third, and environment pieces like sky, floor, and passes sit beside them. That separation is the clue: this is not a one-off scene, it is a library that expects to be composed.
The demo side reinforces that bias. It is built around a spectator-first experience, which makes sense for an ocean engine. Before a developer asks it to be interactive, they need to trust that it can already hold the scene together.
One ocean, two consumers
The most interesting part of the architecture is not that it simulates waves. It is that the same wave field is reused by the renderer and the buoyancy system, instead of being recomputed or approximated in separate places.
That reuse is what turns a pretty ocean into a coherent one. The surface can show a wave, the sampler can measure the same wave, and the floating object can react to that measurement without the two systems drifting apart.
Inside the wave stack
The simulation is built around a cascade approach, which means the engine does not pretend one frequency range can describe the whole sea. Large swells, medium waves, and small ripples are layered together so the motion stays believable at multiple scales.
Under the hood, the wave model is seeded by a JONSWAP spectrum and then pushed through FFT compute work on the GPU. The interesting optimization is the combined shader path, which processes displacement components together instead of dispatching them one by one, cutting work and keeping the pipeline tighter.
That matters because the outputs are not just aesthetic. The engine wants displacement and normals ready for rendering, sampling, and downstream environment effects, all without turning the browser into a math lab.
Why the geometry matters
The mesh has to do the same job the simulation does: spend detail where the viewer can notice it, and stop wasting effort where they cannot. That is where clipmaps and nested grids come in.
Near the camera, the water surface is dense enough to hold up under scrutiny. Far away, the geometry gets leaner, which keeps the horizon feeling endless without throwing triangles at empty distance.
WebGPU-first, but not WebGPU-only
WebGPU is the fast path, and it is clearly the path this engine wants. Compute shaders handle the heavy lifting, the data stays on the GPU, and the simulation can move with far less friction than a CPU-bound approach.
But the fallback is the bigger product decision. A WebGL path keeps the system usable on more browsers and more machines, which matters more than benchmark glory when the goal is to ship a real experience.
What the rest of the world usually does
Most water implementations fall into one of three buckets. They are either visual tricks, a GPU-only showcase that excludes older browsers, or a buoyancy layer bolted onto a surface that was never designed to inform physics.
surfing is trying to be the opposite of that. It is a reusable subsystem with simulation, rendering, buoyancy, and environment handling tied to one shared model, which is why it reads more like engine code than a scene setup.
| Dimension | Visual-only water | slavingia/surfing | WebGPU-only ocean engine |
|---|---|---|---|
| Simulation | No shared wave field | Shared GPU wave pipeline | Shared wave pipeline |
| Buoyancy | Usually hacked on separately | GPU sampler reads the same data as the surface | Often integrated, but tied to the newest runtime |
| Render fidelity | Good at a glance, brittle up close | Clipmaps, normals, and cascades stay coherent | High, but platform reach can narrow |
| Browser reach | Broad | Broad with WebGPU fallback | Narrower |
| Environment systems | Extra code on the side | Underwater, caustics, and floor systems are part of the stack | Varies by implementation |
| Developer ergonomics | Easy to start, hard to extend | One model, many consumers | Powerful but easier to fragment |
The point of the project
The win here is not prettier water. It is a cleaner contract between simulation and scene, where the ocean stops being a special case and starts behaving like infrastructure.
That is why this repo stands out. It is not just drawing a surface. It is organizing a world where the same wave data can carry the look, the motion, and the transitions that make the scene feel real.