篇章 7:Three.js 顶点也疯狂!BufferGeometry 玩转三维积木 🧱

222 阅读7分钟

🎬 开场白

如果说上一章的烟花粒子让 Three.js 的舞台热闹非凡,那么你有没有想过:这些绚丽的粒子,本质上是由点、线、面拼接而成的?
要真正搭建出属于自己的三维世界,我们得从这些最基础的“几何积木”——几何体(Geometry)讲起。

传统几何体 vs BufferGeometry

三维物体的组成

点 (Vertex) ➝ 线 (Line) ➝ 面 (Face) ➝ 体 (Mesh)

在 Three.js 中,一个三维物体并不是直接“画出来”的,而是逐层由基础元素组合而成的:

点(Vertex)
最基本的单位,用三维坐标 (x, y, z) 表示位置。
多个点可以作为构建几何体的基石。

线(Edge/Line)
由两个顶点相连而成。
多条线可以勾勒出物体的轮廓。

面(Face)
由三个或更多顶点首尾相连形成的平面区域。
在 Three.js 中,最常见的是三角形面(Triangle)。
面可以填充颜色、贴图、并参与光照计算。

体(Mesh)
由多个面组合而成的完整三维模型。
在 Three.js 中通常用 Mesh 表示:Mesh = 几何体 (Geometry/BufferGeometry) + 材质 (Material)

👉 这样,点 → 线 → 面 → 体 的层次关系,就构成了我们在 Three.js 中看到的三维世界。

在 Three.js 的早期,Geometry 是主力军,就像普通数组一样,使用简单直观,但性能一般。
而现代的 Three.js 更推荐使用 BufferGeometry,它像 TypedArray 一样,直接和 GPU 打交道,能存放更多顶点、效率更高。

这一章,我们就来搞清楚 Geometry 和 BufferGeometry 的区别,以及如何用它们搭建三维模型的基石。

特性GeometryBufferGeometry
数据结构普通数组存储顶点TypedArray 存储顶点数据
使用难度简单直观,学习成本低稍复杂,需要理解缓冲区概念
性能表现CPU 计算为主,效率一般GPU 加速,性能更高
灵活性方便修改顶点和面修改相对繁琐,需要操作缓冲区
Three.js 支持旧版本主力,已逐渐弃用官方推荐,未来标准

早期的 Three.js 提供了 THREE.Geometry,可以直接往里加顶点和面,非常直观:

// 创建一个几何体
const geometry = new THREE.Geometry();

// 添加顶点
geometry.vertices.push(
  new THREE.Vector3(0, 0, 0), // 顶点0
  new THREE.Vector3(1, 0, 0), // 顶点1
  new THREE.Vector3(0, 1, 0)  // 顶点2
);

// 添加一个面(由三个顶点组成)
geometry.faces.push(new THREE.Face3(0, 1, 2));

// 用材质组合成网格
const material = new THREE.MeshBasicMaterial({ color: 0x3399ff, wireframe: true });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

BufferGeometry 直接用底层的 Typed Array 存储数据,更高效。

// 用类型化数组定义顶点坐标 (3个点,每点3个值)
const vertices = new Float32Array([
  0, 0, 0,  // 顶点0
  1, 0, 0,  // 顶点1
  0, 1, 0   // 顶点2
]);

// 创建几何体
const geometry = new THREE.BufferGeometry();

// 把顶点数据绑定到 position 属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// 创建材质 & 网格
const material = new THREE.MeshBasicMaterial({ color: 0xff9933, wireframe: true });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

JavaScript Typed Array(类型化数组)大全

JavaScript 的 Typed Array 是一类数组,用来在 连续内存块里存储特定类型的数据,特别适合 WebGL / GPU / 高性能计算 使用。相比普通数组,Typed Array 不能存普通对象,只能存数字,而且类型固定。

整数类型

类型字节大小取值范围是否有符号通俗理解
Int8Array1 byte-128 ~ 127✅ 有符号小整数,1字节一个
Uint8Array1 byte0 ~ 255❌ 无符号常用存颜色(0~255)
Uint8ClampedArray1 byte0 ~ 255❌ 无符号像素颜色专用,自动 clamp 超出值
Int16Array2 bytes-32768 ~ 32767✅ 有符号中等整数,2字节一个
Uint16Array2 bytes0 ~ 65535❌ 无符号中等整数,2字节一个
Int32Array4 bytes-2,147,483,648 ~ 2,147,483,647✅ 有符号大整数,4字节一个
Uint32Array4 bytes0 ~ 4,294,967,295❌ 无符号大整数,4字节一个

浮点类型

类型字节大小取值范围通俗理解
Float32Array4 bytes±3.4×10³⁸(约7位精度)GPU / WebGL 顶点坐标常用
Float64Array8 bytes±1.8×10³⁰⁸(约16位精度)高精度科学计算

BigInt 类型(ES2020 引入)

