Babylon.js Thin Instances: Rendering Thousands of Identical Meshes in One Draw Call
Thin instances let Babylon.js render thousands of identical meshes in a single draw call by uploading per-instance matrices to the GPU. Learn the API, trade-offs, and how to verify culling and draw calls.
15 Dec 2025, 00:18 UTC

When 5,000 Rocks Become 5,000 Problems
You need a rocky terrain or a field of grass. The obvious approach is to create a mesh for every rock and add it to the scene. In Babylon.js, each of those meshes becomes a node in the scene graph, and every visible node issues its own draw call. On a crowded scene, the CPU cost of traversing thousands of nodes and the GPU cost of submitting thousands of draw calls can erode your frame budget before the scene even loads. Thin instances offer a different path: keep one source mesh, upload a buffer of per-instance transformation matrices to the GPU, and render every copy in a single draw call.
Thesis: if your scene needs thousands of identical, mostly static objects, thin instances are usually the most pragmatic choice—provided you accept their limitations up front.
What Thin Instances Actually Are
Thin instances are not nodes in the scene graph. They are managed directly on a single source mesh. The source mesh owns the geometry and material, and the engine reads a separate buffer of matrices to place each copy. Because there is only one mesh object, there is no per-instance scene-graph overhead, and the renderer can batch the copies into one draw call.
This makes thin instances ideal for static crowds: grass, rocks, asteroids, forest trees, or architectural details. It also makes them a poor fit for objects that need individual animation, picking, or visibility toggling through the normal scene graph, because those operations expect real nodes.
Uploading a Static Instance Buffer
For a fixed field of objects, the most efficient path is to build a Float32Array of matrices and upload it once. The example below creates a source box mesh and scatters 5,000 copies across a plane. Run this in your scene setup code or in the browser console after the scene is ready.
// Requires a WebGL2 or WebGPU engine context.
const sourceMesh = BABYLON.MeshBuilder.CreateBox("rockSource", { size: 0.4 }, scene);
const instanceCount = 5000;
const matricesData = new Float32Array(16 * instanceCount);
for (let i = 0; i < instanceCount; i++) {
const x = (Math.random() - 0.5) * 80;
const z = (Math.random() - 0.5) * 80;
const matrix = BABYLON.Matrix.Translation(x, 0, z);
matrix.toArray(matricesData, i * 16);
}
// Upload the buffer once for a static field.
sourceMesh.thinInstanceSetBuffer("matrix", matricesData, 16, true);
// Update the bounding volume so frustum culling and shadows work correctly.
sourceMesh.thinInstanceRefreshBoundingInfo();
The thinInstanceSetBuffer call takes the buffer kind ("matrix"), the raw data, the stride (16 floats per 4x4 matrix), and a flag indicating whether the buffer is static. For dynamic streams—say, positions driven by a physics simulation—you can instead call thinInstanceAdd(matrix) incrementally, or rewrite the buffer and refresh the bounding info again.
Trade-Offs You Must Plan For
Thin instances share one geometry and one material across every copy. That means you cannot assign a different material or color to individual instances through the standard API. If you need visual variety, you must extend the shader with custom vertex attributes or use a texture atlas and encode variation into the instance matrices or additional buffers.
Picking is another constraint. Because thin instances bypass the normal scene-graph picking pipeline, clicking or ray-casting against a specific instance requires custom logic. You can read the instance matrices from the buffer, transform ray queries into local space, or maintain a separate spatial index.
Bounding info is easy to forget. If you change the instance buffer and do not call thinInstanceRefreshBoundingInfo(), the mesh may disappear when it enters the view frustum, or shadows and ray casts may miss it entirely. Treat that call as mandatory after any buffer mutation.
Finally, thin instances require a WebGL2 or WebGPU context. They are unavailable in legacy WebGL1 fallback mode. If your target audience includes very old browsers, you need a fallback path or a feature check.
Verifying Draw Calls and Culling in the Inspector
The Babylon.js Inspector is the fastest way to confirm that thin instances are doing what you expect. Open the Inspector, locate the statistics pane, and compare engine.drawCalls between your thin-instance scene and a clone-based version using the same object count. A correctly configured thin-instance mesh should collapse thousands of copies into a single draw call for that source mesh.
Next, toggle the source mesh visibility in the Inspector and confirm that the bounding box displayed around the thin instances updates after you call thinInstanceRefreshBoundingInfo(). If the box does not match the scattered positions, culling and shadow calculations will be wrong.
Because API details and WebGPU support can shift between Babylon.js releases, pin your version in package.json and re-run these checks after upgrades. Run the same scene on both a WebGL2 and a WebGPU engine to confirm your target browsers support the rendering path before shipping.
Choosing the Right Tool
Use thin instances when you have thousands of identical, static objects and you are willing to manage a single shared material. Use regular instances created with createInstance when you need per-instance visibility, picking, or animation through the scene graph. Use GPU particles when the objects are numerous, dynamic, and do not require precise collision geometry.
Before committing, build a small test scene with roughly 5,000 instances, measure engine.drawCalls in the Inspector, and verify that your bounding-info refresh logic keeps culling accurate. That five-minute check will tell you whether thin instances solve your performance problem or simply move it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.