More
Troubleshooting
The problems people hit most often, and the fix for each.
#No grass at all
update()is never called. The chunk lists are built ingrass.update(dt). Until the first call, nothing is drawn.- The camera is outside
bounds. Grass never grows outside the baked rectangle. Checkgrass.terrain.bounds. - The mask is 0 everywhere.
grass.terrain.maskAt(x, z)shows what was baked. Masks are clamped to 0..1. - The terrain is far below or above the ground mesh. The blades sit on
heightAt, not on your mesh. Build both from the same function. grass.enabledisfalse.
#A hard line where the grass ends
Call grass.patchTerrainMaterial(groundMaterial). Without it, the edge of the far ring shows as a line against your ground colour. If your ground uses a custom ShaderMaterial, wire in GRASS_COLOR_GLSL and turfColor() yourself. See No visible ring.
#Blades poke through the water
Return 0 from mask wherever there is water or beach, then call grass.setTerrain() if you changed it at runtime. The fireflies stay off masked ground too.
#The grass doesn't receive shadows
- Turn on
renderer.shadowMap.enabledand keeprenderer.shadowMap.typeat the defaultTHREE.PCFShadowMap. The grass is unshadowed with VSM or basic shadow maps. - The light needs
castShadow = true, and it must be bound (bindLights(sun)) or passed assetLighting({ shadowLight: sun }). - The shadow camera must cover the area around the camera. The grass beyond it is lit but unshadowed.
Blades never cast shadows. That is by design.
#The grass is too bright or too dark
The grass reads light in three.js physically based units. sunColor means DirectionalLight.color × intensity, and ambient means HemisphereLight.color × intensity. If you pass values by hand from an older, non-physical setup, multiply them by π, as the demo does. With bindLights(), match your lights to the rest of the scene and the grass follows.
#Trampling doesn't show
- The object must be
visible. Hidden tramplers are skipped. - It must be near the ground. Flattening fades out from 1.2 × radius above the terrain and stops at 2.7 × radius. The terrain height is
grass.terrain.heightAt(x, z). grass.trample.enabledmust betrue.- There are at most 16 stamps per frame. Extra tramplers and one-shot stamps are dropped for that frame.
- It must be inside the trample buffer,
trampleSizemetres (default 160) around the camera.
#The gusts run at double speed
Two grass systems share one WindField. Each one calls wind.update(dt). See Who advances the wind.
#The chunks pop at the screen edge
Chunk culling uses the baked height range. Terrain spikes narrower than one bake texel (about 0.9 m at the default resolution) can make a chunk's box too low. Raise terrain.resolution, up to what your heightAt can afford at bake time.
#A slow setTerrain()
The bake calls heightAt and mask once per texel: about a million calls at 1024². Make those functions cheap, lower resolution, or re-bake only when the terrain actually changes.
#Scatter layers ignore my options
Before 1.1.0, addFlowers(), addRocks(), addTrees() and addBushes() dropped the layer options they didn't name, such as filter or maskPower. Update to 1.1.0, where they are forwarded to addLayer(). See the changelog.
#It fails on a device
The grass needs WebGL2 and float colour buffers (EXT_color_buffer_float). three enables the extension, and all desktop GPUs support it. On other devices, check renderer.getContext().getExtension('EXT_color_buffer_float').