Reduce Three.js Draw Calls in renderer.info.render.calls
In Three.js, rendering performance is heavily tied to the number of
draw calls sent to the GPU. This article explains how to measure these
draw calls using the renderer.info.render.calls property
and outlines actionable techniques—such as geometry merging, instanced
rendering, and material sharing—to significantly reduce them and boost
your application’s frame rate.
Measuring Draw Calls with renderer.info
Three.js provides a built-in debugging tool via the
WebGLRenderer.info object. This object monitors the
performance of the renderer in real-time.
To measure the active draw calls in your scene, access the
renderer.info.render.calls property inside your animation
loop:
function animate() {
requestAnimationFrame(animate);
// Render the scene
renderer.render(scene, camera);
// Log the number of draw calls
console.log("Draw Calls:", renderer.info.render.calls);
console.log("Triangles:", renderer.info.render.triangles);
}Each draw call represents a separate instruction sent by the CPU to the GPU to render a group of vertices. High draw call counts (typically over a few hundred or thousand, depending on the device) cause CPU bottlenecks, leading to drops in frame rates.
How to Reduce Draw Calls
To optimize your scene, you must minimize the number of individual render commands. Below are the most effective strategies to achieve this.
1. Use InstancedMesh for Duplicate Objects
If your scene contains many identical geometries that share the same
material (such as trees, particles, or building bricks), use
THREE.InstancedMesh. This allows you to render thousands of
objects in a single draw call while still positioning, scaling, and
rotating them individually.
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const count = 1000;
// Create an InstancedMesh
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set(Math.random() * 10, Math.random() * 10, Math.random() * 10);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
scene.add(instancedMesh);
// Result: 1 draw call instead of 10002. Merge Geometries for Static Objects
If you have multiple unique static meshes that do not need to move
independently, merge their geometries into a single mesh. You can use
BufferGeometryUtils.mergeGeometries from the Three.js
addons.
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
const geometries = [];
// Create multiple geometries
for (let i = 0; i < 100; i++) {
const geom = new THREE.BoxGeometry(1, 1, 1);
geom.translate(Math.random() * 10, 0, Math.random() * 10); // Position manually
geometries.push(geom);
}
// Merge into a single geometry
const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mesh);
// Result: 1 draw call instead of 1003. Share Materials and Use Texture Atlases
Three.js groups objects by material during the rendering process. If two meshes have the same geometry but different materials, they will require separate draw calls.
- Share Material Instances: Avoid creating a
new THREE.MeshStandardMaterial()for every object. Instead, instantiate the material once and assign it to multiple meshes. - Use Texture Atlases: If your meshes require different textures, combine those textures into a single large image (a texture atlas). You can then assign different UV coordinates to your geometries to map the correct portion of the texture to each object, allowing them to share a single material.
4. Enable Frustum Culling
By default, Three.js enables frustum culling
(mesh.frustumCulled = true). This automatically skips
rendering objects that are outside the camera’s field of view.
- Ensure your geometries have accurate bounding spheres by calling
geometry.computeBoundingSphere()if you are modifying vertices manually. - If you have large groups of off-screen objects that are still
triggering draw calls, manually toggle their visibility using
mesh.visible = false.
5. Combine Multi-Material Meshes
Using an array of materials on a single mesh (e.g., a cube with six different materials) forces Three.js to split the mesh into subgroups, resulting in one draw call per material. Whenever possible, replace multi-material meshes with a single material that utilizes a texture map or vertex colors.