Merge Multiple Geometries in Three.js

In Three.js, rendering hundreds of individual 3D objects can severely degrade performance due to excessive draw calls. This article explains how to optimize your WebGL scenes by efficiently merging multiple static geometries into a single geometry using the BufferGeometryUtils module, significantly reducing draw calls and boosting your application’s frame rate.

Why Merge Geometries?

Every individual mesh in Three.js requires a separate “draw call” to the GPU. When rendering thousands of static objects—like a forest of trees or a city of buildings—the CPU becomes bottlenecked by managing these calls.

By merging separate geometries into one, the GPU can render all the combined shapes in a single draw call. This optimization is ideal for static scenes where individual objects do not need to move, rotate, or scale independently after creation.

Step 1: Import BufferGeometryUtils

Three.js provides a dedicated utility for combining geometries. To use it, you must import BufferGeometryUtils from the examples folder.

import * as THREE from 'three';
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';

Step 2: Prepare and Transform Geometries

Because merged geometries will share a single origin, you must apply any position, rotation, or scale transformations directly to the geometry objects before merging them. If you do not do this, all merged shapes will overlap at the center of the scene.

const geometries = [];

// Create multiple geometries and apply transformations
for (let i = 0; i < 1000; i++) {
    const geometry = new THREE.BoxGeometry(1, 1, 1);
    
    // Translate, rotate, or scale the geometry directly
    geometry.translate(
        Math.random() * 100 - 50,
        Math.random() * 100 - 50,
        Math.random() * 100 - 50
    );
    
    geometries.push(geometry);
}

Step 3: Merge and Create the Mesh

Once you have gathered all transformed geometries in an array, pass them to BufferGeometryUtils.mergeGeometries(). This function returns a single, optimized BufferGeometry.

// Merge all geometries into a single BufferGeometry
const mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries, true);

// Create a single material
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });

// Create the final single mesh and add it to the scene
const mergedMesh = new THREE.Mesh(mergedGeometry, material);
scene.add(mergedMesh);

// Optional: Clean up memory of original geometries
geometries.forEach(geometry => geometry.dispose());

Handling Multiple Materials

By default, merging geometries works best when they all share a single material. If your geometries require different materials, you have two options:

  1. Merge by Material: Group your geometries by material, and perform a separate merge for each group. This results in one draw call per material, which is still highly efficient.
  2. Use Material Groups: If you pass true as the second argument to mergeGeometries (as shown in the code above), Three.js creates groups within the merged geometry. You can then apply an array of materials to the final mesh. However, this will still result in multiple draw calls internally (one per material group).