One mesh, one material, thousands of copies, one draw call. InstancedMeshRenderer draws from a
matrix slab you own: sixteen column-major floats per instance, the layout Mat4 and
transform.worldMatrix already use. The array is not copied — Babylon Lite reads your memory —
so moving instances is writing into the slab and calling markDirty(range).
capacity sizes the GPU buffer once. gpuCulling and lod are applied when the meshes are
created, before the scene is registered, so changing either after app.start() is refused with
IGX-0717; setCount changes how many of the slab's instances are drawn without re-uploading.
Everything below runs headless: the component holds the slab and the count with no device, which is what lets a test assert an asteroid field it cannot see.
import { Camera, InstancedMeshRenderer, Mat4, MeshAsset, Quat, createApp, createMaterialAsset, pbrMaterialDefinition,} from "@ignifx/core";const COUNT = 2000;const app = await createApp({ headless: true });app.world.createEntity("Main Camera", { position: { x: 0, y: 8, z: -30 } }).addComponent(Camera, { far: 500 });const rock = createMaterialAsset(app, pbrMaterialDefinition({ name: "rock", roughness: 0.9 }), []);const field = app.world.createEntity("Asteroids");const belt = field.addComponent(InstancedMeshRenderer, { mesh: MeshAsset.box(app, { size: 0.6 }), materials: [rock], capacity: 20_000, gpuCulling: true, // Past 60 m each instance draws the cheaper mesh instead; `band` is the hysteresis either side. lod: { mesh: MeshAsset.sphere(app, { diameter: 0.6, segments: 4 }), distance: 60, band: 8 },});// Sixteen floats per instance. `Mat4.composeToRef` writes into one scratch matrix, so a field of// any size costs no allocation beyond the slab itself.const slab = new Float32Array(COUNT * 16);const scratch = Mat4.identity();const spin = Quat.identity();for (let index = 0; index < COUNT; index += 1) { const angle = index * 0.137; const radius = 12 + (index % 40) * 0.8; Quat.fromEulerDegreesToRef(0, angle * 57.3, 0, spin); Mat4.composeToRef( { x: Math.cos(angle) * radius, y: (index % 7) * 0.3 - 1, z: Math.sin(angle) * radius }, spin, { x: 1, y: 1, z: 1 }, scratch, ); slab.set(scratch.elements, index * 16);}belt.setMatrices(slab, COUNT);await app.start();app.step(1 / 60);app.log.info("instances:", belt.count, "of", belt.capacity);// Moving a handful: write into the slab, then say which range moved. Nothing is re-uploaded whole.Mat4.composeToRef({ x: 0, y: 4, z: 0 }, Quat.identity(), { x: 2, y: 2, z: 2 }, scratch);slab.set(scratch.elements, 0);belt.markDirty({ start: 0, count: 1 });app.step(1 / 60);// A quality setting draws fewer of the same instances; the slab is untouched.belt.setCount(500);app.step(1 / 60);app.log.info("after the quality drop:", belt.count);app.dispose();Source: examples/recipes/instance-many-meshes/main.ts