NatureGL Grassv1.1.0

Guides

With other packs

NatureGL Grass renders into a scene you own and takes its inputs as plain data. It needs a height function, a light and a camera. That makes it easy to light from NatureGL Sky, grow on your own heightfield, and share its wind and trampling with the rest of the world.

#The contract

  • Rendering: the grass adds meshes to your scene. update(dt) runs the offscreen passes (blade data and trample) and restores the render target it found.
  • Terrain: { heightAt(x, z), bounds, mask(x, z) }. Mask out water and beaches so blades never poke through a water surface.
  • Lights: bindLights(directional, hemisphere) or setLighting({ sunDirection, sunColor, ambient }), plus setCloudShadow({ texture, matrix }).
  • Materials: patchTerrainMaterial() and wind.patchMaterial() chain with other onBeforeCompile patches, such as NatureGL Sky's cloud shadows and NatureGL Weather's wetness, in either order.
  • Tramplers: addTrampler(player, radius).

#Lit by NatureGL Sky

NatureGL Sky drives your three.js lights. The grass follows the same lights, so both agree at every hour.

js
import { SkySystem } from 'naturegl-sky';
import { GrassSystem } from 'naturegl-grass';

const sun = new THREE.DirectionalLight();
sun.castShadow = true;
const hemi = new THREE.HemisphereLight();
scene.add(sun, sun.target, hemi);

const sky = await SkySystem.create({ renderer, scene, camera, preset: 'marine' });
sky.bindLights(sun, hemi);                  // the sky sets direction, colour and intensity

const grass = await GrassSystem.create({ renderer, scene, camera, terrain });
grass.bindLights(sun, hemi);                // the grass reads the same lights
grass.setCloudShadow(sky.cloudShadow);      // cloud shadows darken the sun on the blades
sky.patchMaterial(groundMat);               // ... and on the ground
grass.patchTerrainMaterial(groundMat);      // chains with the sky's patch

renderer.setAnimationLoop(() => {
  sky.update(dt);                           // updates the lights first
  grass.update(dt);
  renderer.render(scene, camera);
});

NatureGL Sky swaps its shadow textures when its quality changes, so plug them in again: sky.addEventListener('quality', () => grass.setCloudShadow(sky.cloudShadow)). Fireflies fade in with flies.intensity = sky.night.

#On your own heightfield

The terrain contract is a function, so any source works. Here is a Float32Array heightmap with bilinear sampling:

js
// heights: Float32Array of N × N samples covering [-size/2, size/2]² in metres
function heightfield(heights, N, size) {
  const at = (i, j) => heights[Math.min(j, N - 1) * N + Math.min(i, N - 1)];
  return (x, z) => {
    const gx = THREE.MathUtils.clamp((x / size + 0.5) * (N - 1), 0, N - 1);
    const gz = THREE.MathUtils.clamp((z / size + 0.5) * (N - 1), 0, N - 1);
    const i = Math.floor(gx), j = Math.floor(gz), fx = gx - i, fz = gz - j;
    const a = at(i, j), b = at(i + 1, j), c = at(i, j + 1), d = at(i + 1, j + 1);
    return (a + (b - a) * fx) * (1 - fz) + (c + (d - c) * fx) * fz;
  };
}

const heightAt = heightfield(heights, 513, 600);
const grass = await GrassSystem.create({
  renderer, scene, camera,
  terrain: {
    heightAt,
    bounds: [-300, -300, 300, 300],
    mask: (x, z) => THREE.MathUtils.smoothstep(heightAt(x, z), 0.8, 1.6),   // no grass below 0.8 m: beach and sea
  },
});

Build your ground mesh from the same heightAt, so the blades sit exactly on it.

#How NatureGL Island uses it

NatureGL Island grows its meadows with NatureGL Grass on the island heightfield, lit by NatureGL Sky, next to NatureGL Water's ocean. From IslandGame.js:

naturegl-island/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);             // the lights NatureGL Sky drives
this.grass.setCloudShadow(this.sky.cloudShadow);
this.grass.patchTerrainMaterial(tm.material, { amount: 0.85 });
// …
this.grass.addTrampler(this.walker.body, 0.55);
  • T.grassMask is 0 on sand, the path, rock and water.
  • The ground material also carries NatureGL Sky's cloud shadows and NatureGL Water's caustics. The turf patch chains with both.
  • Its undergrowth is a ScatterSystem created with { grass }, so it shares the terrain, the wind and the trampling.
  • In a storm the game raises the wind with grass.setParams({ wind: { speed: 1.3, gust: 1.1 } }).

#Share the wind

The wind field is the one thing the rest of your scene should borrow:

js
grass.wind.patchMaterial(palmLeafMat, { height: 6, strength: 0.04, flutter: 0.03 });   // your trees
const w = grass.wind.sample(x, z);                                                      // rain, particles, audio

See Wind field for the options and the GLSL route.