Model or dataset
Noniv/web-ocean avatar
Noniv/web-ocean

web-ocean renders a whole sea in one WebGPU HTML file

Real-time WebGPU ocean renderer written from scratch, no engine. FFT waves, whitecaps, sun glitter, physically based sunset sky with clouds, free camera. Built with Claude Code (Opus 5.5). Opens from a single 61 KB HTML file in Chrome.

899 stars141 forksHTMLMIT

At a glance

What is it?
web-ocean is a real-time ocean renderer written from scratch on WebGPU with no engine or rendering library: a six-cascade FFT wave simulation, a CDLOD quadtree surface out to 160 km, whitecaps from the surface Jacobian, a physically based sunset sky and AgX tone mapping, delivered as a self-contained 61 KB HTML file that opens from the file system.
Who is it for?
Graphics engineers who want to read a complete WebGPU renderer, or developers who want a drop-in ocean view for a demo, should look at web-ocean, because the whole pipeline from wave simulation through shading to post-processing fits in one readable HTML file with no engine underneath it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly HTML, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A renderer with no engine underneath it

web-ocean is a real-time ocean renderer written from scratch on WebGPU, with no engine or rendering library. It draws a sunset sea with simulated waves, whitecaps, sun glitter, and a physically based sky with clouds, and you fly around it with a free camera.

The delivery is the first thing to notice. Open ocean.html directly in the browser, and ocean.min.html is the same page minified, 61 KB instead of 102 KB. Both are self-contained and work from a file:// URL, which means there is no server, no build step and no asset pipeline between you and the image. The project is MIT licensed, it publishes no GitHub releases, and the last push was on 2026-10-17.

The requirement is narrow and stated plainly: a recent Chrome with WebGPU and a reasonably capable GPU. The README also warns about a laptop detail that catches people out. On a laptop you have to force Chrome onto the dedicated GPU in the Windows graphics settings, or it falls back to the integrated one and the experience collapses.

That is the whole proposition. A graphics demonstration that a reviewer can open on a laptop in five seconds, and a graphics programmer can then read end to end, because there is no framework hiding the mechanism.

The repository layout is accordingly plain: index.html with the page shell, HUD and settings panel styles, a js directory, the two built HTML files, server.mjs for development, build.mjs to produce the bundles, and docs for the README images.

Six FFT cascades from a kilometre down to millimetres

The sea surface is a Tessendorf FFT simulation with six cascades of 256 by 256 samples. The cascades tile a range of patch sizes, from 1103 m down to 0.26 m, so wavelengths run from about a kilometre to a few millimetres.

That range is the point. A single cascade cannot hold both the long swell and the fine capillary detail, so the project stacks six of them at different scales, and the smallest cascade is what produces the sharp sun sparkles near the camera.

The spectrum is a fetch-limited JONSWAP wind sea with Donelan-Banner directional spreading, plus a narrow long-crested swell, which is the standard way of describing a wind sea with a fetch limit, and the dispersion relation includes the capillary term. Choppy horizontal displacement sharpens the crests, which is what stops a spectrum-derived surface from looking like gentle swells.

The inverse FFTs are radix-2 compute shaders working in workgroup memory. There is no library involved; the transforms are written as WGSL and dispatched on the GPU.

The surface mesh is a separate concern from the spectrum. It is a CDLOD quadtree with 17 levels, where vertices are 6.25 cm apart near the camera and the mesh reaches 160 km, with continuous vertex morphing between levels so that the transition from fine to coarse is not visible. The result is a horizon that looks like an ocean rather than a tiled plane, at a cost that scales with the camera position rather than with the visible area.

Shading: slope statistics, anisotropic glitter and foam from folding

The shading section is where most of the work sits, and the techniques are named precisely.

Mip-mapped first and second moments of the wave slopes, that is LEAN mapping, give the right anisotropic roughness at every distance, so the sun glitter widens with distance and the far sea settles into a sheen without aliasing, and screen-space slope derivatives add specular anti-aliasing on top. The sun glitter itself uses an anisotropic Gaussian slope distribution with Smith masking and the size of the solar disc.

The sky reflection comes from an environment cube map rebuilt every frame, with roughness-filtered mips, and at grazing angles the reflection direction is tilted to account for which wave facets the viewer can actually see, which is the detail that makes a low camera height look right.

The water body is turbid blue-green lit by the whole sky hemisphere. Backlit crests get a subsurface glow, and wind gust patches modulate the short ripples.

Whitecaps are not painted on. They start where the total surface Jacobian folds, which is where the displacement map compresses the surface more than the geometry can represent. Foam is then stored in two camera-centred world-space maps, 128 m and 1 km wide, decays over time, drifts downwind, and breaks into streaks as it ages, with a procedural bubble texture giving it structure. That is a physically motivated trigger and a persistent simulation, and it is why the foam in this scene persists in the right places as the camera moves.

The README's own performance note is that the per-frame code reuses preallocated typed arrays and descriptors so the garbage collector has almost nothing to do, and that the only recurring allocations are the objects WebGPU itself requires, such as the swap-chain view and command buffers.

The sky, the clouds and the post chain

The atmosphere follows Hillaire's approach, with transmittance, multiple-scattering and sky-view lookup tables plus aerosol haze. Two cloud layers are simulated, a broken low cumulus and a fibrous altostratus deck, each shaded with self-shadowing toward the sun, lit rims, silver linings when backlit, and direction-dependent multiple scattering.

