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)orsetLighting({ sunDirection, sunColor, ambient }), plussetCloudShadow({ texture, matrix }). - Materials:
patchTerrainMaterial()andwind.patchMaterial()chain with otheronBeforeCompilepatches, 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.
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:
// 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:
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.grassMaskis 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
ScatterSystemcreated 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:
grass.wind.patchMaterial(palmLeafMat, { height: 6, strength: 0.04, flutter: 0.03 }); // your trees
const w = grass.wind.sample(x, z); // rain, particles, audioSee Wind field for the options and the GLSL route.