类型字节大小取值范围通俗理解
BigInt64Array8 bytes±9,223,372,036,854,775,807超大整数,JavaScript Number 精度不够用
BigUint64Array8 bytes0 ~ 18,446,744,073,709,551,615超大无符号整数

创建与使用

// 方法 1:指定长度
// 创建长度为3的 Float32Array
const arr = new Float32Array(3);
console.log(arr); // Float32Array [0, 0, 0]

// 方法 2:用普通数组初始化
const arr = new Float32Array([1.0, 2.0, 3.0]);
console.log(arr); // Float32Array [1, 2, 3]

// 方法 3:从已有 ArrayBuffer 创建
const buffer = new ArrayBuffer(12); // 12字节
const arr = new Float32Array(buffer);

BufferGeometry 概览

常用属性

属性类型说明
attributes.positionFloat32BufferAttribute顶点坐标,每 3 个数为一个顶点 (x, y, z)
attributes.normalFloat32BufferAttribute法向量,用于光照计算,每 3 个数为一个向量
attributes.colorFloat32BufferAttribute顶点颜色,每 3 个数为 RGB
attributes.uvFloat32BufferAttributeUV 贴图坐标,每 2 个数为一个纹理点 (u, v)
indexUint16Array / Uint32Array索引数组,用于复用顶点,减少重复存储
drawRangeObject控制渲染的起始点和顶点数量
groupsArray支持分组渲染不同材质
boundingBox / boundingSphereBox3 / Sphere几何体边界信息,用于碰撞或裁剪

使用方法示例

// 创建一个 BufferGeometry
const geometry = new THREE.BufferGeometry();

// 定义顶点数据 (3个顶点构成一个三角形)
const vertices = new Float32Array([
  0, 0, 0,   // 顶点0
  1, 0, 0,   // 顶点1
  0, 1, 0    // 顶点2
]);

// 将顶点数据绑定到 position 属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// 可选:定义索引,复用顶点
const indices = new Uint16Array([0, 1, 2]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

// 创建材质和网格
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const mesh = new THREE.Mesh(geometry, material);

// 添加到场景
scene.add(mesh);

内置几何体分类

在 Three.js 中,几何体(Geometry / BufferGeometry)是创建三维模型的基础。为了方便快速构建常见形状,Three.js 提供了一系列内置几何体(Built-in Geometries),我们可以直接使用,无需手动定义顶点和面。

为什么要有内置几何体?

  • 快速创建常见形状:比如立方体、球体、圆柱、平面等,避免重复手动定义顶点。
  • 节省计算和代码量:内置几何体内部已经封装好了顶点、法线、UV 等数据。
  • 便于统一操作和渲染:所有内置几何体都是 BufferGeometry 的实例,可以直接挂载材质(Material)进行渲染。
  • 可拓展性:可以在内置几何体的基础上修改顶点或生成自定义几何体。

基础体

  • BoxGeometry:立方体/长方体,你的积木砖头。
  • SphereGeometry:球体,地球、篮球都可以。
  • PlaneGeometry:平面,地板、墙壁的最佳选择。
  • CircleGeometry:圆形,简单又高效的圆盘。
  • ConeGeometry:圆锥,圣诞树、冰激凌造型都能来。
  • CylinderGeometry:圆柱,水桶、火箭筒都靠它。

环形体

  • TorusGeometry:甜甜圈,带洞洞的圆环。
  • TorusKnotGeometry:神秘结,数学和艺术的完美结合。
  • RingGeometry:圆环平面版,像戒指或光环。

正多面体

  • TetrahedronGeometry:四面体,顶点狂欢的小家伙。
  • OctahedronGeometry:八面体,钻石的灵感源泉。
  • DodecahedronGeometry:十二面体,魔法骰子!
  • IcosahedronGeometry:二十面体,适合模拟球形效果。
  • PolyhedronGeometry:自定义多面体,随心所欲。

路径类

  • TubeGeometry:沿着路径生成管道,比如火箭轨道或蛇形灯。
  • LatheGeometry:旋转体,通过旋转 2D 轮廓得到三维模型,像车轮或花瓶。

自由绘制

  • ShapeGeometry:用 Shape 绘制自定义平面。
  • ExtrudeGeometry:拉伸 Shape,生成三维立体,比如 3D 字体或装饰物。

辅助几何

  • EdgesGeometry:显示模型边缘,调试或做线框风格。
  • WireframeGeometry:整个模型线框化,带点科技感。

🌟 结尾

好了,经过这一章的疯狂堆砌,我们的三维积木世界已经初具规模 🧱。但是,不管是立方体、球体,还是那条神秘的管道,如果没有尺寸和单位的概念,它们可能会大得像高楼,或者小到只有蚂蚁能看到 😅。

所以,在下一章里,我们要把这些三维积木放到“现实世界”的尺子上,聊聊 Three.js 的单位体系、如何设置几何体尺寸,以及如何用比例和缩放让你的模型既美观又合理。毕竟,再炫的模型,如果大小不对,看起来就像拿放大镜看蚂蚁一样尴尬 😂。