Two implementation details stand out. The cloud noise octaves are filtered by pixel footprint, and the silhouettes are anti-aliased analytically, with the screen-space derivative of each layer's edge function setting the width of its opacity edge, rather than relying on supersampling. Cloud transmittance along the sun direction dims the sunlight that reaches the water, and aerial perspective is applied over the sea, so the horizon and the water agree about how much light survives the atmosphere.

The scene renders in HDR with 4x MSAA, and the post chain adds an 8-level bloom, AgX tone mapping, a slight white balance shift and dithering. AgX is the notable choice, because it is a tone mapping curve designed to keep saturated highlights from turning into flat colour patches, which matters in a scene built around a low sun over specular water.

The HUD shows FPS and GPU time when the browser supports timestamp-query, so the numbers are measured on the machine rather than estimated. The README reports a figure of about 6 to 8 ms per frame at 2560 by 1440 on an RTX 5070 Ti, measured while other programs were also using the GPU, which is an honest caveat to quote alongside it.

Running it, and the URL parameters that make testing easy

The development path is three commands, and the build step is worth understanding because it is the only thing between you and a single-file artefact.

bash
npm install
npm start        # serves http://localhost:8080/

The package.json is minimal: a private ES module package named web-ocean at version 1.0.0, with a start script that runs the tiny static server and a build script that runs the bundler, and esbuild as the only development dependency.

After editing anything under js/, you run npm run build to regenerate both bundles. The minified build strips WGSL comments and whitespace and minifies the JavaScript with esbuild, which is why the minified file is 61 KB rather than 102 KB.

The URL parameters are the part that makes this project pleasant to evaluate. ?w=2560&h=1440 forces the render resolution, cam=x,y,z,yawDeg,pitchDeg sets the camera, fov=deg sets the field of view, ui=0 hides the HUD, and any settings key works too, for example windSpeed=12&swellHs=2&sunElev=8. That means a reviewer can reproduce a specific frame without touching the settings panel.

The controls follow the same pattern. Clicking the canvas captures the mouse and Escape releases it, with the right mouse button as an alternative, W A S D move, Space or E and Q or C go up and down, Shift gives five times the speed, the mouse wheel changes the base speed, and H opens the settings panel for wind, swell, choppiness, turbidity, foam, sun, haze, clouds, exposure and bloom. The camera also keeps itself above the displaced water surface, so waves never pass through it.

Known limitations, and how the project was built

The limitations section is short and worth reading in full, because each item bounds a use case.

Far water is still somewhat darker than the bright horizon sky above it. The water is tuned toward turbid coastal water, and the turbidity slider gives darker, bluer deep water when turned down. The scene is open ocean only, with no shoreline, no surf and no objects in the water. And the clouds are two flat layers, not volumetric.

The last one has a story behind it. The How it was made section says the project was built with Claude Code, with the request being the most realistic water it could produce in WebGPU with a floating camera, and the architecture and implementation left to it. It checked its own work by rendering screenshots in headless Chrome and having separate critic subagents review them, so it was not grading its own output, and the human feedback, mainly on the clouds, led to replacing the volumetric clouds with the flat layers and later to fixing their edge aliasing.

That account is unusually transparent, and it explains the shape of the result: the water and the sky are the deep work, while the parts that were cut for scope are the parts that a scene would need. If you want a beach, a boat or a volumetric sky, this is a starting point rather than a finished renderer.

The shader layout matches that division of labour. The js/wgsl directory holds the shaders as JavaScript modules: sim.js for waves and foam, sky.js for atmosphere and clouds, ocean.js for the surface and sky pass, post.js for bloom and tone mapping, and common.js for shared structs.

Editorial conclusion

Graphics engineers who want to read a complete WebGPU renderer, or developers who want a drop-in ocean view for a demo, should look at web-ocean, because the whole pipeline from wave simulation through shading to post-processing fits in one readable HTML file with no engine underneath it. It is the wrong tool if you need a scene, since the README states the scene is open ocean only with no shoreline, surf or objects in the water, and the wrong tool if you need volumetric clouds, because they were replaced by two flat layers. Before adopting, open ocean.html in a Chrome build with WebGPU, force the dedicated GPU on a laptop, and read the known limitations list, which is short and specific.

Frequently asked questions

What do I need to run the web-ocean renderer?

A recent Chrome with WebGPU and a reasonably capable GPU. On a laptop you have to force Chrome onto the dedicated GPU in the Windows graphics settings, otherwise it falls back to the integrated one. You can open ocean.html directly, since both bundles are self-contained and work from a file:// URL.

How do I run web-ocean from source?

Run npm install and then npm start, which serves the project on http://localhost:8080/ through the small static server. After editing anything under js/, run npm run build to regenerate ocean.html and ocean.min.html.

How is the wave surface simulated?

With a Tessendorf FFT simulation of six cascades of 256 by 256 samples, ranging in patch size from 1103 m down to 0.26 m, using a fetch-limited JONSWAP spectrum with Donelan-Banner directional spreading plus a long-crested swell. The inverse FFTs are radix-2 compute shaders, and the surface mesh is a CDLOD quadtree with 17 levels reaching 160 km.

What are the known limitations of web-ocean?

Far water is somewhat darker than the bright horizon sky, the water is tuned toward turbid coastal water, the scene is open ocean only with no shoreline, surf or objects, and the clouds are two flat layers rather than volumetric ones.

Official sources

  1. Issues
  2. License: MIT
  3. Noniv/web-ocean on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/noniv-web-ocean.svg)](https://hysenlabs.com/projects/noniv-web-ocean)

Was this useful?