Ultra-Water README

The project's README from github.com/Humpalumps/Ultra-Water. As one markdown file: README.md.

Ultra Water

Real-time ocean renderer for three.js (WebGL2), with a demo island scene.

npm install
npm run dev

Open the printed localhost URL. Orbit with the mouse (or a finger: phones get their own profile, see Phones and tablets). Pick shots from the camera dropdown (they also set the sun). The performance panel (top left) shows the GPU, render size, fps, ms per frame and a 20 s graph. Its Uncap FPS button switches the loop off the display's refresh: frames then render back-to-back, with at most three in flight on the GPU, so the counter shows what the machine can do while the scene keeps running live. The screen still updates at its own rate. Cap to display switches back. Run benchmark (also benchmark (uncapped) in the settings) renders 300 frames back-to-back with the render loop paused and times them to GPU completion with a fence. The screen freezes for about a second while it runs, so it only runs when asked.

Performance

Measured with the in-page benchmark on an RTX 3060 (Chrome, ANGLE/D3D11), 4× MSAA, all features on, at 1954×1692 (3.3 MP, the pixel count of 1440p; devicePixelRatio 2 on the browser window):

Shot ms / frame fps (uncapped)
Open ocean 3.30 ~303
Breakers 4.17 ~240
Golden hour 4.20 ~238
Lagoon 4.48 ~223
Flotsam (10 floating props) 4.62 ~217
Underwater 4.71 ~212
Aerial 4.80 ~208

Per pass in Breakers: water 1.66 ms, opaque (island, rocks, props) 1.21, post 0.37, caustics 0.24, FFT sim ~0.45 (independent of resolution). Caustics cost more under a low sun (about 0.36 ms at 3.5° elevation), because the refracted rays fold more.

The crest-foam upgrade landed after this run. It adds three texture taps and some ALU per foam pixel only. It hasn't been re-measured cleanly yet, because another application was loading the GPU at the time.

Earlier versions of this README quoted numbers 2–4× lower. Those came from a benchmark that synced with readPixels/finish, which ANGLE doesn't wait on. The fence-based benchmark is the one to trust. The HUD's GPU timer-query numbers read high while vsync-capped, because the GPU downclocks between frames.

Phones and tablets

The same build spots phones and tablets (a finger as the main pointer, matchMedia('(pointer: coarse)')) and starts them on a lighter profile. Desktop is unchanged. Add ?mobile or ?desktop to the URL to force either.

The mobile profile is resolution 0.6 (of the device pixel ratio, capped at 2), 2× MSAA, reflections at 0.35 scale and no bloom. On a phone the cost is mostly the number of render passes Chrome's GPU process has to translate, not pixels, so dropping bloom's 12 passes did more than lowering resolution. Auto resolution (auto resolution in the Quality folder) steps down by 0.05, to a floor of 0.5, when more than 4% of frames miss vsync over 2 s. It climbs back to 0.6 once frames are clean, and locks to a steady 30 fps if the floor still misses more than a quarter.

Measured on a OnePlus Nord 4 (Snapdragon 7+ Gen 3, Adreno 732, Chrome 156, which runs pages at 60 Hz on it), portrait, 424×782:

Shot live fps missed frames fps (uncapped)
Open ocean 60 0% 142
Aerial 60 0% 118
Underwater 60 0% 103
Lagoon 60 0% 102
Flotsam 60 0% 101
Golden hour 60 0% 99
Breakers 60 0% 95

With the desktop settings (708×1304, 4× MSAA, bloom) the same phone ran 41–50 fps live, missing up to a third of its frames, and 62 fps uncapped on average. Phone numbers move with temperature. These were taken warm (thermal status 1) while charging over USB.

What's in it

Waves (src/water/OceanSim.js)

Surface (src/water/OceanSurface.js)

Caustics (src/water/Caustics.js)

Underwater (src/water/UnderwaterPass.js, underside branch in OceanSurface.js)

Floating objects (src/water/WaterQuery.js, src/scene/floaters.js, src/scene/floaterMeshes.js, src/scene/floaterShadow.js)

Pipeline (src/main.js)

  1. Ingest the last water query, step the floaters, then:
    • render the floater shadow map;
    • update the impact rings;
    • run the FFT;
    • splat the caustics;
    • submit the next query.
  2. Planar reflection (half res, skipped underwater).
  3. Opaque pass into a single 4× MSAA HDR target.
  4. Water pass, depth-tested against the kept MSAA depth for correct per-sample edges.
  5. Underwater pass, when near or below the surface.
  6. Post (PostFX.js):
    • GPU auto exposure, metered in log space and adapted over time; the GUI value is compensation.
    • Dual-filter bloom with Karis average.
    • AgX.
    • Lens droplets.
    • TPDF dither.

Scene

The Water folder has a colour preset dropdown (clear, tropical, toxic green, purple, rust orange, murky brown). Each sets the absorption, in-scatter colour, thin-foam tint and underwater scattering together; clarity and scatter still adjust on top.

Camera presets Flotsam and Underwater spawn a field of floating props. The Floaters & underwater folder has spawn, drop and clear buttons, plus drag, buoyancy, scattering, shaft and meniscus controls.

Not in yet

The design was argued out with a second agent before building, and again for each round of upgrades.

License

MIT, see LICENSE. Code adapted from other projects is listed in THIRD_PARTY_NOTICES.md.

Ultra-Water (Humpalumps/Ultra-Water)

Searching Database…

Open the printed localhost URL. Orbit with the mouse (or a finger: phones get their own profile, see Phones and tablets). Pick shots from the camera dropdown (they also set the sun).

Creator
Humpalumps
Size
0.1 MB
Stars
436
Updated
CommitOct 28
Age
2d
Commits · 90 Days