Start here
Quick start
Grass on your own terrain in an ordinary three.js scene. It follows your lights, receives the sun's shadow map, paints turf colour on your ground, and flattens under a rolling ball.
This is the complete examples/basic/ integration, with commentary. Every step is required except where it says optional.
#Set up the renderer
Give the renderer a tone mapper. Enable shadows if you want the grass to receive the sun's shadow map. It reads the default THREE.PCFShadowMap.
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GrassSystem } from 'naturegl-grass';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.5));
renderer.setSize(innerWidth, innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.shadowMap.enabled = true; // PCF (default): the grass receives it
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x9fb8d0);
scene.fog = new THREE.FogExp2(0x9fb8d0, 0.012); // the grass uses scene fog
const camera = new THREE.PerspectiveCamera(55, innerWidth / innerHeight, 0.1, 500);
camera.position.set(8, 4, 10);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1, 0);#Describe your terrain
The grass needs a height function and the area it may grow in. The optional mask returns 0 where no grass should grow. Here it cuts out a pond.
const heightAt = (x, z) => 1.5 * Math.sin(x * 0.08) * Math.cos(z * 0.06) + 0.6 * Math.sin(x * 0.2 + z * 0.13);
const pond = (x, z) => THREE.MathUtils.smoothstep(Math.hypot(x + 6, z - 4), 3.5, 5);
const terrain = { heightAt, bounds: [-100, -100, 100, 100], mask: pond }; // minX, minZ, maxX, maxZThe ground mesh is yours. Build it from the same function:
const groundGeo = new THREE.PlaneGeometry(200, 200, 200, 200).rotateX(-Math.PI / 2);
const pos = groundGeo.attributes.position;
for (let i = 0; i < pos.count; i++) pos.setY(i, heightAt(pos.getX(i), pos.getZ(i)));
groundGeo.computeVertexNormals();
const groundMat = new THREE.MeshStandardMaterial({ color: 0x3b2f22, roughness: 1 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.receiveShadow = true;
scene.add(ground);#Add your lights
Ordinary three.js lights. The grass reads them in three.js units.
const sun = new THREE.DirectionalLight(0xfff1dd, 5);
sun.position.set(-30, 40, -20);
sun.castShadow = true;
Object.assign(sun.shadow.camera, { left: -30, right: 30, top: 30, bottom: -30 });
scene.add(sun);
const hemi = new THREE.HemisphereLight(0xbcd4ff, 0x4a3f2a, 1.2);
scene.add(hemi);#Create the grass
create() bakes the terrain, builds the LOD rings for the quality tier and adds them to your scene.
const grass = await GrassSystem.create({ renderer, scene, camera, terrain, quality: 'high', preset: 'meadow' });
grass.bindLights(sun, hemi); // sun direction/colour, ambient, and the sun's shadow map
grass.patchTerrainMaterial(groundMat); // turf colour on the ground under and behind the bladespatchTerrainMaterial() is what hides the edge of the last LOD ring. See Chunked LOD tiles.
#Add a trampler (optional)
Any Object3D works: a player, a wheel, a ball.
const ball = new THREE.Mesh(new THREE.SphereGeometry(0.5, 32, 16), new THREE.MeshStandardMaterial({ color: 0xc0392b }));
ball.castShadow = true;
scene.add(ball);
grass.addTrampler(ball, 1.1); // radius in metres#Update, then render
Call update(dt) once per frame, before renderer.render. It advances the wind and trample buffer, culls the chunks and runs the blade pass.
const timer = new THREE.Timer();
timer.connect(document);
renderer.setAnimationLoop(t => {
timer.update(t);
const dt = Math.min(timer.getDelta(), 0.05), e = timer.getElapsed();
ball.position.set(Math.cos(e * 0.4) * 5, 0, Math.sin(e * 0.4) * 5);
ball.position.y = heightAt(ball.position.x, ball.position.z) + 0.5;
controls.update();
grass.update(dt);
renderer.render(scene, camera);
});
addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});meadow, the ball registered as a trampler, and the trail it leaves behind it.#Change the look
grass.loadPreset('golden'); // one of the seven presets
grass.setParams({ grass: { height: 1.4, season: 0.6 } }); // partial: the rest keep their value
grass.setParams({ wind: { speed: 1.2, gust: 1, direction: 90 } });
grass.setQualityLevel('ultra'); // cheap, runs at runtime