1. Three.js 自定义几何体与 BufferGeometry 详解

1.1. BufferGeometry 基础概念

BufferGeometry 是 Three.js 中高效的几何体表示方式,它使用 BufferAttribute 存储顶点数据。

1.1.1. BufferGeometry 的基本结构

javascript

// 创建 BufferGeometry 实例
const geometry = new THREE.BufferGeometry();

// 顶点位置数据 (每个顶点由 x, y, z 三个值组成)
const vertices = new Float32Array([
  // 第一个三角形
  -1.0, -1.0,  0.0, // 顶点 0
   1.0, -1.0,  0.0, // 顶点 1
   1.0,  1.0,  0.0, // 顶点 2

  // 第二个三角形
   1.0,  1.0,  0.0, // 顶点 3
  -1.0,  1.0,  0.0, // 顶点 4
  -1.0, -1.0,  0.0  // 顶点 5
]);

// 创建位置属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// 索引(可选,用于重用顶点)
const indices = new Uint16Array([0, 1, 2, 3, 4, 5]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

// 法线(用于光照计算)
const normals = new Float32Array([
  0, 0, 1, // 顶点 0 法线
  0, 0, 1, // 顶点 1 法线
  0, 0, 1, // 顶点 2 法线
  0, 0, 1, // 顶点 3 法线
  0, 0, 1, // 顶点 4 法线
  0, 0, 1  // 顶点 5 法线
]);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));

// UV 坐标(用于纹理映射)
const uvs = new Float32Array([
  0, 0, // 顶点 0 UV
  1, 0, // 顶点 1 UV
  1, 1, // 顶点 2 UV
  1, 1, // 顶点 3 UV
  0, 1, // 顶点 4 UV
  0, 0  // 顶点 5 UV
]);
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

