Three.js InstancedMesh vs Individual Meshes: Draw-Call Decision Guide
Choose InstancedMesh for many identical geometries to reduce draw calls; keep separate Mesh objects for unique materials or independent visibility.
01 Aug 2025, 01:01 UTC

Problem: too many draw calls for identical objects
You are rendering many copies of the same object—crates, trees, tiles, or blocks—and each copy is a separate THREE.Mesh. A scene with 1,000 boxes can issue roughly 1,000 draw calls. On CPU-bound devices, frame time climbs with object count even though every copy shares the same geometry and material.
Useful takeaway: if every copy shares one geometry and one material and only needs a different transform, THREE.InstancedMesh can draw them with a single draw call. If copies need unique materials, independent visibility, or separate raycasting logic, keep using individual Mesh objects.
Constraints
- Three.js r73 or later;
InstancedMeshwas introduced in r73. - All instances share one geometry and one material.
- Per-instance variation is limited to transforms unless you use
InstancedBufferGeometryor custom shader attributes. - Instance count is bounded by GPU memory and driver attribute limits.
Supported options compared
| Factor | Individual Mesh | InstancedMesh |
|---|---|---|
| Draw calls | One per object | One for all instances, typically |
| Geometry and material | Can differ per object | Shared across all instances |
| Per-instance transform | Object3D position, rotation, scale | instanceMatrix entry per instance |
| Per-instance color | Vertex colors or separate material | Requires InstancedBufferGeometry or shader attribute |
| Independent visibility | Yes, per object | No; entire instanced object is visible or hidden |
| Raycasting | Per-object intersection | Version-dependent instance-ID behavior |
| Memory overhead | Higher per object | Lower per object, matrix buffer scales with count |
Trade-offs
Choose InstancedMesh when
- You render hundreds or thousands of identical shapes.
- Instances differ only by position, rotation, or scale.
- You want to reduce CPU overhead and GPU state changes from draw calls.
Keep separate Mesh objects when
- Each object needs a different material or texture.
- You must show or hide individual copies at runtime.
- Your raycasting logic depends on per-object intersection and your Three.js version has incomplete InstancedMesh picking support.
Implementation: render many identical boxes
Run this in browser application code or the developer console. It assumes THREE, scene, and renderer already exist.
const count = 1000;
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x55aa55 });
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
scene.add(instancedMesh);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set(Math.random() * 50, 0, Math.random() * 50);
dummy.rotation.y = Math.random() * Math.PI;
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
// instanceMatrix is a Float32Array holding one 4x4 matrix per instance.
// Mark it dirty so the renderer uploads the new transforms.
instancedMesh.instanceMatrix.needsUpdate = true;
setMatrixAt writes to the instance matrix buffer without immediately uploading it. Setting instanceMatrix.needsUpdate = true tells Three.js to upload the full buffer on the next render. Batch matrix changes before setting the flag to avoid repeated full-buffer uploads.
Validation: confirm draw-call reduction
Run this check in the browser developer console after the scene is set up and at least one frame has rendered.
// Placeholder: replace `renderer` with your WebGLRenderer instance.
console.log(renderer.info.render);
Expected behavior: the instanced object contributes roughly one draw call. One thousand separate Mesh objects would contribute roughly one thousand draw calls, plus overhead for any other scene objects. Isolate the instanced mesh in a test scene or subtract a baseline count so other geometry does not skew the result.
To confirm transforms applied, modify one matrix, set needsUpdate = true, render, and verify the corresponding instance moved. Do not assume the update took effect without checking the rendered output or reading back the matrix buffer.
Limitations
- One material is shared by every instance. Per-instance material variation requires a custom shader or
InstancedBufferGeometry. - Visibility is all-or-nothing for the instanced object; you cannot hide a single instance through the standard scene graph.
- Raycasting support and instance-ID reporting vary by Three.js release. Test picking against an
InstancedMeshin your target version before relying on it. - Setting
instanceMatrix.needsUpdate = trueevery frame uploads the entire matrix buffer. Very large instance counts may require chunking or a GPU-driven approach instead.
Rollback
Adding InstancedMesh changes scene state. To remove it:
scene.remove(instancedMesh);
If geometry and material were created only for this mesh and are unused elsewhere, dispose of them to free GPU memory:
geometry.dispose();
material.dispose();
Do not dispose shared geometry or material if other objects still reference them.
Practical result check
After adding the instanced mesh, open the renderer info log and compare draw calls against a baseline scene with separate meshes. Confirm that moving an instance and setting needsUpdate = true changes its rendered position. Verify THREE.InstancedMesh exists in your build before relying on it:
if (typeof THREE !== 'undefined' && THREE.InstancedMesh) {
console.log('InstancedMesh available');
} else {
console.warn('InstancedMesh missing; check Three.js version and imports');
}
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.