Skip to content

Geometry System

Purpose and Scope

The Geometry System provides the core infrastructure for representing 3D mesh, line, and point geometry in Three.js. It defines how vertex data (positions, normals, UVs, colors, custom attributes) is stored in GPU-friendly typed arrays and how this data is organized for efficient rendering. The system is designed around BufferGeometry, which replaced the older Geometry class to reduce CPU-to-GPU transfer overhead.

This page covers vertex data storage, indexing, multi-material support via groups, morph targets, bounding volume computation, and geometry manipulation operations. For information about procedural geometry generation, see Procedural Geometries. For details about how geometries are loaded from external files, see GLTF Import & Export and Additional Format Loaders.

Core Architecture

The geometry system centers on BufferGeometry as the primary container for all geometric data. Vertex attributes are stored as BufferAttribute instances, which wrap typed arrays for efficient GPU upload.

Diagram: Geometry System Architecture

SVG
100%

BufferGeometry Class

BufferGeometry is the base class for all geometry in Three.js. It extends EventDispatcher to support disposal events and provides a structured way to define mesh, line, or point geometry.

Key Properties

PropertyTypeDescription
idnumberAuto-incrementing unique identifier
uuidstringGlobally unique identifier
namestringOptional name for the geometry
attributesObjectDictionary mapping attribute names to BufferAttribute instances
indexBufferAttribute | nullOptional index buffer for indexed rendering
groupsArray<Object>Defines material groups for multi-material rendering
morphAttributesObjectMorph target data for vertex animation
boundingBoxBox3 | nullAxis-aligned bounding box (computed via computeBoundingBox())
boundingSphereSphere | nullBounding sphere (computed via computeBoundingSphere())
drawRangeObject{start, count} to render a subset of the geometry
userDataObjectApplication-specific custom data

Basic Usage Example

const geometry = new THREE.BufferGeometry();

// Define a square using 6 vertices (2 triangles)
const vertices = new Float32Array([
    -1.0, -1.0,  1.0,  // v0
     1.0, -1.0,  1.0,  // v1
     1.0,  1.0,  1.0,  // v2
     1.0,  1.0,  1.0,  // v3
    -1.0,  1.0,  1.0,  // v4
    -1.0, -1.0,  1.0   // v5
]);

geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

Vertex Data Storage with BufferAttribute

Vertex data is stored in BufferAttribute instances, which wrap typed arrays. Each attribute has an itemSize (number of components per vertex) and a normalized flag.

Diagram: Attribute Storage Structure

SVG
100%

Common Attributes

Attribute NameItem SizeDescription
position3Vertex positions (x, y, z)
normal3Vertex normals for lighting
uv2Texture coordinates (u, v)
uv1, uv2, uv32Additional UV sets for multi-texturing
color3 or 4Vertex colors (RGB or RGBA)
tangent4Tangent vectors (x, y, z, w) for normal mapping

Attribute Management Methods

// Add or update an attribute
geometry.setAttribute(name, attribute);

// Retrieve an attribute
const position = geometry.getAttribute('position');

// Remove an attribute
geometry.deleteAttribute(name);

// Check if attribute exists
if (geometry.hasAttribute('normal')) { /* ... */ }

Typed Array Types

Different typed arrays are used based on data requirements:

Typed ArrayUse Case
Float32ArrayPositions, normals, UVs, colors (most common)
Uint16ArrayIndices (up to 65,535 vertices)
Uint32ArrayIndices (more than 65,535 vertices)
Int16ArrayCompressed integer attributes
Uint8ArrayColor data (0-255 range)

Index Buffers

Index buffers enable vertex reuse, reducing memory usage and improving cache coherency. Instead of duplicating vertices, indices reference positions in the vertex arrays.

Diagram: Indexed vs Non-Indexed Geometry

SVG
100%

Setting an Index Buffer

// Using an array (automatically chooses Uint16 or Uint32)
geometry.setIndex([0, 1, 2, 2, 3, 0]);

// Using a BufferAttribute directly
const indices = new Uint16Array([0, 1, 2, 2, 3, 0]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

Converting Between Indexed and Non-Indexed

The toNonIndexed() method expands indexed geometry into non-indexed form by duplicating vertices:

const nonIndexed = geometry.toNonIndexed();

This is useful when you need to modify per-triangle data or when working with systems that don't support indexing.

Material Groups

Groups allow a single geometry to be rendered with multiple materials. Each group defines a range of indices or vertices to render with a specific material index.

Diagram: Multi-Material Rendering with Groups

SVG
100%

Group Management

// Add a group for indices 0-299 using material index 0
geometry.addGroup(0, 300, 0);

// Add a group for indices 300-499 using material index 1
geometry.addGroup(300, 200, 1);

// Clear all groups
geometry.clearGroups();

// Access groups array
console.log(geometry.groups);
// Output: [{start: 0, count: 300, materialIndex: 0}, ...]

Important: Every vertex/index must belong to exactly one group. Groups must not overlap or leave gaps.

Draw Range

The drawRange property allows rendering a subset of the geometry without creating new geometry objects:

// Render only the first 1000 vertices/indices
geometry.setDrawRange(0, 1000);

// Render everything (default)
geometry.setDrawRange(0, Infinity);

Morph Targets

Morph targets enable vertex animation by interpolating between multiple attribute sets. They are commonly used for facial animation and organic deformations.

Diagram: Morph Target Structure

SVG
100%

Morph Target Modes

  • Relative Mode (morphTargetsRelative: true): Morph attributes store offsets from the base position
  • Absolute Mode (morphTargetsRelative: false): Morph attributes store absolute positions
geometry.morphTargetsRelative = true; // Use relative offsets

// Morph attributes are stored in the morphAttributes dictionary
geometry.morphAttributes.position = [
    new Float32BufferAttribute([/* target 0 positions */], 3),
    new Float32BufferAttribute([/* target 1 positions */], 3)
];

Note: Once the geometry has been rendered, morph attribute data cannot be changed. You must call dispose() and create a new geometry.

Bounding Volumes

Bounding volumes are essential for view frustum culling, raycasting, and physics. They must be computed explicitly via computeBoundingBox() or computeBoundingSphere().

Bounding Box (Box3)

An axis-aligned bounding box (AABB) defined by min and max corners:

geometry.computeBoundingBox();

console.log(geometry.boundingBox);
// Box3 { min: Vector3(-1, -1, -1), max: Vector3(1, 1, 1) }

// Access corners
const min = geometry.boundingBox.min;
const max = geometry.boundingBox.max;

Bounding Sphere

A sphere defined by a center point and radius, optimal for distance-based culling:

geometry.computeBoundingSphere();

console.log(geometry.boundingSphere);
// Sphere { center: Vector3(0, 0, 0), radius: 1.732 }

The bounding sphere algorithm first computes the center from the bounding box, then finds the maximum distance from the center to any vertex.

Morph Target Consideration

Both bounding volume methods account for morph targets when present:

// Expands bounding volumes to include all morph target positions
geometry.computeBoundingBox();    // Considers morphAttributes.position
geometry.computeBoundingSphere(); // Considers morphAttributes.position

Geometry Operations

BufferGeometry provides methods for transforming and manipulating geometry data.

Transformation Methods

MethodDescriptionTypical Use Case
applyMatrix4(matrix)Applies 4×4 transformation matrix to position, normal, tangentBaking object transforms into geometry
applyQuaternion(q)Applies rotation via quaternionRotating geometry in place
rotateX(angle)Rotates around X-axisOne-time geometry adjustment
rotateY(angle)Rotates around Y-axisOne-time geometry adjustment
rotateZ(angle)Rotates around Z-axisOne-time geometry adjustment
translate(x, y, z)Translates geometryMoving geometry origin
scale(x, y, z)Scales geometryResizing geometry
center()Centers geometry at originCentering imported models
lookAt(vector)Orients geometry toward a pointAligning geometry

Important: These are typically one-time operations for geometry preprocessing, not for runtime animation. For runtime transforms, use Object3D.position, Object3D.rotation, and Object3D.scale instead.

Normal Computation

The computeVertexNormals() method calculates smooth normals by averaging face normals for shared vertices:

geometry.computeVertexNormals();

// For indexed geometry: averages normals of all faces sharing a vertex
// For non-indexed geometry: each triangle gets its own flat normal

After computing normals, normalizeNormals() ensures all normal vectors have unit length:

geometry.normalizeNormals();

Tangent Computation

Tangents are required for normal mapping. The computeTangents() method generates tangent vectors using the Terathon algorithm:

geometry.computeTangents();
// Requires: index, position, normal, and uv attributes
// Produces: tangent attribute (4 components: x, y, z, w)

The fourth component (w) stores handedness for bitangent calculation in shaders.

Note: For better results with normal maps, use BufferGeometryUtils.computeMikkTSpaceTangents() instead, which implements the MikkTSpace algorithm used by most 3D content creation tools.

Point Cloud Helpers

The setFromPoints() method creates or updates geometry from an array of Vector2 or Vector3 points:

const points = [
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(1, 0, 0),
    new THREE.Vector3(1, 1, 0)
];

geometry.setFromPoints(points);
// Creates a position attribute with 3 vertices

Indirect Drawing (WebGPU)

For use with WebGPURenderer, BufferGeometry supports indirect draw calls where draw parameters are stored in a GPU buffer generated by compute shaders:

// Storage buffer with indirect draw parameters
const indirectBuffer = new THREE.StorageBufferAttribute(data, 4);
geometry.setIndirect(indirectBuffer, 0);

// Multiple draw calls with different offsets
geometry.setIndirect(indirectBuffer, [0, 16, 32]);

This enables techniques like GPU-driven rendering where the GPU determines what to render without CPU involvement.

Serialization Format

BufferGeometry implements toJSON() for serialization and can be loaded via BufferGeometryLoader.

Diagram: Serialization Structure

SVG
100%

Example JSON Structure

{
  "metadata": {
    "version": 4.7,
    "type": "BufferGeometry"
  },
  "uuid": "...",
  "type": "BufferGeometry",
  "data": {
    "attributes": {
      "position": {
        "itemSize": 3,
        "type": "Float32Array",
        "array": [0, 0, 0, 1, 0, 0, ...],
        "normalized": false
      },
      "normal": { /* ... */ },
      "uv": { /* ... */ }
    },
    "index": {
      "type": "Uint16Array",
      "array": [0, 1, 2, 2, 3, 0]
    },
    "groups": [
      {"start": 0, "count": 300, "materialIndex": 0}
    ],
    "boundingSphere": {
      "center": [0, 0, 0],
      "radius": 1.732
    }
  }
}

Loading Serialized Geometry

const loader = new THREE.BufferGeometryLoader();
const geometry = await loader.loadAsync('models/json/pressure.json');

Integration with Object3D

BufferGeometry is referenced by renderable objects like Mesh, Line, and Points:

const geometry = new THREE.BufferGeometry();
// ... configure geometry ...

const material = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(geometry, material);

// Geometry is shared and reference-counted
const mesh2 = new THREE.Mesh(geometry, material); // Same geometry instance

When an object is added to the scene, the renderer accesses its geometry during the render loop:

Memory Management

Disposal

Call dispose() to free GPU resources when geometry is no longer needed:

geometry.dispose();
// Triggers 'dispose' event that WebGLRenderer listens to
// Deletes GPU buffers, VAOs, etc.

Important: Disposal does not happen automatically. You must explicitly dispose geometries to prevent memory leaks.

Clone and Copy

// Deep clone
const clone = geometry.clone();

// Copy from another geometry
geometry.copy(sourceGeometry);

Both methods handle all geometry data including attributes, morph targets, groups, and bounding volumes.

Performance Considerations

  1. Use Indexed Geometry: Reduces memory bandwidth by 30-50% for typical meshes
  2. Compute Bounding Volumes Once: Cache results; don't recompute every frame
  3. Avoid Dynamic Geometry Changes: Updating vertex data requires attribute.needsUpdate = true and triggers GPU re-upload
  4. Group Similar Geometries: Use InstancedMesh or BatchedMesh for multiple instances
  5. Use Appropriate Typed Arrays: Float32Array for positions, Uint16Array for indices when possible

Relationship to Other Systems

  • Material System (2.5): Geometries are paired with materials to create renderable objects
  • Scene Graph (2.3): Mesh, Line, and Points objects hold geometry references
  • Asset Pipeline (4): Various loaders deserialize geometry from external formats
  • WebGL Rendering (3.1): Renderer converts BufferGeometry to VAOs and draw calls
  • Raycasting (5.1): Uses geometry data and bounding volumes for intersection tests