// 颜色(每个顶点可以有不同的颜色)
const colors = new Float32Array([
  1, 0, 0, // 顶点 0 颜色 (红色)
  0, 1, 0, // 顶点 1 颜色 (绿色)
  0, 0, 1, // 顶点 2 颜色 (蓝色)
  1, 1, 0, // 顶点 3 颜色 (黄色)
  0, 1, 1, // 顶点 4 颜色 (青色)
  1, 0, 1  // 顶点 5 颜色 (紫色)
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

1.2. 创建自定义几何体

1.2.1. 简单自定义几何体类

javascript

class CustomGeometry extends THREE.BufferGeometry {
  constructor(width = 1, height = 1, depth = 1) {
    super();
    this.type = 'CustomGeometry';

    this.width = width;
    this.height = height;
    this.depth = depth;

    this.buildGeometry();
  }

  buildGeometry() {
    const halfWidth = this.width / 2;
    const halfHeight = this.height / 2;
    const halfDepth = this.depth / 2;

    // 定义8个顶点(立方体的8个角)
    const vertices = new Float32Array([
      // 前面
      -halfWidth, -halfHeight,  halfDepth, // 0: 前左下
       halfWidth, -halfHeight,  halfDepth, // 1: 前右下
       halfWidth,  halfHeight,  halfDepth, // 2: 前右上
      -halfWidth,  halfHeight,  halfDepth, // 3: 前左上

      // 后面
      -halfWidth, -halfHeight, -halfDepth, // 4: 后左下
       halfWidth, -halfHeight, -halfDepth, // 5: 后右下
       halfWidth,  halfHeight, -halfDepth, // 6: 后右上
      -halfWidth,  halfHeight, -halfDepth  // 7: 后左上
    ]);

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

    // 设置位置和索引
    this.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
    this.setIndex(new THREE.BufferAttribute(indices, 1));

    // 计算法线
    this.computeVertexNormals();

    // 计算UV坐标
    this.computeUVs();
  }

  computeUVs() {
    const position = this.attributes.position;
    const count = position.count;
    const uvs = new Float32Array(count * 2);

    // 简单UV映射
    for (let i = 0; i < count; i++) {
      const x = position.getX(i);
      const y = position.getY(i);
      const z = position.getZ(i);

      // 简单的UV映射(可根据需要调整)
      uvs[i * 2] = (x + this.width / 2) / this.width;
      uvs[i * 2 + 1] = (y + this.height / 2) / this.height;
    }

    this.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
  }
}

// 使用自定义几何体
const geometry = new CustomGeometry(2, 1, 3);
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

1.2.2. 动态几何体(可修改的)

javascript

class DynamicGeometry extends THREE.BufferGeometry {
  constructor(segments = 10) {
    super();
    this.segments = segments;
    this.points = [];

    this.initGeometry();
  }

  initGeometry() {
    // 创建初始网格
    this.regenerateGeometry();
  }

  regenerateGeometry() {
    const vertices = [];
    const indices = [];

    // 生成网格顶点
    for (let i = 0; i <= this.segments; i++) {
      for (let j = 0; j <= this.segments; j++) {
        const x = (i / this.segments - 0.5) * 2;
        const y = (j / this.segments - 0.5) * 2;
        const z = Math.sin(x * 2) * Math.cos(y * 2);

        vertices.push(x, y, z);
      }
    }

    // 生成索引
    for (let i = 0; i < this.segments; i++) {
      for (let j = 0; j < this.segments; j++) {
        const a = i * (this.segments + 1) + j;
        const b = a + 1;
        const c = a + this.segments + 1;
        const d = c + 1;

        // 两个三角形组成一个四边形
        indices.push(a, b, c);
        indices.push(b, d, c);
      }
    }

    // 更新几何体
    this.setAttribute('position', new THREE.BufferAttribute(
      new Float32Array(vertices), 3
    ));

    this.setIndex(new THREE.BufferAttribute(
      new Uint16Array(indices), 1
    ));

    this.computeVertexNormals();
    this.computeUVs();
  }

  computeUVs() {
    const position = this.attributes.position;
    const count = position.count;
    const uvs = new Float32Array(count * 2);

    for (let i = 0; i < count; i++) {
      const x = position.getX(i);
      const y = position.getY(i);

      uvs[i * 2] = (x + 1) / 2;
      uvs[i * 2 + 1] = (y + 1) / 2;
    }

    this.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
  }

  // 动态更新顶点位置
  update(time = 0) {
    const position = this.attributes.position;

    for (let i = 0; i < position.count; i++) {
      const x = position.getX(i);
      const y = position.getY(i);

      // 动态改变Z坐标
      const z = Math.sin(x * 2 + time) * Math.cos(y * 2 + time);
      position.setZ(i, z);
    }

    position.needsUpdate = true;
    this.computeVertexNormals();
  }
}

// 使用动态几何体
const dynamicGeometry = new DynamicGeometry(20);
const dynamicMaterial = new THREE.MeshNormalMaterial({ wireframe: false });
const dynamicMesh = new THREE.Mesh(dynamicGeometry, dynamicMaterial);
scene.add(dynamicMesh);

// 在动画循环中更新
function animate() {
  requestAnimationFrame(animate);
  dynamicGeometry.update(Date.now() * 0.001);
  renderer.render(scene, camera);
}

1.3. 几何体工具和实用函数

1.3.1. 合并几何体

javascript

import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';

// 创建多个几何体
const geometries = [];

for (let i = 0; i < 10; i++) {
  const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
  geometry.translate(
    Math.random() * 5 - 2.5,
    Math.random() * 5 - 2.5,
    Math.random() * 5 - 2.5
  );
  geometries.push(geometry);
}

// 合并几何体
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);

// 为合并后的几何体创建单个材质
const mergedMaterial = new THREE.MeshNormalMaterial();
const mergedMesh = new THREE.Mesh(mergedGeometry, mergedMaterial);
scene.add(mergedMesh);

1.3.2. 计算包围盒和球

javascript

const geometry = new CustomGeometry(2, 3, 1);

// 计算包围盒
geometry.computeBoundingBox();
console.log('Bounding Box:', geometry.boundingBox);

// 计算包围球
geometry.computeBoundingSphere();
console.log('Bounding Sphere:', geometry.boundingSphere);

// 手动设置包围盒
geometry.boundingBox = new THREE.Box3(
  new THREE.Vector3(-1, -1.5, -0.5),
  new THREE.Vector3(1, 1.5, 0.5)
);

1.3.3. 几何体变换

javascript

const geometry = new THREE.BoxGeometry(1, 1, 1);

// 平移
geometry.translate(2, 0, 0);

// 旋转
geometry.rotateX(Math.PI / 4);

// 缩放
geometry.scale(2, 1, 0.5);

// 居中
geometry.center();

// 应用矩阵变换
const matrix = new THREE.Matrix4();
matrix.makeRotationY(Math.PI / 3);
geometry.applyMatrix4(matrix);

1.4. 高级自定义几何体示例

1.4.1. 参数化曲面几何体

javascript

class ParametricSurfaceGeometry extends THREE.BufferGeometry {
  constructor(uSegments = 32, vSegments = 32) {
    super();

    this.uSegments = uSegments;
    this.vSegments = vSegments;

    this.generateSurface();
  }

  // 曲面函数 - 可重写此方法来创建不同的曲面
  surfaceFunction(u, v) {
    // 球面坐标
    const radius = 1;
    const phi = u * Math.PI * 2;
    const theta = v * Math.PI;

    const x = radius * Math.sin(theta) * Math.cos(phi);
    const y = radius * Math.cos(theta);
    const z = radius * Math.sin(theta) * Math.sin(phi);

    return new THREE.Vector3(x, y, z);
  }

  generateSurface() {
    const vertices = [];
    const indices = [];
    const normals = [];
    const uvs = [];

    // 生成顶点
    for (let v = 0; v <= this.vSegments; v++) {
      for (let u = 0; u <= this.uSegments; u++) {
        const uNorm = u / this.uSegments;
        const vNorm = v / this.vSegments;

        // 计算顶点位置
        const vertex = this.surfaceFunction(uNorm, vNorm);
        vertices.push(vertex.x, vertex.y, vertex.z);

        // UV 坐标
        uvs.push(uNorm, vNorm);
      }
    }

    // 生成索引(三角形)
    for (let v = 0; v < this.vSegments; v++) {
      for (let u = 0; u < this.uSegments; u++) {
        const a = u + (this.uSegments + 1) * v;
        const b = a + 1;
        const c = a + this.uSegments + 1;
        const d = c + 1;

        indices.push(a, b, c);
        indices.push(b, d, c);
      }
    }

    // 设置属性
    this.setAttribute('position', new THREE.BufferAttribute(
      new Float32Array(vertices), 3
    ));

    this.setAttribute('uv', new THREE.BufferAttribute(
      new Float32Array(uvs), 2
    ));

    this.setIndex(new THREE.BufferAttribute(
      new Uint16Array(indices), 1
    ));

    // 计算法线
    this.computeVertexNormals();
  }
}

// 特殊曲面扩展
class TorusSurfaceGeometry extends ParametricSurfaceGeometry {
  constructor(R = 1, r = 0.3, uSegments = 32, vSegments = 32) {
    super(uSegments, vSegments);
    this.R = R;
    this.r = r;
  }

  surfaceFunction(u, v) {
    const phi = u * Math.PI * 2;
    const theta = v * Math.PI * 2;

    const x = (this.R + this.r * Math.cos(theta)) * Math.cos(phi);
    const y = (this.R + this.r * Math.cos(theta)) * Math.sin(phi);
    const z = this.r * Math.sin(theta);

    return new THREE.Vector3(x, y, z);
  }
}

// 使用参数化曲面
const torusGeometry = new TorusSurfaceGeometry(2, 0.5, 64, 32);
const torusMaterial = new THREE.MeshNormalMaterial({ wireframe: true });
const torusMesh = new THREE.Mesh(torusGeometry, torusMaterial);
scene.add(torusMesh);

1.4.2. 实例化几何体(高性能大量复制)

javascript

// 创建基础几何体
const baseGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);

