Switch from Indexed to Non-Indexed in Three.js
This article explains how to dynamically convert an indexed
BufferGeometry to a non-indexed one in Three.js by
disposing of and removing its index buffer. You will learn how to
restructure the vertex attributes in-place, release the GPU memory
associated with the index, and notify the WebGL renderer to switch from
indexed drawing to non-indexed drawing.
The Challenge of Dynamic Conversion
In Three.js, an indexed geometry uses an index buffer
attribute to define the order in which vertices are connected to form
faces. A non-indexed geometry, however, reads vertices sequentially.
Simply setting geometry.index = null is not enough to
switch rendering modes. If you remove the index without updating the
other vertex attributes (like position,
normal, and uv), the geometry will render
incorrectly because the vertices are still packed for indexed rendering.
To successfully switch dynamically, you must duplicate the shared
vertices and safely dispose of the index buffer from GPU memory.
Step-by-Step Implementation
To perform this switch dynamically on an existing geometry instance (to preserve material assignments and scene graph references), follow these steps:
1. Generate Non-Indexed Attributes
First, use the built-in toNonIndexed() helper method.
This creates a temporary geometry with duplicated, sequential vertex
attributes.
const tempGeometry = geometry.toNonIndexed();2. Dispose of the Index Buffer
To free up WebGL resources and prevent memory leaks, you must
explicitly dispose of the existing index attribute. Setting the index to
null tells the Three.js WebGLRenderer to stop
using drawElements and switch to
drawArrays.
if (geometry.index !== null) {
// Release WebGL buffer resource
geometry.index.dispose();
// Remove the index from the geometry
geometry.setIndex(null);
}3. Replace the Vertex Attributes
Copy the expanded, sequential attributes from the temporary geometry to your original geometry. This ensures the vertices are now aligned for sequential drawing.
// Replace position attribute
geometry.setAttribute('position', tempGeometry.getAttribute('position'));
// Replace normal attribute if it exists
if (tempGeometry.hasAttribute('normal')) {
geometry.setAttribute('normal', tempGeometry.getAttribute('normal'));
}
// Replace UV attribute if it exists
if (tempGeometry.hasAttribute('uv')) {
geometry.setAttribute('uv', tempGeometry.getAttribute('uv'));
}4. Clean Up and Flag Updates
Dispose of the temporary geometry to avoid memory leaks, and flag the original geometry’s attributes as needing an update so the GPU receives the new data.
// Dispose of the temporary geometry container
tempGeometry.dispose();
// Force Three.js to upload the new attributes to the GPU
geometry.attributes.position.needsUpdate = true;
if (geometry.attributes.normal) geometry.attributes.normal.needsUpdate = true;
if (geometry.attributes.uv) geometry.attributes.uv.needsUpdate = true;Summary of Renderer Behavior
Once geometry.index is set to null, the
WebGLRenderer automatically detects the change during the
next render frame. It will rebind the updated vertex attributes and
switch its internal draw call from gl.drawElements() (used
for indexed geometries) to gl.drawArrays() (used for
non-indexed geometries), completing the dynamic transition.