Rendering Thousands of Objects Efficiently with Three.js InstancedMesh
Learn how to replace thousands of individual Three.js meshes with a single InstancedMesh to cut draw calls and boost frame rate.
02 Sept 2025, 04:27 UTC

Desired outcome
Render many copies of the same geometry (e.g., trees, particles, or blocks) with a single draw call to improve frame rate and reduce CPU overhead.
Necessary prerequisites
- A basic Three.js scene set up with a
THREE.Scene,THREE.PerspectiveCamera, and aTHREE.WebGLRenderer. - The geometry and material you want to instance (e.g.,
THREE.BoxGeometryandTHREE.MeshStandardMaterial). - Knowledge of the maximum number of instances you will need at any time.
Focused procedure
- Create the InstancedMesh
// geometry and material shared by all instances const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); // maximum number of instances you plan to use const MAX_INSTANCES = 5000; const instancedMesh = new THREE.InstancedMesh(geometry, material, MAX_INSTANCES); scene.add(instancedMesh); - Prepare a buffer for instance matrices
Three.js provides a
Float32Arrayaccessible viainstancedMesh.instanceMatrix. Each instance uses 16 consecutive floats (a 4×4 matrix). - Set the transform for each instance
// Helper to set position, rotation (Euler), and scale function setInstanceMatrix(index, position, rotation, scale) { const matrix = new THREE.Matrix4(); matrix.compose(position, new THREE.Quaternion().setFromEuler(rotation), scale); instancedMesh.instanceMatrix.set(matrix.elements, index * 16); } // Example: place instances in a grid const spacing = 2; for (let i = 0; i < MAX_INSTANCES; i++) { const x = (i % 100) * spacing - 100; const y = Math.floor(i / 100) * spacing - 100; const z = 0; setInstanceMatrix(i, new THREE.Vector3(x, y, z), new THREE.Euler(0, 0, 0), new THREE.Vector3(1, 1, 1)); } // Mark the buffer as needing an update instancedMesh.instanceMatrix.needsUpdate = true; - Enable dynamic updates (if you will change matrices frequently)
instancedMesh.instanceMatrixUsage = THREE.DynamicDrawUsage;After each batch of changes, remember to set
instancedMesh.instanceMatrix.needsUpdate = true;. - Render as usual
function animate() { requestAnimationFrame(animate); // optionally update some instance matrices here renderer.render(scene, camera); } animate();
Expected checks
- Visual verification: all instances appear at the positions you set.
- Performance check: open the browser console and inspect
renderer.info.render.callsbefore and after adding the InstancedMesh. The call count should drop dramatically (often to 1 for the instanced draw call). - Update verification: modify one instance’s matrix (e.g., move it upward) and render a single frame; you should see that instance move while others stay static.
Relevant recovery options (rollback)
Creating an InstancedMesh adds an object to the scene. To revert:
- Remove it from the scene:
scene.remove(instancedMesh); - Dispose of its geometry and material if they are not used elsewhere:
geometry.dispose(); material.dispose(); - Set the reference to
nullto allow garbage collection.
Limitations and practical verification
- Changing the maximum instance count after creation requires disposing the original InstancedMesh and creating a new one with the desired count.
- Each instance still consumes GPU memory for its matrix; profile memory usage when scaling beyond a few hundred thousand instances.
- All instances share the same material; if you need per‑instance material variations, consider using
InstancedMeshwith a custom shader that reads per‑instance attributes (e.g., color) from an additional buffer.
To verify that memory growth stays within bounds, use the browser’s developer tools → Memory panel and take a heap snapshot before and after creating a large InstancedMesh; look for a steady increase in WebGL buffer size rather than unbounded growth.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.