// 创建实例化几何体
const instanceCount = 10000;
const instancedGeometry = new THREE.InstancedBufferGeometry();

// 复制基础几何体的属性
instancedGeometry.index = baseGeometry.index;
instancedGeometry.attributes.position = baseGeometry.attributes.position;
instancedGeometry.attributes.normal = baseGeometry.attributes.normal;
instancedGeometry.attributes.uv = baseGeometry.attributes.uv;

// 创建实例变换矩阵
const matrix = new THREE.Matrix4();
const matrices = [];
const colors = [];

for (let i = 0; i < instanceCount; i++) {
  // 随机位置、旋转和缩放
  const position = new THREE.Vector3(
    (Math.random() - 0.5) * 10,
    (Math.random() - 0.5) * 10,
    (Math.random() - 0.5) * 10
  );

  const rotation = new THREE.Euler(
    Math.random() * Math.PI * 2,
    Math.random() * Math.PI * 2,
    Math.random() * Math.PI * 2
  );

  const scale = new THREE.Vector3(
    Math.random() * 0.5 + 0.5,
    Math.random() * 0.5 + 0.5,
    Math.random() * 0.5 + 0.5
  );

  matrix.compose(position, new THREE.Quaternion().setFromEuler(rotation), scale);

  // 存储矩阵数据
  matrices.push(...matrix.toArray());

  // 随机颜色
  colors.push(
    Math.random(),
    Math.random(),
    Math.random()
  );
}

