Three.js Grass: How to Render a Grass Field
Three.js grass is built with InstancedMesh: one blade geometry (usually a couple of triangles or a simple bent-plane shape) drawn thousands of times in a single draw call, with per-blade position and rotation set via instance transforms and wind sway added in the vertex shader. Never one mesh per blade.
The technique: one draw call, thousands of blades
A grass field is not thousands of separate meshes; that would murder your frame rate. The standard approach:
- One low-poly blade geometry. A single quad, a bent quad (a few segments so it can curve), or a simple 2-triangle blade. Keep the vertex count tiny; you're about to draw it thousands of times.
THREE.InstancedMeshwith that geometry and a shared material. You set an instance count up front (e.g. 50,000) and a 4x4 transform matrix per instance for position, rotation and scale, scattered across your terrain, usually with some randomness so the field doesn't look like a grid.- Wind in the vertex shader. Static grass reads as a texture, not a plant. Bending the top vertices of each blade based on a scrolling noise or sine function (sampling world position + time) is what sells the effect, and it costs almost nothing since it runs per-vertex, not per-blade in JavaScript.
- Density via instance count, not geometry. If a field looks sparse, raise the instance count before you reach for more detailed blade geometry. Density reads more than shape at grass scale.
This is exactly the technique behind most "realistic grass field" demos you'll find in the showcase below, and it's a small amount of code for the result.
Adding wind sway
Wind is the difference between "grass" and "green triangles." The common approach in a custom ShaderMaterial (or a TSL node material on WebGPU):
- Pass a
uTimeuniform, updated every frame. - In the vertex shader, only displace vertices near the top of the blade (interpolate by the blade's local Y so the base stays planted).
- Drive the displacement with a low-frequency sine or simplex noise sampled from world-space XZ plus time, so neighbouring blades sway together in waves rather than independently. That coherence is what makes it read as wind rather than noise.
- Two octaves (a slow broad sway plus a faster small flutter) reads far more convincing than one.
The same idea works identically in GLSL (ShaderMaterial) or in TSL (MeshStandardNodeMaterial with a custom positionNode); see the code tab below for both.
Keeping it fast at scale
Instancing gets you most of the way, but a large open field still needs a few more tricks once you're past tens of thousands of blades:
- Frustum and distance culling. Don't instance grass the camera can't see or is too far to resolve. Chunk your field into tiles and only instance the tiles near the camera, swapping instance buffers as the camera moves.
- LOD by distance. Full blade geometry up close, a flattened billboard or lower blade count far away.
- GPU-driven placement. For very large fields, computing instance transforms in a compute shader (WebGPU/TSL) instead of uploading a CPU-computed buffer avoids a large upload and lets density scale with GPU headroom rather than JavaScript time.
- Shared material, batched draw calls. Keep it to one
InstancedMesh(or a handful of tiles) rather than many small ones; draw call count matters more than triangle count for this kind of scene.
Tools and libraries
Code
Learn this properly
Learn Practical TSL
Your First Node Material
Learn TSL node materials from the ground up before tackling wind-shader effects like this one.
Frequently asked questions
How do you make grass in Three.js?
With THREE.InstancedMesh: one small blade geometry drawn thousands of times in a single draw call, with per-instance position and rotation, plus a vertex shader that bends the top of each blade for wind. You never create one mesh per blade.
How many grass blades can Three.js handle?
Tens of thousands of instanced blades run comfortably on modest hardware. Beyond that, add distance-based culling and LOD (fewer or flattened blades far from the camera), or move instance placement to a compute shader on WebGPU for very large fields.
Do I need a custom shader for grass, or can I use a standard material?
A standard material (MeshStandardMaterial) will render static grass fine, but the wind sway that makes it look alive requires a custom vertex shader: either GLSL via ShaderMaterial, or a TSL positionNode on a node material.
Keep reading
Water
Three.js water is a flat plane whose surface normal is perturbed by a scrolling or animated normal map (or procedural no…
Materials
For most realistic objects, use MeshStandardMaterial: physically-based, responds correctly to lights, and the sensible d…
WebGPU vs WebGL
WebGPU is the successor to WebGL: a lower-level, more modern graphics API that exposes compute shaders and reduces CPU o…
All Three.js Guides
Back to the guides hub.



