NatureGL Waterv1.1.0

More

Changelog

Every release of naturegl-water. The package follows semantic versioning.

#1.1.0

2026-09-24 These fixes came out of the NatureGL Island integration.

#Fixed

  • setSky(skySystem) threw a TypeError with NatureGL Sky, whose ambient is { color, skyColor, groundColor, intensity } with no sky. The ambient is now read from a Color, ambient.sky, ambient.color or skyColor × intensity, falling back to the horizon × 0.6. water.setSky(sky) with a real SkySystem works as documented.
  • External-sky moonlight was a hard-coded constant, about 25× NatureGL Sky's moon. With a sky whose moon has color and intensity, the night key light is moon.color × moon.intensity × moonIntensityScale (× the optional moonTint), faded in below the horizon. The constant remains the fallback.
  • Shore-wave foam rings over wide shelves. Offshore trains keep their own k·a, reach shoreSteepness only as they break, and fold into the whitecap Jacobian only past a breaking index of about 0.6. Trains are limited to water shallower than about 2–4× the breaker depth. On a flat shelf they are depth-limited bores (H < 0.44 d). Swash foam is limited to about 6–18 m from the waterline. Breaker foam starts at a breaking index of 0.62 (was 0.75). Read more.
  • Bull's-eye rings round small islets. Bathymetry bakes a shore-train weight from the area of the nearest land mass (shoreTrainMinRadius, default 40 m), and a slow phase wobble breaks up perfect contour crests.
  • bindLights() with an external sky used the built-in sky's hemisphere ground colour. It now uses the external ambient.ground or groundColor × intensity.

#Added

  • Param shoreSteepness (default 0.92, eased).
  • BathymetryDesc.shoreTrainMinRadius, Bathymetry.trainTexture (RG: train weight, breaking allowance), sample().train and .breaking, and the uniform tShoreW in WAVES_GLSL.
  • SetSkyOptions.moonIntensityScale, .moonTint and .syncExposure (default true: render() tone-maps with sky.displayExposure × params.exposure when the sky provides it, as NatureGL Sky 1.1 does), and the read-only water.exposure.
  • water.renderHDR() → { texture, depthTexture }: world, water and overlay in linear HDR with world and water depth, for external post chains such as NatureGL Sky's SkyPostPass.composite(). render() is unchanged. Read more.
  • BuiltinSky.bindLights(…, { groundColor }).
  • Examples: examples/with-sky/ (a real SkySystem with day, night and the sky post pass, built when the sibling naturegl-sky/build/ exists) and examples/lagoon/ (a shore-wave stress test).

#Changed

  • Shore waves look calmer offshore on every coast. Raise shoreWaves or shoreFoam if you relied on the old offshore foam.

#1.0.0

2026-09-24 The first packaged release, ported from the single-file index.html ("WATER FREE" ocean and Dive reef).

#Library

  • The WaterSystem facade: create, update, render, resize, loadPreset, setParams, getParams, setQualityLevel, setSky, bindLights, setBathymetry, sampleHeight, sampleNormal, isUnderwater, addWake, dispose, profiling / gpuTimings and stats.
  • A GPU FFT ocean (Tessendorf): JONSWAP directional spectrum, 2–3 rotated non-harmonic cascades in one Float32 atlas (2·log2 N Stockham passes), mip-mapped HalfFloat outputs, and an asynchronous read-back of the swell cascade for CPU sampling and buoyancy. The default wave source on high and ultra.
  • A JONSWAP-sampled Gerstner spectrum (24–56 waves) on a camera-centred polar grid for low, medium and the fallback. Long waves' normals and Jacobian are summed per vertex, the short band per pixel, and sub-pixel waves become LEAN roughness.
  • The depth pipeline: a HalfFloat world target with a DepthTexture (MSAA on high and ultra), Beer–Lambert thickness, in-scatter, refraction with forward-scattering blur, screen-space reflections, GGX glitter with a clamped peak, crest SSS, bloom and ACES.
  • A baked bathymetry (EDT shore distance and direction): shoaling, breakers (Green's law, breaking index), swash foam, and depth-buffer contact foam.
  • A persistent camera-centred foam field, with mip-mapped foam lace and micro-normal textures (Toksvig).
  • Buoyancy (single-probe spring and tilt, multi-probe least-squares plane), wakes (contact foam, Kelvin V, persistent trails) and a caustics material patch.
  • Underwater: fog and absorption, volumetric light shafts, Snell's window with the refracted scene, a TIR mirror, and a near-plane waterline split with a meniscus.
  • BuiltinSky (the ported analytic sky), the SkyLike composition contract with setSky(), and the overlay layer for effects drawn after the water.
  • 7 presets (Tropical, Sunset, Dusk, Storm, Foggy, Moonlit, Arctic), one file each, and 4 quality tiers.

#Demo

  • The index.html island (terrain, basalt headland, palms with wind sway, jungle), buoys, Kenney Pirate Kit ships, dock, wreck and props (extracted to .glb files), and the reef planted on the island shelf. Dive now goes under the same ocean.
  • The original UI (presets, Wind / Wave / Time / Cloud, Dive), plus quality buttons, Copy preset JSON and the window.__pro contract.

#Tooling

  • npm test, a headless real-GPU smoke test. scripts/shot.mjs and scripts/perf.mjs dev helpers. Examples: basic (Vite), CDN (import map) and external sky.

#Performance

Apple M5, headless Chromium with ANGLE/Metal, 1280×720, full demo scene, uncapped: the original page ~200 fps, medium ~220, high ~140 (120 with vsync), ultra ~107. See Quality and performance.