几何体系统
目的与范围
几何体系统提供了在 Three.js 中表示 3D 网格、线条和点几何体的核心基础设施。它定义了顶点数据(位置、法线、UV、颜色、自定义属性)如何存储在 GPU 友好的类型化数组中,以及如何组织这些数据以实现高效渲染。该系统围绕 BufferGeometry 设计,它取代了较旧的 Geometry 类以减少 CPU 到 GPU 的传输开销。
本页涵盖顶点数据存储、索引、通过 groups 的多材质支持、变形目标、包围体计算和几何体操作。有关过程几何体生成的信息,请参阅 过程几何体。有关如何从外部文件加载几何体的详细信息,请参阅 GLTF 导入与导出和其他格式加载器。
核心架构
几何体系统以 BufferGeometry 为中心,作为所有几何体数据的主要容器。顶点属性存储为 BufferAttribute 实例,它们包装类型化数组以实现高效的 GPU 上传。
图示:几何体系统架构
BufferGeometry 类
BufferGeometry 是 Three.js 中所有几何体的基类。它扩展 EventDispatcher 以支持处置事件,并提供了一种结构化的方式来定义网格、线条或点几何体。
关键属性
| 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 |
基本用法示例
const geometry = new THREE.BufferGeometry();
// 使用 6 个顶点(2 个三角形)定义一个正方形
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));使用 BufferAttribute 存储顶点数据
顶点数据存储在 BufferAttribute 实例中,这些实例包装类型化数组。每个属性都有一个 itemSize(每个顶点的分量数)和一个 normalized 标志。
图示:属性存储结构
常见属性
| 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 |
属性管理方法
// 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')) { /* ... */ }类型化数组类型
根据数据需求使用不同的类型化数组:
| 类型化数组 | 用例 |
|---|---|
| Float32Array | 位置、法线、UV、颜色(最常见) |
| Uint16Array | 索引(最多 65,535 个顶点) |
| Uint32Array | 索引(超过 65,535 个顶点) |
| Int16Array | 压缩整数属性 |
| Uint8Array | 颜色数据(0-255 范围) |
索引缓冲区
索引缓冲区启用顶点重用,减少内存使用并提高缓存一致性。通过索引引用顶点数组中的位置,而不是复制顶点。
图示:索引与非索引几何体
设置索引缓冲区
// 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));索引和非索引之间的转换
toNonIndexed() 方法通过复制顶点将索引几何体扩展为非索引形式:
const nonIndexed = geometry.toNonIndexed();当您需要修改每三角形数据或使用不支持索引的系统时,这很有用。
材质组
组允许单个几何体使用多个材质渲染。每个组定义使用特定材质索引渲染的索引或顶点范围。
图示:使用组的多材质渲染
组管理
// 为索引 0-299 添加使用材质索引 0 的组
geometry.addGroup(0, 300, 0);
// 为索引 300-499 添加使用材质索引 1 的组
geometry.addGroup(300, 200, 1);
// 清除所有组
geometry.clearGroups();
// 访问组数组
console.log(geometry.groups);
// 输出: [{start: 0, count: 300, materialIndex: 0}, ...]重要: 每个顶点/索引必须恰好属于一个组。组不能重叠或留有空白。
绘制范围
drawRange 属性允许渲染几何体的子集而无需创建新的几何体对象:
// Render only the first 1000 vertices/indices
geometry.setDrawRange(0, 1000);
// Render everything (default)
geometry.setDrawRange(0, Infinity);变形目标
变形目标通过在多个属性集之间插值来实现顶点动画。它们通常用于面部动画和有机变形。
图示:变形目标结构
变形目标模式
- 相对模式 (
morphTargetsRelative: true): 变形属性存储相对于基础位置的偏移量 - 绝对模式 (
morphTargetsRelative: false): 变形属性存储绝对位置
geometry.morphTargetsRelative = true; // 使用相对偏移量
// 变形属性存储在 morphAttributes 字典中
geometry.morphAttributes.position = [
new Float32BufferAttribute([/* 目标 0 位置 */], 3),
new Float32BufferAttribute([/* 目标 1 位置 */], 3)
];注意: 几何体一旦被渲染,变形属性数据就不能更改。您必须调用 dispose() 并创建新的几何体。
包围体
包围体对于视锥体剔除、光线投射和物理模拟至关重要。它们必须通过 computeBoundingBox() 或 computeBoundingSphere() 显式计算。
包围盒 (Box3)
由最小和最大角点定义的轴对齐包围盒 (AABB):
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;包围球
由中心点和半径定义的球体,最适合基于距离的剔除:
geometry.computeBoundingSphere();
console.log(geometry.boundingSphere);
// Sphere { center: Vector3(0, 0, 0), radius: 1.732 }包围球算法首先从包围盒计算中心,然后找到从中心到任何顶点的最大距离。
变形目标考虑
两种包围体方法在存在变形目标时都会考虑变形目标:
// Expands bounding volumes to include all morph target positions
geometry.computeBoundingBox(); // Considers morphAttributes.position
geometry.computeBoundingSphere(); // Considers morphAttributes.position几何体操作
BufferGeometry 提供用于变换和操作几何体数据的方法。
变换方法
| 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 |
重要: 这些通常是几何体预处理的一次性操作,不适用于运行时动画。对于运行时变换,请改用 Object3D.position、Object3D.rotation 和 Object3D.scale。
法线计算
computeVertexNormals() 方法通过平均共享顶点的面法线来计算平滑法线:
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();切线计算
法线贴图需要切线。computeTangents() 方法使用 Terathon 算法生成切线向量:
geometry.computeTangents();
// Requires: index, position, normal, and uv attributes
// Produces: tangent attribute (4 components: x, y, z, w)第四个分量 (w) 存储用于着色器中副切线计算的手性。
注意: 对于更好的法线贴图效果,请改用 BufferGeometryUtils.computeMikkTSpaceTangents(),它实现了大多数 3D 内容创建工具使用的 MikkTSpace 算法。
点云辅助工具
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]);这实现了诸如 GPU 驱动渲染之类的技术,其中 GPU 在不涉及 CPU 的情况下确定要渲染的内容。
序列化格式
BufferGeometry 实现 toJSON() 用于序列化,可以通过 BufferGeometryLoader 加载。
图示:序列化结构
JSON 结构示例
{
"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
}
}
}加载序列化几何体
const loader = new THREE.BufferGeometryLoader();
const geometry = await loader.loadAsync('models/json/pressure.json');与 Object3D 集成
BufferGeometry 被可渲染对象如 Mesh、Line 和 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当对象添加到场景时,渲染器在渲染循环期间访问其几何体:
内存管理
处置
当不再需要几何体时,调用 dispose() 以释放 GPU 资源:
geometry.dispose();
// Triggers 'dispose' event that WebGLRenderer listens to
// Deletes GPU buffers, VAOs, etc.重要: 处置不会自动发生。您必须显式地处置几何体以防止内存泄漏。
克隆和拷贝
// Deep clone
const clone = geometry.clone();
// Copy from another geometry
geometry.copy(sourceGeometry);这两种方法都处理所有几何体数据,包括属性、变形目标、组和包围体。
性能考虑
- 使用索引几何体: 对于典型网格,减少 30-50% 的内存带宽
- 一次计算包围体: 缓存结果;不要每帧重新计算
- 避免动态几何体更改: 更新顶点数据需要
attribute.needsUpdate = true并触发 GPU 重新上传 - 分组相似几何体: 对多个实例使用
InstancedMesh或BatchedMesh - 使用适当的类型化数组: 尽可能对位置使用
Float32Array,对索引使用Uint16Array