NatureGL Islandv1.1.0

How it's built

Composing the libraries

How one game drives SkySystem, WaterSystem, GrassSystem and WeatherSystem together. NatureGL Sky is the light source, NatureGL Water owns the render pipeline, NatureGL Grass owns the wind, and NatureGL Weather is layered on top.

LibraryWhat the island uses it for
NatureGL SkyTime of day, sun, moon and stars, volumetric clouds and their shadows, the sky env map, the light rig (bindLights), and patchMaterial for cloud shadows on the game's own materials
NatureGL WaterThe FFT ocean, bathymetry-aware shore waves and foam, caustics on the terrain, reef and sea grass, underwater fog and light shafts, buoyancy and wake for the boat, and the HDR render pipeline (water.render())
NatureGL GrassThe grass field, the shared wind field (palms, trees, ferns, banana plants and the sea-grass surge all sway with it), trampling, scatter layers for bushes, rocks, flowers, beach tufts and shells, leaf texture and translucency
NatureGL WeatherRain mode: rain, splashes, lightning and wet surfaces

#NatureGL Sky: the light source

src/IslandGame.js
this.sun = new THREE.DirectionalLight(0xffffff, 3);
this.hemi = new THREE.HemisphereLight(0xbfd8ff, 0x6b5a40, 1);
scene.add(this.sun, this.sun.target, this.hemi);

this.sky = await SkySystem.create({
  renderer, scene, camera, quality: q.sky, qualityOverrides: q.skyOverrides,
  preset: 'partlyCloudy', autoExposure: true,
  maxExposure: 2.4, nightExposure: this.nightExposure,   // 5
});
this.sky.bindLights(this.sun, this.hemi, { distance: 150 });

bindLights drives the game's sun and hemisphere from the sky every frame, by day and by night. Cloud shadows reach the game's own materials through sky.patchMaterial(material): the terrain, the palms and trees, the village, the boat, the props, the sea grass, the fish, the whale, the birds and the crabs.

#NatureGL Water: the ocean and the pipeline

src/IslandGame.js
this.water = await WaterSystem.create({
  renderer, scene, camera, quality: q.water, preset: 'tropical',
  bathymetry: { heightAt: T.heightAt, bounds: T.bounds },
});
this.water.setSky(this.sky, { moonIntensityScale: 1 });
// the sky re-creates its env / shadow textures on quality changes
this.sky.addEventListener('quality', () => this.water.setSky(this.sky, { moonIntensityScale: 1 }));
  • Bathymetry is the same heightAt the terrain mesh, grass and gameplay use, so the shore waves break where the beach really is.
  • setSky(sky) makes the ocean read the sky's sun, moon, ambient, env map, cloud shadow, fog colour and display exposure. moonIntensityScale: 1 uses the physical moon.
  • Caustics are patched into the terrain and skirt (water.caustics.patch(material)), and into the reef and sea grass.
  • Transparent overlays (rain, bird splashes, marine snow, lamplight streaks) go on water.overlayLayer so they draw after the water surface.

#Exposure

The game never writes the renderer's exposure. NatureGL Water tone-maps with sky.displayExposure (already capped by maxExposure / nightExposure) times water.params.exposure, and the game sets only the second factor:

IslandGame.update()
this.water.params.exposure = this.waterExposure      // the water preset's own exposure
                           * this.exposureBias       // the HUD's Exposure slider
                           * this._underwaterExposure(); // pins the display exposure near 1.0 under water

#Render

IslandGame.render()
R.toneMapping = THREE.NoToneMapping; // water.render() tone maps
this.water.render();

#The optional sky post pass

game.setSkyPost(true) (or demo/?skypost) runs NatureGL Sky's post chain (height fog, god rays, clouds over geometry, bloom) over NatureGL Water's HDR render instead:

IslandGame.render() with setSkyPost(true)
R.toneMapping = THREE.ACESFilmicToneMapping;
const hdr = this.water.renderHDR();
this.post.composite(hdr.texture, hdr.depthTexture, this.camera);

Under water the plain water.render() path is always used, because it owns the underwater grade. The post pass is off by default: over the lagoon the difference is small and the drone views are already at the 60 fps budget.

Sky post pass
water.render()
water.render()Sky post pass
drone-lagoon at 16:36, without and with setSkyPost(true).

#NatureGL Grass: grass, wind and scatter

src/IslandGame.js
this.grass = await GrassSystem.create({
  renderer, scene, camera, quality: q.grass, preset: 'meadow',
  terrain: { heightAt: T.heightAt, mask: T.grassMask, bounds: { minX: -300, minZ: -300, maxX: 300, maxZ: 300 } },
});
this.grass.setParams({
  grass: { season: 0.22, height: 1.15, density: 0.95, clumping: 0.7 },
  wind: { speed: 0.55, gust: 0.6, direction: 250 },
});
this.grass.bindLights(this.sun, this.hemi);
this.grass.setCloudShadow(this.sky.cloudShadow);
this.grass.patchTerrainMaterial(tm.material, { amount: 0.85 });
this.grass.addTrampler(this.walker.body, 0.55);

The grass wind is the island's only wind. The palms and trees, the undergrowth scatter layers and the village cloth and signs all read grass.wind, so a squall moves everything together. The undergrowth (ferns, banana plants, bushes, rocks, flowers, beach tufts and shells) is a NatureGL Grass ScatterSystem, streamed round the camera with the same wind and trampling.

#NatureGL Weather

Rain mode creates the WeatherSystem the first time you switch it on:

src/world/rain.js
const w = await WeatherSystem.create({
  renderer: g.renderer, scene: g.scene, camera: g.camera,
  quality: g.qualityName === 'ultra' ? 'high' : 'medium',
  preset: 'clear', lights: false,
  occlusion: { size: 80, resolution: 256 },
  layer: g.water.overlayLayer,          // draw after the water surface
});
w.fog.sceneFog = false;                 // NatureGL Water manages scene.fog
w.setOccluders([g.village.group, g.vegetation.group, g.boat.group]);
w.patchMaterial(g.terrainMesh.material, {});
w.patchMaterial(g.village.material, {});

Every frame the rain is lit from NatureGL Sky's key light and the game's hemisphere, plus the village lamps:

js
w.setLighting({
  sunDirection: s.keyLight.direction, sunColor: s.keyLight.color, sunIntensity: s.keyLight.intensity,
  ambient: g.hemi.color.clone().multiplyScalar(g.hemi.intensity),
  localLights: g.village.lights,
});
w.update(dt);
Rain mode on the pier: storm sky, storm sea, rain and wet planks.