Skip to content

Scatter Multi

App: apps/scatter_multi/

Verifies two things about multiple GPU-instanced meshes in one frame — the orange field on the left and the cyan field on the right each render independently, at their own transforms, in their own shader’s colour.

  • PLM-257 — before the per-draw instance-SSBO sub-allocation, both instanced meshes wrote the shared instance buffer at offset 0, so only the last-submitted one rendered. PLM-257 gives each draw its own sub-region, so both fields render (the fix behind the “one GPU-instanced mesh per frame” limit once called out on Grass Field / Scatter Demo).
  • PLM-264 — each field’s prototype mesh carries its own per-mesh instancing shader (blade_a orange, blade_b cyan) while the active shader is a non-instancing plain shader. Before the fix, an instanced draw dropped the prototype’s per-mesh shader and fell back to the active shader, which applies an identity transform → every blade collapsed onto the origin. Now each field uses its own shader: two distinct colours at their transforms.
Terminal window
% ./plume3d scatter_multi

A raised camera looks across both fields. The demo captures a screenshot at frame 60 — printing each mesh’s visibleCount — and exits at frame 80. A pile at the origin, or one colour across both fields, means a regression.

  • Loads a non-instancing plain shader first (so it becomes the active/fallback shader) and both per-mesh instancing shaders:

    Graphics.loadShader("shaders/plain") // active fallback — NON-instancing (identity transform)
    Graphics.loadShader("shaders/blade_a") // per-mesh instancing shader (orange), for the LEFT field
    Graphics.loadShader("shaders/blade_b") // per-mesh instancing shader (cyan), for the RIGHT field
  • Creates two GPU-instanced meshes, each from a crossed-quad blade prototype that carries its own per-mesh shader (the 3-arg newMesh — this is the PLM-264 path), with per-instance frustum culling on:

    var greenBlade = Graphics.newMesh(bladeVerts(...), "triangles", "shaders/blade_a")
    _green = _scene.createInstancedMesh("green")
    _green.setMesh(greenBlade)
    _green.useGpuInstancing = true
    _green.setCullingEnabled(true)
    // … the blue field is the same with "shaders/blade_b" …
  • Fills each field with a jittered grid over its own half of the world via addInstanceColored + setInstanceParams — the orange field over the left half [-22,-1], the cyan field over the right half [1,22].

  • Sets a global wind (Graphics.setWind) both blade shaders read, and draws the scene (_scene.draw() submits both instanced meshes, each into its own SSBO sub-region and with its own per-mesh shader).

  • InstancedMesh — createInstancedMesh, setMesh, useGpuInstancing, setCullingEnabled, addInstanceColored, setInstanceParams, instanceCount, visibleCount.
  • Scene — createInstancedMesh, addNode, addCamera.
  • Graphics — loadShader, newMesh (both the 2-arg and the 3-arg per-mesh-shader form), setWind.
  • Two instanced meshes, two SSBO sub-regions. The per-draw sub-allocation (PLM-257) is what lets a scene submit several GPU-instanced prototypes in one frame — the prerequisite for multi-prototype scatter (several foliage types, or a Blender blend that bakes more than one scatter prototype via BlendResult.instantiateScatter).
  • Each instanced mesh can carry its own shader. Passing a shader to newMesh sets the prototype’s per-mesh shader, and the instanced draw now honours it (PLM-264) — so a scene can mix instanced fields with different shaders (e.g. rain and snow as separate meshes), not just one shared active shader. The shader must be an instancing shader (declares the set-2 instance SSBO and applies instanceData[SV_InstanceID].transform); a non-instancing shader ignores the per-instance transform and collapses the field to the origin — which is exactly what the plain active shader would do here if the fix regressed.
  • visibleCount is a per-mesh diagnostic — the number that mesh drew last frame; it equals its instanceCount with culling off and drops as its blades leave the frustum.

See InstancedMesh for the full instancing surface, the Grass Field example for a single enhanced-instancing field, and the Blend Scatter Demo for baked Blender scatter as instanced draws.