// 添加实例属性
instancedGeometry.setAttribute(
  'instanceMatrix',
  new THREE.InstancedBufferAttribute(new Float32Array(matrices), 16)
);

instancedGeometry.setAttribute(
  'instanceColor',
  new THREE.InstancedBufferAttribute(new Float32Array(colors), 3)
);

// 使用特殊材质支持实例化
const instancedMaterial = new THREE.MeshPhongMaterial({
  vertexColors: true
});

// 自定义着色器支持实例颜色
instancedMaterial.onBeforeCompile = (shader) => {
  shader.vertexShader = shader.vertexShader.replace(
    '#include <common>',
    `
    #include <common>
    attribute vec3 instanceColor;
    varying vec3 vInstanceColor;
    `
  );

  shader.vertexShader = shader.vertexShader.replace(
    '#include <begin_vertex>',
    `
    #include <begin_vertex>
    vInstanceColor = instanceColor;
    `
  );

  shader.fragmentShader = shader.fragmentShader.replace(
    '#include <common>',
    `
    #include <common>
    varying vec3 vInstanceColor;
    `
  );

  shader.fragmentShader = shader.fragmentShader.replace(
    'vec4 diffuseColor = vec4( diffuse, opacity );',
    `
    vec4 diffuseColor = vec4( diffuse * vInstanceColor, opacity );
    `
  );
};

const instancedMesh = new THREE.InstancedMesh(
  instancedGeometry,
  instancedMaterial,
  instanceCount
);

scene.add(instancedMesh);

1.5. 性能优化技巧

javascript

// 1. 重用 BufferAttribute
const positions = new Float32Array(1000 * 3);
const normals = new Float32Array(1000 * 3);

const positionAttr = new THREE.BufferAttribute(positions, 3);
const normalAttr = new THREE.BufferAttribute(normals, 3);

// 2. 使用 TypedArray 直接操作
const array = geometry.attributes.position.array;
for (let i = 0; i < array.length; i += 3) {
  array[i + 2] = Math.sin(array[i] * 0.1) * 0.5; // 修改 Z 坐标
}
geometry.attributes.position.needsUpdate = true;

// 3. 使用 drawRange 只渲染部分几何体
geometry.setDrawRange(0, 100); // 只绘制前100个顶点

// 4. 合并几何体减少 draw calls
const geometriesToMerge = [geometry1, geometry2, geometry3];
const merged = THREE.BufferGeometryUtils.mergeBufferGeometries(geometriesToMerge);

// 5. 使用索引重用顶点
const indexedGeometry = new THREE.BufferGeometry();
indexedGeometry.setIndex(new THREE.BufferAttribute(indices, 1));

// 6. 正确设置 needsUpdate
geometry.attributes.position.needsUpdate = true; // 位置改变时
geometry.attributes.normal.needsUpdate = true;   // 法线改变时

1.6. 完整示例

javascript

// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 添加灯光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1).normalize();
scene.add(light);

const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);

// 创建自定义几何体
class WaveGeometry extends THREE.BufferGeometry {
  constructor(width = 10, height = 10, segments = 50) {
    super();

    this.width = width;
    this.height = height;
    this.segments = segments;

    this.generateWave();
  }

