Reference
Quality and performance
Four tiers, from integrated laptop GPUs to desktop cards. The top two switch the waves to a GPU FFT. Every setting can be overridden on its own, at creation or at runtime.
const water = await WaterSystem.create({ renderer, scene, camera, quality: 'medium' });
water.setQualityLevel('ultra');
water.setQualityLevel('high', { msaa: 0, ssrSteps: 32 }); // tier + overrides
water.qualityName; // 'high'
water.quality; // the effective settings

sunset, low against ultra. Low uses 24 Gerstner waves at 75 % resolution with no bloom, foam field or reflections; ultra uses the FFT with three 256² cascades.#Every field
| Field | low | medium | high | ultra | |
|---|---|---|---|---|---|
waveSource | gerstner | gerstner | fft | fft | Where the waves come from |
fftSize | 128 | 128 | 128 | 256 | FFT resolution per cascade (power of two) |
fftCascades | 2 | 2 | 2 | 3 | 1–3 cascades: 512 m, 67.3 m rotated 35°, 10.7 m rotated 78° |
waves | 24 | 32 | 40 | 56 | Gerstner components. On FFT tiers only used if you override waveSource |
gridAngular × gridRadial | 160 × 140 | 208 × 170 | 256 × 200 | 320 × 240 | Polar grid round the camera |
renderScale | 0.75 | 0.9 | 1 | 1 | Offscreen resolution relative to the drawing buffer |
msaa | 0 | 0 | 4 | 4 | World-pass MSAA samples |
bloomLevels | 0 | 4 | 5 | 6 | Bloom mips (0 = off) |
foamResolution | 0 | 256 | 512 | 1024 | Persistent foam field texels (0 = instant foam only) |
foamExtent | 256 | 256 | 320 | 400 | Foam field size round the camera, m |
ssrSteps | 0 | 0 | 20 | 32 | Screen-space reflection steps (0 = sky only) |
detailLayers | 1 | 2 | 2 | 2 | Micro-normal layers |
godRaySteps | 0 | 6 | 10 | 16 | Underwater light-shaft samples (0 = off) |
bathymetryResolution | 192 | 256 | 384 | 512 | Default seabed bake size |
setQualityLevel always recompiles the ocean material. The spectrum, grid, FFT and foam field are rebuilt only when their settings change. A bathymetry baked without an explicit resolution is re-baked at the new tier's size.
#Measured frame rates
Apple M5, headless Chromium with ANGLE/Metal, 1280×720, the full demo scene, uncapped (--disable-gpu-vsync --disable-frame-rate-limit), GPU otherwise idle, measured back to back with the original page:
| fps | |
|---|---|
| The original single-file page | ~200 |
medium (Gerstner 32) | ~220 |
high (FFT 128² × 2, MSAA 4×, SSR, bloom, foam field) | ~140 (120 with vsync) |
ultra (FFT 256² × 3) | ~107 |
Run npm test to print the fps line on your own machine. Run one smoke test at a time; parallel GPU runs skew the numbers.
#Where the time goes
water.profiling = true;
setInterval(() => console.table(water.gpuTimings), 2000); // ms per pass, smoothed- World-pass MSAA is the biggest single cost on high, about 20 %.
setQualityLevel('high', { msaa: 0 })trades it for aliased silhouettes. - Resolution.
renderScalescales every offscreen target. The demo also lowers its pixel ratio in 0.25 steps (down to 0.75) when the frame time stays above about 34 ms. - Reflections.
ssrSteps: 0falls back to sky-only reflections. - Waves.
fftCascades: 2on ultra, orwaveSource: 'gerstner'on high, cuts the FFT cost. - Foam. A smaller
foamResolutionkeeps trails and whitecaps with coarser lace.0turns the field off.