Chapter 02

几何体与网格

掌握 Three.js 内置几何体全家桶,理解 Mesh = Geometry + Material 组合模式,用 BufferGeometry 自定义任意形状

1. 内置几何体全览

Three.js 提供了丰富的内置几何体,覆盖日常 3D 开发的绝大多数需求。每种几何体都继承自 BufferGeometry,可以通过构造函数参数控制尺寸和分段数。

几何体构造参数用途
BoxGeometrywidth, height, depth, wSegs, hSegs, dSegs正方体/长方体,建筑、UI 元素
SphereGeometryradius, widthSegs, heightSegs球体、星球、灯泡
TorusGeometryradius, tube, radialSegs, tubularSegs甜甜圈、戒指、轮胎
PlaneGeometrywidth, height, wSegs, hSegs地面、水面、屏幕
CylinderGeometryradiusTop, radiusBottom, height, segs圆柱、圆锥(顶半径为0)
TorusKnotGeometryradius, tube, tubularSegs, radialSegs, p, q装饰性纽结形状
TetrahedronGeometryradius, detail四面体,低多边形风格
OctahedronGeometryradius, detail八面体,宝石、钻石
IcosahedronGeometryradius, detail二十面体,球体近似
TextGeometrytext, { font, size, depth }3D 文字(需 FontLoader)
// 常用几何体示例
const box    = new THREE.BoxGeometry(1, 1, 1);
const sphere = new THREE.SphereGeometry(0.5, 32, 16); // 分段越多越圆滑
const torus  = new THREE.TorusGeometry(0.5, 0.2, 16, 50);
const plane  = new THREE.PlaneGeometry(10, 10, 10, 10); // 10x10 网格地面
const cone   = new THREE.CylinderGeometry(0, 0.5, 1, 32); // 顶半径为0 = 圆锥

2. Mesh = Geometry + Material

Three.js 中的可见 3D 对象通常是 Mesh,它由两部分组成:

const geometry = new THREE.SphereGeometry(1, 32, 16);
const material = new THREE.MeshStandardMaterial({ color: 0x049EF4 });
const mesh     = new THREE.Mesh(geometry, material);
scene.add(mesh);

// 一个 Geometry 可以被多个 Mesh 共享(节省内存)
const sharedGeo = new THREE.BoxGeometry(1, 1, 1);
const mesh1 = new THREE.Mesh(sharedGeo, new THREE.MeshStandardMaterial({ color: 'red' }));
const mesh2 = new THREE.Mesh(sharedGeo, new THREE.MeshStandardMaterial({ color: 'blue' }));
mesh1.position.x = -1.5;
mesh2.position.x = 1.5;
scene.add(mesh1, mesh2);

3. wireframe 线框模式

线框模式只显示几何体的边线,不填充面,常用于调试几何体结构或实现低多边形艺术风格:

// 方式一:材质属性
const mat = new THREE.MeshBasicMaterial({
  color: 0x049EF4,
  wireframe: true
});

// 方式二:运行时切换
mesh.material.wireframe = true;

// 方式三:专用 WireframeGeometry(显示所有边,包括对角线)
const wireGeo = new THREE.WireframeGeometry(geometry);
const wireLines = new THREE.LineSegments(wireGeo,
  new THREE.LineBasicMaterial({ color: 0x049EF4 })
);
scene.add(wireLines);

4. BufferGeometry 自定义顶点

当内置几何体无法满足需求时,可以使用 BufferGeometry 从顶点数据构建任意形状。顶点数据存储在类型化数组(TypedArray)中,直接对应 GPU 缓冲区。

// 手动创建一个三角形
const geometry = new THREE.BufferGeometry();

// 定义三个顶点(每个顶点 3 个值:x, y, z)
const vertices = new Float32Array([
  -1, 0, 0,   // 顶点 0
   1, 0, 0,   // 顶点 1
   0, 1, 0    // 顶点 2
]);