  generateWave(time = 0) {
    const vertices = [];
    const indices = [];
    const colors = [];

    const halfWidth = this.width / 2;
    const halfHeight = this.height / 2;

    // 生成网格顶点
    for (let i = 0; i <= this.segments; i++) {
      for (let j = 0; j <= this.segments; j++) {
        const x = (i / this.segments - 0.5) * this.width;
        const y = (j / this.segments - 0.5) * this.height;

        // 波动效果
        const distance = Math.sqrt(x * x + y * y);
        const z = Math.sin(distance * 0.5 + time) * 0.5;

        vertices.push(x, y, z);

        // 基于高度设置颜色
        const normalizedZ = (z + 0.5) / 1.0; // 归一化到 0-1
        colors.push(
          1 - normalizedZ,    // R
          normalizedZ * 0.5,  // G
          normalizedZ         // B
        );
      }
    }

    // 生成三角形索引
    for (let i = 0; i < this.segments; i++) {
      for (let j = 0; j < this.segments; j++) {
        const a = i * (this.segments + 1) + j;
        const b = a + 1;
        const c = a + this.segments + 1;
        const d = c + 1;

        indices.push(a, b, c);
        indices.push(b, d, c);
      }
    }

    // 设置几何体属性
    this.setAttribute('position', new THREE.BufferAttribute(
      new Float32Array(vertices), 3
    ));

    this.setAttribute('color', new THREE.BufferAttribute(
      new Float32Array(colors), 3
    ));

    this.setIndex(new THREE.BufferAttribute(
      new Uint16Array(indices), 1
    ));

    // 计算法线
    this.computeVertexNormals();
  }

  update(time) {
    // 更新顶点位置
    const positions = this.attributes.position.array;
    const colors = this.attributes.color.array;

    for (let i = 0; i <= this.segments; i++) {
      for (let j = 0; j <= this.segments; j++) {
        const idx = (i * (this.segments + 1) + j) * 3;
        const x = positions[idx];
        const y = positions[idx + 1];

        // 新的波动效果
        const distance = Math.sqrt(x * x + y * y);
        const z = Math.sin(distance * 0.5 + time * 2) * 0.5;

        positions[idx + 2] = z;

        // 更新颜色
        const normalizedZ = (z + 0.5) / 1.0;
        colors[idx] = 1 - normalizedZ;
        colors[idx + 1] = normalizedZ * 0.5;
        colors[idx + 2] = normalizedZ;
      }
    }

    this.attributes.position.needsUpdate = true;
    this.attributes.color.needsUpdate = true;

    // 重新计算法线
    this.computeVertexNormals();
  }
}

// 创建波浪几何体
const waveGeometry = new WaveGeometry(10, 10, 50);

// 使用顶点颜色材质
const waveMaterial = new THREE.MeshPhongMaterial({
  vertexColors: true,
  shininess: 100,
  specular: 0x222222
});

const waveMesh = new THREE.Mesh(waveGeometry, waveMaterial);
scene.add(waveMesh);

// 添加线框
const wireframe = new THREE.WireframeGeometry(waveGeometry);
const wireframeMaterial = new THREE.LineBasicMaterial({ 
  color: 0x000000,
  linewidth: 1,
  transparent: true,
  opacity: 0.2
});
const wireframeMesh = new THREE.LineSegments(wireframe, wireframeMaterial);
waveMesh.add(wireframeMesh);

// 动画循环
let time = 0;
function animate() {
  requestAnimationFrame(animate);

  time += 0.01;

  // 更新波浪几何体
  waveGeometry.update(time);

  // 旋转相机
  camera.position.x = Math.sin(time * 0.1) * 5;
  camera.position.z = Math.cos(time * 0.1) * 5;
  camera.lookAt(0, 0, 0);

  renderer.render(scene, camera);
}

animate();

// 窗口大小调整
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

这个全面的指南涵盖了 BufferGeometry 的核心概念、自定义几何体的创建方法以及性能优化技巧。通过掌握这些内容,你可以创建各种复杂的3D形状并优化渲染性能。