Static Batching in Three.js with mergeGeometries
This article explains the concept of static batching in 3D web
development and provides a step-by-step guide on how to implement it in
Three.js using the mergeGeometries utility. You will learn
how combining multiple individual geometries into a single mesh reduces
draw calls, significantly boosting rendering performance for static
scenes.
What is Static Batching?
In WebGL and Three.js, every individual mesh added to a scene typically triggers a “draw call”—a command sent by the CPU to the GPU telling it to render an object. When a scene contains thousands of independent objects, the CPU becomes bottlenecked by the sheer volume of these draw calls, leading to low frame rates.
Static batching is a performance optimization technique where multiple separate, non-moving (static) geometries that share the same material are combined into a single, large geometry. By merging them, the GPU can render hundreds or thousands of objects in a single draw call, drastically improving rendering performance.
Achieving Static Batching with mergeGeometries
Three.js provides an official utility called
BufferGeometryUtils which contains the
mergeGeometries function (historically known as
mergeBufferGeometries). This function takes an array of
individual BufferGeometry instances and outputs a single,
consolidated BufferGeometry.
Step 1: Import the Utility
First, import the necessary modules. The
BufferGeometryUtils module is located in the addons folder
of the Three.js package.
import * as THREE from 'three';
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';Step 2: Prepare and Position Geometries
Because the merged geometries will live within a single coordinate
system, you cannot use individual THREE.Mesh position,
rotation, or scale properties to arrange them. Instead, you must apply
these transformations directly to the geometry data before merging.
const geometries = [];
for (let i = 0; i < 1000; i++) {
const geometry = new THREE.BoxGeometry(1, 1, 1);
// Generate random positions
const x = Math.random() * 100 - 50;
const y = Math.random() * 100 - 50;
const z = Math.random() * 100 - 50;
// Apply transformation directly to the geometry vertices
geometry.translate(x, y, z);
geometries.push(geometry);
}Step 3: Merge and Render
Once all geometries are positioned in the array, pass them to
mergeGeometries. Finally, create a single Mesh
using the merged geometry and a single material, then add it to your
scene.
// Merge all geometries into one
const mergedGeometry = mergeGeometries(geometries);
// Create a single material shared by all objects
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
// Create one single mesh representing all 1,000 boxes
const staticBatchMesh = new THREE.Mesh(mergedGeometry, material);
scene.add(staticBatchMesh);Crucial Considerations
While static batching is highly effective, it comes with specific trade-offs:
- No Individual Movement: Because the merged geometry
exists as a single mesh, you cannot move, rotate, or scale individual
sub-objects at runtime. If objects need to move independently, dynamic
instancing (
InstancedMesh) is a better alternative. - Shared Materials: To merge geometries successfully,
they should share the same material. If you pass geometries with
mismatched attributes (like differing vertex colors or UV channels),
mergeGeometriesmay fail or require additional configuration. - Memory Footprint: Merging geometries duplicates
vertex data into a new, unique memory buffer. Always dispose of the
original source geometries using
geometry.dispose()after merging to prevent memory leaks.