NatureGL Waterv1.1.0

Guides

Composing with other packs

The water renders into a scene you own and takes its light from whatever sky you give it. NatureGL Sky plugs in with one call, NatureGL Weather's particles go on the overlay layer, and NatureGL Island builds a whole game on the combination.

#The contract

  • The ocean is a mesh in your scene. create() adds water.ocean and, unless you plug in another sky, the built-in dome. dispose() removes both.
  • Offscreen work happens in update(dt). Waves, the FFT passes, the foam simulation, buoyancy and wakes all run there.
  • render() owns the depth pipeline. Your scene goes into an HDR target with depth, the water draws over it, and the composite tone-maps. renderHDR() stops before bloom and tone mapping for other post chains.
  • The sky is any object with a sun. setSky() reads it every update(), so a live, animated sky works.

#With NatureGL Sky

js
import { SkySystem } from 'naturegl-sky';
import { WaterSystem } from 'naturegl-water';

const sky = await SkySystem.create({
  renderer, scene, camera,
  maxExposure: 2.4, nightExposure: 5,   // keep moonlit nights dark
});
const water = await WaterSystem.create({ renderer, scene, camera, sky });   // or water.setSky(sky)

// the sky swaps its env and shadow textures on quality changes: re-plug
sky.addEventListener('quality', () => water.setSky(sky));

renderer.setAnimationLoop(() => {
  sky.update(dt);
  water.update(dt);
  water.render();   // tone-maps itself, matched to sky.displayExposure
});

The water reads the sky's sun, moon (color × intensity), ambient.color, envMap, cloudShadow, fogColor and, from NatureGL Sky 1.1, displayExposure. The ocean then reflects the live clouds, cloud shadows sweep across the water, and the sea and sky agree on brightness at every hour. Light the rest of your scene with sky.bindLights(sun, hemi) or water.bindLights(sun, hemi); both follow the same sun.

#Exposure

With syncExposure on (the default) and a sky that has a numeric displayExposure, render() tone-maps with sky.displayExposure × params.exposure. The water's exposure param becomes a multiplier on the sky's metered exposure, and water.exposure reads back the result. Set maxExposure and nightExposure on the sky, not on the water, to keep nights dark.

#Water and the sky's post pass

For NatureGL Sky's height fog, god rays, clouds over geometry and bloom, replace water.render() with renderHDR() and the post pass's composite():

js
renderer.toneMapping = THREE.ACESFilmicToneMapping;   // the sky post pass uses the renderer's tone mapping
const post = sky.createPostPass();

renderer.setAnimationLoop(() => {
  sky.update(dt);
  water.update(dt);
  const hdr = water.renderHDR();                          // scene + ocean, linear HDR + depth
  post.composite(hdr.texture, hdr.depthTexture, camera);  // fog, god rays, clouds over, bloom
});

The water still fogs itself with fogDensity. Lower it, or create the water with manageFog: false, if the sky's fog is enough. NatureGL Island switches back to water.render() while the camera is under water, because the composite owns the underwater grade.

#Any sky-like object

setSky(sky, options?) accepts any object shaped like this. Only sun is required.

ts
interface SkyLike {
  sun: { direction: THREE.Vector3; color: THREE.Color; intensity?: number };
  moon?: { direction: THREE.Vector3; color?: THREE.Color; intensity?: number };  // key light when the sun is down
  envMap?: THREE.Texture;                                   // equirectangular radiance for reflections
  cloudShadow?: { texture: THREE.Texture; matrix: THREE.Matrix4 };   // matrix: world position -> uv
  fogColor?: THREE.Color;                                   // horizon haze
  ambient?: THREE.Color
    | { sky: THREE.Color; ground?: THREE.Color }
    | { color: THREE.Color; skyColor: THREE.Color; groundColor: THREE.Color; intensity: number };
  displayExposure?: number;                                 // NatureGL Sky >= 1.1
}
OptionDefault
sunIntensityScale1The water's key light is sun.color × sun.intensity × this
moonIntensityScale11.1 At night: moon.color × moon.intensity × this
moonTint—1.1 A tint multiplied into the moonlight, for example 0x8ca6ff
envIntensity1Multiplies the envMap reflections
syncExposuretrue1.1 Tone-map with sky.displayExposure × params.exposure
  • Ambient is read from a Color, ambient.sky, ambient.color or ambient.skyColor × ambient.intensity, in that order. Without any, it's fogColor × 0.6.
  • Moon. When the sun is below the horizon the key light switches to the moon, faded in over the first degrees of dusk. Skies without moon.intensity get a fixed dim blue moonlight.
  • Background. With an external sky the scene background is yours, for example scene.background = sky.envMap. NatureGL Sky adds its own backdrop mesh.

examples/external-sky/ builds a sky object by hand: a canvas-gradient equirect env map, a fixed low sun and a scrolling cloud-shadow texture.

js
const sky = {
  sun: { direction: sunDir, color: new THREE.Color(1.0, 0.72, 0.48), intensity: 2.2 },
  envMap: makeEquirect(sunDir),
  fogColor: new THREE.Color(0.85, 0.62, 0.45),
  ambient: new THREE.Color(0.25, 0.3, 0.45),
  cloudShadow: { texture: makeCloudShadow(), matrix: new THREE.Matrix4() },
};
scene.background = sky.envMap;
water.setSky(sky);

#With NatureGL Weather and NatureGL Island

LibraryHow it fits
NatureGL WeatherCreate it on the overlay layer, WeatherSystem.create({ renderer, scene, camera, layer: water.overlayLayer }), so rain, splashes and snow draw over the water. Set weather.fog.sceneFog = false, because the water manages scene.fog. Call water.render(), not weather.render()
NatureGL IslandUses all of it: one SkySystem plugged in with setSky, the terrain as bathymetry, caustics on the seabed, a buoyant boat with a wake, and the sky post pass over renderHDR()
NatureGL SkyThe light source, above