Three.js InstancedMesh: How to Reduce Draw Calls
Rendering thousands of 3D objects in Three.js without dropping frames
can be challenging due to CPU-to-GPU overhead known as draw calls. This
article explains what an InstancedMesh is, how it solves
performance bottlenecks by merging multiple identical objects into a
single draw call, and how to implement it to optimize your WebGL
projects.
The Problem: What are Draw Calls?
In WebGL and Three.js, every time you want to render an object on the screen, the CPU must send a command to the GPU. This command is called a draw call.
If you create 5,000 individual THREE.Mesh objects—such
as a forest of trees or a crowd of people—the CPU has to prepare and
send 5,000 separate draw calls to the GPU every frame. Even if the 3D
models are low-polygon, the communication overhead between the CPU and
GPU quickly bottlenecks your application, resulting in a low frame rate
(FPS).
The Solution: What is InstancedMesh?
InstancedMesh is a specialized class in Three.js
designed to render a large number of objects that share the exact same
geometry and material, but have different transformations (such as
position, rotation, scale, or color).
Instead of sending thousands of separate draw calls,
InstancedMesh allows you to send the geometry and material
to the GPU only once, along with an array of
transformation matrices for each instance. The GPU then renders all of
those instances in a single draw call.
This drastically reduces the CPU overhead, allowing you to render tens of thousands of objects smoothly at 60 FPS.
How to Implement InstancedMesh in Three.js
To use InstancedMesh, you define the shared geometry,
the shared material, and the maximum number of instances you want to
render.
Here is a step-by-step example of how to implement it:
import * as THREE from 'three';
// 1. Create the shared geometry and material
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 2. Define the number of instances
const count = 1000;
// 3. Create the InstancedMesh
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
// 4. Position each instance using a 4x4 Matrix
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
// Set random position, rotation, and scale on a dummy object
dummy.position.set(
Math.random() * 50 - 25,
Math.random() * 50 - 25,
Math.random() * 50 - 25
);
dummy.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
0
);
dummy.updateMatrix();
// Apply the dummy's transformation matrix to the specific instance
instancedMesh.setMatrixAt(i, dummy.matrix);
// Optional: Set a unique color for each instance
const randomColor = new THREE.Color(Math.random(), Math.random(), Math.random());
instancedMesh.setColorAt(i, randomColor);
}
// 5. Notify Three.js that the matrices and colors need an update
instancedMesh.instanceMatrix.needsUpdate = true;
if (instancedMesh.instanceColor) {
instancedMesh.instanceColor.needsUpdate = true;
}
// 6. Add the InstancedMesh to the scene
scene.add(instancedMesh);When Should You Use InstancedMesh?
InstancedMesh is highly effective, but it is not a
silver bullet for every scenario.
- Use it when: You need to render many duplicates of the same object (e.g., grass blades, particles, building debris, trees, asteroid fields).
- Do not use it when: Every object requires a completely different geometry or a unique material with different textures. In those cases, standard meshes or alternative batching techniques are required.