Babylon.js Thin Instances vs InstancedMesh: Picking the Right Repetition Strategy
Babylon.js offers clones, InstancedMesh, and thin instances for repeated geometry. Here's how they differ on picking, culling, memory, and when each one is the right call.
24 Mar 2026, 08:28 UTC

You have a scene with 50,000 grass tufts, 12,000 floor tiles, or a starfield of debris. Your frame rate is dying and the inspector shows thousands of scene nodes. The fix in Babylon.js is almost always one of two APIs — InstancedMesh or thin instances — but they solve different problems, and picking the wrong one either wastes memory or quietly breaks features you needed, like picking.
The short version: use InstancedMesh when instances need to behave like real objects (picking, per-instance culling, individual transforms). Use thin instances when you need sheer count and the copies are static decoration. The rest of this post explains why, with a worked example.
Three ways to repeat a mesh
Babylon.js gives you three repetition mechanisms, in decreasing order of per-copy cost:
- Clones — full node copies. Independent materials, hierarchies, everything. Also the most expensive: each clone is a real scene node with its own transform, culling entry, and draw overhead.
- InstancedMesh — GPU instancing. All instances share the source mesh's geometry and material, but each instance is still a scene node. You get per-instance picking, frustum culling, and easy per-instance transforms.
- Thin instances — a raw matrix buffer rendered with minimal overhead. No scene nodes at all. This is the cheapest option and the only one that scales comfortably into the hundreds of thousands.
You can confirm the node-count difference yourself: after creating instances, check scene.meshes.length. InstancedMesh instances appear there; thin instances do not.
What thin instances actually are
A thin instance is just 16 floats — one 4x4 transformation matrix — appended to a buffer on the source mesh. There is no node, no name, no bounding box per instance. The GPU draws the source geometry once per matrix in a single instanced draw call.
You create them like this (run in your scene setup code, after the source mesh exists; no special permissions needed):
// One at a time
const matrix = BABYLON.Matrix.Translation(x, y, z);
mesh.thinInstanceAdd(matrix);
// Or bulk-load from a flat Float32Array (16 floats per instance)
const matrices = new Float32Array(count * 16);
// ... fill matrices ...
mesh.thinInstanceSetBuffer("matrix", matrices, 16);The bulk form matters: building the buffer once and uploading it is far cheaper than thousands of individual calls when you're populating a large scene.
The trade-offs that actually bite
Thin instances are cheap because they give things up. Know these limits before committing:
- Picking: thin instances are not individually pickable by default. A
scene.pickray test will not resolve to a specific thin instance the way it does for InstancedMesh. If users need to click individual objects, thin instances are the wrong tool (or you need extra work, such as a separate invisible picking proxy). - Culling: frustum culling is effectively all-or-nothing against the source mesh's bounding info. One giant thin-instance batch covering your whole map means the entire batch renders even when 95% of it is behind the camera. The standard mitigation is to split instances into spatial chunks (grid cells, octree leaves), each with its own thin-instance buffer and bounding data.
- Per-instance variation: all thin instances share the source material. Variation is possible through additional thin-instance buffers — for example a
colorbuffer when the material supports instanced color — but it takes more manual setup than per-instance properties on InstancedMesh. - Memory: the matrix buffer is a flat Float32Array at 64 bytes per instance. 500,000 instances is ~32 MB of GPU buffer before you add color or custom attributes. Cheap per copy, not free.
Worked example: scattered rocks
Say you're scattering 80,000 rocks across a terrain. They never move and the player never clicks them — a textbook thin-instance case:
const rock = BABYLON.MeshBuilder.CreateIcoSphere("rock", { radius: 1 }, scene);
rock.material = rockMaterial;
const COUNT = 80000;
const data = new Float32Array(COUNT * 16);
const m = BABYLON.Matrix.Identity();
for (let i = 0; i < COUNT; i++) {
const pos = sampleTerrainPosition(i); // your placement logic
const rot = BABYLON.Quaternion.FromEulerAngles(0, Math.random() * Math.PI * 2, 0);
const scale = 0.5 + Math.random();
BABYLON.Matrix.ComposeToRef(
new BABYLON.Vector3(scale, scale, scale), rot, pos, m
);
m.copyToArray(data, i * 16);
}
rock.thinInstanceSetBuffer("matrix", data, 16, true); // static bufferThe final true marks the buffer static, which tells Babylon it won't be rewritten every frame. To verify it worked, open the Babylon inspector (debug layer) and check the draw call count — you should see one instanced draw for the rocks — and confirm scene.meshes.length did not grow by 80,000.
If those rocks instead needed to be clickable — say, a strategy game where you select individual units — you'd use rock.createInstance("rock" + i) instead, accept the node overhead, and likely cap the count in the low thousands.
Updating thin instances without losing the win
Thin instance buffers can be updated in place with thinInstanceSetBuffer at the same size, which suits dynamic scenes. But rewriting the full buffer every frame can erase the savings — you're paying an upload cost each frame on top of everything else. Prefer partial updates, update only when data actually changes, or drive motion in the shader (e.g., wind sway computed from instance index and time) so the buffer stays static.
A decision rule you can apply today
- Need independent materials or child hierarchies per copy? Clone, and keep counts low.
- Need picking, per-instance culling, or frequent individual transform changes? InstancedMesh.
- Static decoration where count dominates — foliage, tiles, rubble, distant props? Thin instances, chunked spatially if they span a large area.
One caveat before you ship: thin instance APIs and the set of supported per-instance buffer attributes have evolved across Babylon.js releases. Check the documentation and release notes for your installed version to confirm thinInstanceSetBuffer signatures and material support for instanced attributes. Then validate with the inspector: compare draw calls and frame time across clone, InstancedMesh, and thin-instance versions of the same scene. The numbers will make the decision for you.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.