Skip to content

几何体系统

目的与范围

几何体系统提供了在 Three.js 中表示 3D 网格、线条和点几何体的核心基础设施。它定义了顶点数据(位置、法线、UV、颜色、自定义属性)如何存储在 GPU 友好的类型化数组中,以及如何组织这些数据以实现高效渲染。该系统围绕 BufferGeometry 设计,它取代了较旧的 Geometry 类以减少 CPU 到 GPU 的传输开销。

本页涵盖顶点数据存储、索引、通过 groups 的多材质支持、变形目标、包围体计算和几何体操作。有关过程几何体生成的信息,请参阅 过程几何体。有关如何从外部文件加载几何体的详细信息,请参阅 GLTF 导入与导出其他格式加载器

核心架构

几何体系统以 BufferGeometry 为中心,作为所有几何体数据的主要容器。顶点属性存储为 BufferAttribute 实例,它们包装类型化数组以实现高效的 GPU 上传。

图示:几何体系统架构

SVG
100%

BufferGeometry 类

BufferGeometry 是 Three.js 中所有几何体的基类。它扩展 EventDispatcher 以支持处置事件,并提供了一种结构化的方式来定义网格、线条或点几何体。

关键属性

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

基本用法示例

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 标志。

图示:属性存储结构

SVG
100%

常见属性

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

属性管理方法

// 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 范围)

索引缓冲区

索引缓冲区启用顶点重用,减少内存使用并提高缓存一致性。通过索引引用顶点数组中的位置,而不是复制顶点。

图示:索引与非索引几何体

SVG
100%

设置索引缓冲区

// 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();

当您需要修改每三角形数据或使用不支持索引的系统时,这很有用。

材质组

组允许单个几何体使用多个材质渲染。每个组定义使用特定材质索引渲染的索引或顶点范围。

图示:使用组的多材质渲染

SVG
100%

组管理

// 为索引 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);

变形目标

变形目标通过在多个属性集之间插值来实现顶点动画。它们通常用于面部动画和有机变形。

图示:变形目标结构

SVG
100%

变形目标模式

  • 相对模式 (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 提供用于变换和操作几何体数据的方法。

变换方法

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

重要: 这些通常是几何体预处理的一次性操作,不适用于运行时动画。对于运行时变换,请改用 Object3D.positionObject3D.rotationObject3D.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 normal

After 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 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]);

这实现了诸如 GPU 驱动渲染之类的技术,其中 GPU 在不涉及 CPU 的情况下确定要渲染的内容。

序列化格式

BufferGeometry 实现 toJSON() 用于序列化,可以通过 BufferGeometryLoader 加载。

图示:序列化结构

SVG
100%

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 被可渲染对象如 MeshLinePoints 引用:

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);

这两种方法都处理所有几何体数据,包括属性、变形目标、组和包围体。

性能考虑

  1. 使用索引几何体: 对于典型网格,减少 30-50% 的内存带宽
  2. 一次计算包围体: 缓存结果;不要每帧重新计算
  3. 避免动态几何体更改: 更新顶点数据需要 attribute.needsUpdate = true 并触发 GPU 重新上传
  4. 分组相似几何体: 对多个实例使用 InstancedMeshBatchedMesh
  5. 使用适当的类型化数组: 尽可能对位置使用 Float32Array,对索引使用 Uint16Array

与其他系统的关系

  • 材质系统 (2.5): 几何体与材质配对以创建可渲染对象
  • 场景图 (2.3): MeshLinePoints 对象保存几何体引用
  • 资源管线 (4): 各种加载器从外部格式反序列化几何体
  • WebGL 渲染 (3.1): 渲染器将 BufferGeometry 转换为 VAO 和绘制调用
  • 光线投射 (5.1): 使用几何体数据和包围体进行相交测试