Grass Field
App: apps/grass_field/
Demonstrates enhanced instancing (engine PLM-248 / ADR 0081) end to end: one
InstancedMesh draws 4096 grass blades in a single draw call,
each blade carrying its own base colour and height / wind-phase params, swaying
against the global frame clock + wind, with per-instance frustum culling dropping the
blades outside the view. The blade count exceeds the instance SSBO’s initial capacity, so
it also exercises the renderer’s per-frame SSBO grow.
Run from root
Section titled “Run from root”% ./plume3d grass_fieldThe camera sits raised near one edge and looks across the field, so the far and side blades
fall outside the frustum and per-instance culling has something to drop. The demo captures a
screenshot at frame 80 — printing visibleCount versus the total — and exits at frame 100.
What it does
Section titled “What it does”-
Loads a grass shader (it must be the active shader — the instanced draw path uses the active shader), builds a single blade prototype (two crossed upright quads so it reads as 3D from any horizontal angle), and creates a GPU-instanced mesh with culling on:
Graphics.loadShader("shaders/grass")var blade = Graphics.newMesh(bladeVerts(), "triangles")_grass = _scene.createInstancedMesh("grass")_grass.setMesh(blade)_grass.useGpuInstancing = true_grass.setCullingEnabled(true) // per-instance frustum cull -
Scatters a 64 × 64 jittered grid (4096 blades over a 48 × 48 world area). Each blade gets a per-instance colour via
addInstanceColored(a green varied toward yellow-green / deep-green across the field) and per-instance params viasetInstanceParams(a height multiplier0.7–1.25and a wind-phase offset so blades don’t sway in lockstep):var idx = _grass.addInstanceColored(x, 0.0, z, 1.0, r, g, b)var scaleMul = 0.7 + 0.55 * hash(ix + 300, iz + 9)var windPhase = (x + z) * 0.6 + 6.28 * hash(ix + 17, iz + 41)_grass.setInstanceParams(idx, scaleMul, windPhase, 0.0) -
Sets a global wind direction + strength (fed to the frame block at set 0) that the grass shader reads to sway the blades:
Graphics.setWind(1.0, 0.0, 0.35, 0.18) -
Reads back the diagnostic
visibleCountto prove the culling is live — with this camera roughly 1700 of the 4096 blades are off-screen and not drawn:System.print("visibleCount = %(_grass.visibleCount) / %(_total) (culling on)")
- InstancedMesh —
createInstancedMesh,setMesh,useGpuInstancing,setCullingEnabled,addInstanceColored,setInstanceParams,visibleCount. - Graphics —
loadShader,newMesh,setWind(the global frame / wind uniform). - Scene —
createInstancedMesh,addNode,addCamera.
- The per-instance base colour and params ride the widened 96-byte instance SSBO record
(
[transform, baseColor, params]) — the same buffer as the transform, no extra descriptor. The mesh colour of the blade prototype is white; the shader tints it by each instance’sbaseColor. visibleCountis a diagnostic only. It equals the instance count with culling off and drops below it as blades leave the frustum; under split-screen it reflects the last viewport.- Enhanced instancing scales to large foliage / scatter fields. Multi-prototype scatter
(more than one GPU-instanced mesh in a frame) is a documented follow-up —
grass_fielduses a single instanced mesh.
See InstancedMesh for the full instancing surface and the Instanced Mesh GPU example for the plain transform-only path.