// BufferAttribute(array, itemSize)
// itemSize=3 表示每个属性由3个值组成(xyz)
geometry.setAttribute(
  'position',
  new THREE.BufferAttribute(vertices, 3)
);

// 自动计算法线(用于光照计算)
geometry.computeVertexNormals();

const mesh = new THREE.Mesh(geometry,
  new THREE.MeshStandardMaterial({ color: 0x049EF4, side: THREE.DoubleSide })
);
scene.add(mesh);

// 大量随机顶点(粒子效果预热)
const count = 5000;
const positions = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i++) {
  positions[i] = (Math.random() - 0.5) * 10;
}
const pointsGeo = new THREE.BufferGeometry();
pointsGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));

5. 分段数的重要性

分段数(Segments)决定几何体的细腻程度。分段越多,表面越平滑,但顶点数也越多,影响渲染性能。

// 低分段球(看起来像多面体)
const lowPolyBall = new THREE.SphereGeometry(1, 8, 6);  // 48 个三角面

// 高分段球(接近真正的球面)
const hiPolyBall = new THREE.SphereGeometry(1, 64, 32); // 4096 个三角面

// 实践原则:
// - 近景大物体:高分段(32-64)
// - 远景/小物体:低分段(8-16)
// - 地面/水面需要细分才能做波浪效果

6. 实战:太阳系模型

综合运用本章知识,创建一个带轨道旋转的简化太阳系:

import * as THREE from 'three';

const scene = new THREE.Scene();
const clock = new THREE.Clock();

// 太阳——自发光黄色球
const sunGeo = new THREE.SphereGeometry(1.5, 32, 16);
const sunMat = new THREE.MeshBasicMaterial({ color: 0xffcc00 });
const sun = new THREE.Mesh(sunGeo, sunMat);
scene.add(sun);

// 点光源从太阳中心发光
const sunLight = new THREE.PointLight(0xffffff, 3, 50);
scene.add(sunLight);

// 创建行星的辅助函数
function createPlanet(radius, color, orbitRadius, speed) {
  const pivot = new THREE.Object3D(); // 轴心点(用于轨道旋转)
  scene.add(pivot);

  const mesh = new THREE.Mesh(
    new THREE.SphereGeometry(radius, 24, 12),
    new THREE.MeshStandardMaterial({ color })
  );
  mesh.position.x = orbitRadius; // 偏移到轨道半径处
  pivot.add(mesh);

  // 绘制轨道圆圈
  const orbitGeo = new THREE.TorusGeometry(orbitRadius, 0.01, 2, 80);
  const orbit = new THREE.Mesh(orbitGeo,
    new THREE.MeshBasicMaterial({ color: 0x334455 })
  );
  orbit.rotation.x = Math.PI / 2;
  scene.add(orbit);

  return { pivot, mesh, speed };
}

const planets = [
  createPlanet(0.3, 0x9a9a9a, 3, 1.6),  // 水星
  createPlanet(0.5, 0xd4956a, 4.5, 1.2), // 金星
  createPlanet(0.5, 0x2266bb, 6.5, 1.0), // 地球
  createPlanet(0.4, 0xcc4422, 8.5, 0.8), // 火星
];

function animate() {
  requestAnimationFrame(animate);
  const t = clock.getElapsedTime();

  // 太阳自转
  sun.rotation.y = t * 0.2;

  // 行星公转(绕各自 pivot 旋转)
  planets.forEach(({ pivot, speed }) => {
    pivot.rotation.y = t * speed;
  });

  renderer.render(scene, camera);
}
animate();
ℹ️

Object3D 作为轴心:行星轨道旋转的技巧是创建一个空的 Object3D 作为父对象(pivot),将行星 Mesh 添加为子对象并偏移到轨道半径处,然后旋转父对象——子对象就会绕父对象的原点(即太阳中心)公转。这是 Three.js 中层级变换的核心技巧。

本章小结:Three.js 内置的几何体覆盖大多数场景,复杂形状用 BufferGeometry 自定义。Mesh = Geometry + Material 的组合设计让形状和外观独立变化。下一章深入学习材质与纹理系统。