Skip to content

自定义几何体

虽然 Three.js 提供了丰富的内置几何体,但在实际开发中,我们经常需要创建自定义的几何体来满足特定的需求。通过 THREE.BufferGeometry,我们可以完全控制几何体的每一个细节,包括顶点位置、面的构成、法线方向以及 UV 坐标等。

自定义几何体是 Three.js 的核心技能之一,掌握它能让你创建出独特的 3D 形状和效果。

BufferGeometry 基础概念

BufferGeometry 是 Three.js 中用于存储几何体数据的高效数据结构。它使用**缓冲区属性(Buffer Attributes)**来存储几何体的各种信息:

  • position(位置):顶点的 3D 坐标 (x, y, z)
  • normal(法线):用于光照计算的法线向量
  • uv(UV 坐标):用于纹理映射的 2D 坐标 (u, v)
  • index(索引):定义面的顶点索引
  • color(颜色):顶点颜色(可选)

创建简单三角形

让我们从最基础的三角形开始,了解自定义几何体的创建过程:

javascript
// 创建一个自定义的三角形几何体
function createTriangleGeometry() {
  const geometry = new THREE.BufferGeometry();

  // 定义三个顶点的位置 (x, y, z)
  const vertices = new Float32Array([
    -1.0,
    -1.0,
    0.0, // 左下角
    1.0,
    -1.0,
    0.0, // 右下角
    0.0,
    1.0,
    0.0, // 顶部
  ]);

  // 将顶点数据添加到几何体
  geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));

  return geometry;
}

// 使用自定义几何体
const geometry = createTriangleGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
const triangle = new THREE.Mesh(geometry, material);
scene.add(triangle);

创建矩形平面

让我们创建一个更复杂的例子 - 矩形平面,并学习如何使用索引来定义面:

javascript
function createRectangleGeometry(width = 2, height = 2) {
  const geometry = new THREE.BufferGeometry();

  // 定义四个顶点
  const vertices = new Float32Array([
    -width / 2,
    -height / 2,
    0, // 左下 (0)
    width / 2,
    -height / 2,
    0, // 右下 (1)
    width / 2,
    height / 2,
    0, // 右上 (2)
    -width / 2,
    height / 2,
    0, // 左上 (3)
  ]);

  // 定义索引,每三个顶点组成一个三角形面
  // 矩形由两个三角形组成
  const indices = [
    0,
    1,
    2, // 第一个三角形:左下、右下、右上
    2,
    3,
    0, // 第二个三角形:右上、左上、左下
  ];

  // 设置几何体属性
  geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
  geometry.setIndex(indices);

  return geometry;
}

添加法线向量

法线向量对于光照计算至关重要。我们可以手动计算或使用 Three.js 的内置方法:

javascript
function createRectangleWithNormals(width = 2, height = 2) {
  const geometry = new THREE.BufferGeometry();

  const vertices = new Float32Array([
    -width / 2,
    -height / 2,
    0,
    width / 2,
    -height / 2,
    0,
    width / 2,
    height / 2,
    0,
    -width / 2,
    height / 2,
    0,
  ]);

  // 手动定义法线向量(向Z轴正方向)
  const normals = new Float32Array([
    0,
    0,
    1, // 顶点0的法线
    0,
    0,
    1, // 顶点1的法线
    0,
    0,
    1, // 顶点2的法线
    0,
    0,
    1, // 顶点3的法线
  ]);

  const indices = [0, 1, 2, 2, 3, 0];

  geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
  geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
  geometry.setIndex(indices);

  // 或者使用自动计算法线(推荐)
  // geometry.computeVertexNormals();

  return geometry;
}

UV 坐标映射

UV 坐标是纹理映射的关键,它定义了纹理图像如何贴到 3D 模型上。UV 坐标使用二维坐标系,范围通常是 0 到 1:

javascript
function createRectangleWithUV(width = 2, height = 2) {
  const geometry = new THREE.BufferGeometry();

  const vertices = new Float32Array([
    -width / 2,
    -height / 2,
    0, // 左下
    width / 2,
    -height / 2,
    0, // 右下
    width / 2,
    height / 2,
    0, // 右上
    -width / 2,
    height / 2,
    0, // 左上
  ]);

  // UV坐标:将纹理完整地映射到矩形上
  const uvs = new Float32Array([
    0,
    0, // 左下对应纹理左下角 (0,0)
    1,
    0, // 右下对应纹理右下角 (1,0)
    1,
    1, // 右上对应纹理右上角 (1,1)
    0,
    1, // 左上对应纹理左上角 (0,1)
  ]);

  const indices = [0, 1, 2, 2, 3, 0];

  geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
  geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
  geometry.setIndex(indices);
  geometry.computeVertexNormals();

  return geometry;
}

