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
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
| Property | Type | Description |
|---|---|---|
id | number | Auto-incrementing unique identifier |
uuid | string | Globally unique identifier |
name | string | Optional name for the geometry |
attributes | Object | Dictionary mapping attribute names to BufferAttribute instances |
index | BufferAttribute | null | Optional index buffer for indexed rendering |
groups | Array<Object> | Defines material groups for multi-material rendering |
morphAttributes | Object | Morph target data for vertex animation |
boundingBox | Box3 | null | Axis-aligned bounding box (computed via computeBoundingBox()) |
boundingSphere | Sphere | null | Bounding sphere (computed via computeBoundingSphere()) |
drawRange | Object | {start, count} to render a subset of the geometry |
userData | Object | Application-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
Common Attributes
| Attribute Name | Item Size | Description |
|---|---|---|
| position | 3 | Vertex positions (x, y, z) |
| normal | 3 | Vertex normals for lighting |
| uv | 2 | Texture coordinates (u, v) |
| uv1, uv2, uv3 | 2 | Additional UV sets for multi-texturing |
| color | 3 or 4 | Vertex colors (RGB or RGBA) |
| tangent | 4 | Tangent 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 Array | Use Case |
|---|---|
| Float32Array | Positions, normals, UVs, colors (most common) |
| Uint16Array | Indices (up to 65,535 vertices) |
| Uint32Array | Indices (more than 65,535 vertices) |
| Int16Array | Compressed integer attributes |
| Uint8Array | Color 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
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
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
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.positionGeometry Operations
BufferGeometry provides methods for transforming and manipulating geometry data.
Transformation Methods
| Method | Description | Typical Use Case |
|---|---|---|
| applyMatrix4(matrix) | Applies 4×4 transformation matrix to position, normal, tangent | Baking object transforms into geometry |
| applyQuaternion(q) | Applies rotation via quaternion | Rotating geometry in place |
| rotateX(angle) | Rotates around X-axis | One-time geometry adjustment |
| rotateY(angle) | Rotates around Y-axis | One-time geometry adjustment |
| rotateZ(angle) | Rotates around Z-axis | One-time geometry adjustment |
| translate(x, y, z) | Translates geometry | Moving geometry origin |
| scale(x, y, z) | Scales geometry | Resizing geometry |
| center() | Centers geometry at origin | Centering imported models |
| lookAt(vector) | Orients geometry toward a point | Aligning 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 normalAfter 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 verticesIndirect 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
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 instanceWhen 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
- Use Indexed Geometry: Reduces memory bandwidth by 30-50% for typical meshes
- Compute Bounding Volumes Once: Cache results; don't recompute every frame
- Avoid Dynamic Geometry Changes: Updating vertex data requires
attribute.needsUpdate = trueand triggers GPU re-upload - Group Similar Geometries: Use
InstancedMeshorBatchedMeshfor multiple instances - Use Appropriate Typed Arrays:
Float32Arrayfor positions,Uint16Arrayfor 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, andPointsobjects hold geometry references - Asset Pipeline (4): Various loaders deserialize geometry from external formats
- WebGL Rendering (3.1): Renderer converts
BufferGeometryto VAOs and draw calls - Raycasting (5.1): Uses geometry data and bounding volumes for intersection tests