Share BufferGeometry Across Multiple Meshes in Three.js
This article explains how to optimize memory usage in Three.js by
sharing a single BufferGeometry instance across thousands
of individual Mesh objects without using
InstancedMesh. You will learn the implementation process,
how memory reuse works on the GPU, and the critical performance
trade-offs regarding draw calls and CPU overhead.
The Implementation
To share a BufferGeometry among multiple meshes, you
instantiate the geometry once and pass that same reference to the
constructor of each new Mesh.
import * as THREE from 'three';
// 1. Create a single geometry and material instance
const sharedGeometry = new THREE.BoxGeometry(1, 1, 1);
const sharedMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
// 2. Instantiate thousands of meshes using the shared references
const meshCount = 5000;
for (let i = 0; i < meshCount; i++) {
const mesh = new THREE.Mesh(sharedGeometry, sharedMaterial);
// Position each mesh individually
mesh.position.set(
(Math.random() - 0.5) * 100,
(Math.random() - 0.5) * 100,
(Math.random() - 0.5) * 100
);
scene.add(mesh);
}How It Works Under the Hood
When you create a BufferGeometry, Three.js uploads its
vertex attributes (positions, normals, UVs) to the GPU’s Video RAM
(VRAM) as WebGL buffers.
By passing the sharedGeometry reference to multiple
meshes, you prevent Three.js from duplicating this data in VRAM. Each
mesh simply points to the same GPU memory address for its vertex data.
The only unique data stored for each individual mesh is its
transformation matrix (position, rotation, and scale) and its WebGL draw
state.
Advantages of This Approach
- Low VRAM Footprint: You can render complex geometries thousands of times without running out of GPU memory.
- Individual Control: Because each mesh is a distinct object in the Three.js scene graph, you can easily move, rotate, scale, or animate them independently.
- Frustum Culling: Three.js automatically performs frustum culling on each individual mesh. Meshes outside the camera’s view are not rendered, saving GPU processing time.
- Flexible Materials: While the geometry is shared, you can still assign unique materials to individual meshes if needed.
Performance Limitations
While sharing geometry saves a massive amount of GPU memory, it does not reduce the number of draw calls.
In WebGL, every individual mesh in your scene requires its own draw call. Rendering 5,000 individual meshes using this method results in 5,000 draw calls per frame. This can quickly bottleneck the CPU, leading to a drop in frame rate.
If your meshes are static and do not need to be moved independently,
merging the geometries into a single mesh using
BufferGeometryUtils.mergeGeometries() is a more performant
alternative. If they must move independently and CPU overhead becomes a
bottleneck, you should use InstancedMesh, which reduces the
overhead to a single draw call.