// 使用带纹理的材质
const geometry = createRectangleWithUV(4, 4);
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("path/to/texture.jpg");
const material = new THREE.MeshBasicMaterial({ map: texture });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

创建立方体几何体

让我们创建一个完整的立方体,展示更复杂的几何体构建:

javascript
function createCubeGeometry(size = 1) {
  const geometry = new THREE.BufferGeometry();
  const s = size / 2;

  // 立方体的8个顶点
  const vertices = new Float32Array([
    // 前面 (z = s)
    -s,
    -s,
    s, // 0: 左下前
    s,
    -s,
    s, // 1: 右下前
    s,
    s,
    s, // 2: 右上前
    -s,
    s,
    s, // 3: 左上前

    // 后面 (z = -s)
    -s,
    -s,
    -s, // 4: 左下后
    s,
    -s,
    -s, // 5: 右下后
    s,
    s,
    -s, // 6: 右上后
    -s,
    s,
    -s, // 7: 左上后
  ]);

  // 定义立方体的12个三角形面(6个面 × 2个三角形)
  const indices = [
    // 前面
    0, 1, 2, 2, 3, 0,
    // 后面
    4, 6, 5, 6, 4, 7,
    // 左面
    4, 0, 3, 3, 7, 4,
    // 右面
    1, 5, 6, 6, 2, 1,
    // 顶面
    3, 2, 6, 6, 7, 3,
    // 底面
    4, 5, 1, 1, 0, 4,
  ];

  // 为每个面定义UV坐标
  const uvs = new Float32Array([
    // 前面
    0, 0, 1, 0, 1, 1, 0, 1,
    // 后面
    0, 0, 1, 0, 1, 1, 0, 1,
  ]);

  geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
  geometry.setIndex(indices);
  geometry.computeVertexNormals();

  return geometry;
}

UV 坐标进阶技巧

1. 纹理重复

javascript
// 创建重复纹理的UV坐标
const uvs = new Float32Array([
  0,
  0,
  2,
  0,
  2,
  2,
  0,
  2, // UV值超过1会产生重复效果
]);

// 设置纹理重复模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;

2. 纹理偏移和旋转

javascript
// 纹理偏移
texture.offset.set(0.5, 0.5);

// 纹理旋转(绕中心点)
texture.rotation = Math.PI / 4; // 45度

// 纹理缩放
texture.repeat.set(2, 2);

3. 多纹理 UV 映射

javascript
function createMultiUVGeometry() {
  const geometry = new THREE.BufferGeometry();

  // ... 顶点数据 ...

  // 第一套UV坐标(用于基础纹理)
  const uv1 = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);

  // 第二套UV坐标(用于细节纹理)
  const uv2 = new Float32Array([
    0,
    0,
    4,
    0,
    4,
    4,
    0,
    4, // 更高频率的重复
  ]);

  geometry.setAttribute("uv", new THREE.BufferAttribute(uv1, 2));
  geometry.setAttribute("uv2", new THREE.BufferAttribute(uv2, 2));

  return geometry;
}

实用工具函数

计算法线向量

javascript
function calculateNormal(v1, v2, v3) {
  const normal = new THREE.Vector3();

  const edge1 = new THREE.Vector3().subVectors(v2, v1);
  const edge2 = new THREE.Vector3().subVectors(v3, v1);

  normal.crossVectors(edge1, edge2).normalize();

  return normal;
}

生成球形 UV 坐标

javascript
function sphericalUV(vertex) {
  const normal = vertex.clone().normalize();
  const u = 0.5 + Math.atan2(normal.z, normal.x) / (2 * Math.PI);
  const v = 0.5 - Math.asin(normal.y) / Math.PI;
  return [u, v];
}

性能优化建议

  1. 使用索引:避免重复顶点,减少内存使用
  2. 合并几何体:使用 BufferGeometryUtils.mergeBufferGeometries() 合并多个几何体
  3. 适当的精度:根据需要选择 Float32ArrayFloat64Array
  4. 预计算法线:对于静态几何体,预先计算法线向量

总结

自定义几何体的创建涉及以下关键步骤:

  1. 创建 BufferGeometry 实例
  2. 定义顶点位置:使用 position 属性
  3. 设置面索引:使用 setIndex() 方法
  4. 添加法线向量:手动定义或使用 computeVertexNormals()
  5. 设置 UV 坐标:用于纹理映射
  6. 优化性能:合理使用索引和合并几何体

通过掌握这些技能,你可以创建出任何想要的 3D 形状,为你的 Three.js 项目带来无限的创意可能性。

相关资源: