InstancedMesh Changed Everything — 5 FPS to 60 FPS

Rendering thousands of identical objects is the most common Three.js performance trap. One API call — InstancedMesh — solves it completely. Benchmarks and code included.

The Problem: Draw Calls

When you create 10,000 Mesh objects in Three.js and add them to the scene, each one generates a separate draw call when rendered. A draw call is a CPU command sent to the GPU that says: bind this buffer, apply this transform matrix, draw this geometry.

The GPU can process millions of polygons per second — but it can only process a few thousand draw call state changes per second. The CPU bottleneck kills your frame rate long before you run out of geometric complexity.

N Objects Method Draw Calls FPS
1,000 Mesh ×1000 1,000 55
10,000 Mesh ×10000 10,000 12
100,000 Mesh ×100000 100,000 1
100,000 InstancedMesh 1 60

The Solution: InstancedMesh

InstancedMesh packs all instance transforms into a single GPU buffer and renders everything in one draw call. Each instance can have an independent position, rotation, scale and colour — but they all share one geometry and one material.

// Before: 10,000 draw calls
for (let i = 0; i < 10000; i++) {
  const mesh = new THREE.Mesh(geometry, material);
  mesh.position.set(Math.random() * 100, 0, Math.random() * 100);
  scene.add(mesh);
}

// After: 1 draw call
const instancedMesh = new THREE.InstancedMesh(geometry, material, 10000);
const dummy = new THREE.Object3D();

for (let i = 0; i < 10000; i++) {
  dummy.position.set(Math.random() * 100, 0, Math.random() * 100);
  dummy.updateMatrix();
  instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;
scene.add(instancedMesh);

Updating Instance Transforms Each Frame

For animated instances, update the matrix each frame and flag it dirty:

function animate() {
  for (let i = 0; i < COUNT; i++) {
    dummy.position.x = positions[i].x + Math.sin(time + i) * 0.1;
    dummy.updateMatrix();
    instancedMesh.setMatrixAt(i, dummy.matrix);
  }
  instancedMesh.instanceMatrix.needsUpdate = true; // crucial!
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

Gotcha: Forgetting needsUpdate = true means the GPU never sees the new matrices. Instances will appear frozen even though your JavaScript loop runs. This is the single most common bug with InstancedMesh.

Per-Instance Colour

const color = new THREE.Color();
instancedMesh.instanceColor = new THREE.InstancedBufferAttribute(
  new Float32Array(COUNT * 3), 3
);

for (let i = 0; i < COUNT; i++) {
  color.setHSL(i / COUNT, 1.0, 0.5);
  instancedMesh.setColorAt(i, color);
}
instancedMesh.instanceColor.needsUpdate = true;

When NOT to Use InstancedMesh