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: