首页 / Three.js 入门教程 / 几何体基础

Three.js 入门教程

几何体基础

本教程共 40 篇 · 第 10 篇 · 更新于 2026-08-14 · 约 7 分钟阅读

Three.js几何体BufferGeometry顶点索引Mesh法线

本节目标:理解几何体的本质是顶点数据,学会用 BufferGeometry 手写自定义形状,搞清 position / normal / uv 属性和索引的作用。

几何体:形状的数据

几何体描述这个物体是什么形状:有哪些顶点、顶点怎么连成三角形。它只是一堆数据,本身看不见。

WebGL 只会画三种东西:点、线、三角形。所有复杂的 3D 模型,本质都是大量三角形拼出来的。立方体由 12 个三角形组成,球体由几百上千个三角形组成。

一个场景里所有网格的三角形总数,是衡量渲染压力的基本指标。建模软件里”面数”说的也是这个。三角形越多,GPU 每帧要处理的工作越多。

一个网格(Mesh)由两部分组成:

const mesh = new THREE.Mesh(geometry, material);

几何体管形状,材质管长相,两者缺一不可。几何体可以被多个网格共享,材质也可以。

为什么叫 BufferGeometry

three.js 的几何体基类是 BufferGeometry。名字里的 Buffer 指 GPU 缓冲区:顶点数据以 TypedArray(如 Float32Array)的形式上传到 GPU,渲染时直接读取,速度很快。

旧版 three.js 有另一个 Geometry 类,r125(2021 年)已彻底移除。现在所有几何体都是 BufferGeometry。教程里如果看到 new THREE.Geometry(),那是远古代码,直接忽略。

顶点属性:position

几何体的核心是 position 属性:每个顶点的 x、y、z 坐标,按顺序排成一长串数字:

const geometry = new THREE.BufferGeometry();

const vertices = new Float32Array([
  -1.0, -1.0, 0.0, // 顶点 0
   1.0, -1.0, 0.0, // 顶点 1
   0.0,  1.0, 0.0, // 顶点 2
]);

geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

BufferAttribute(vertices, 3) 里的 3 是 itemSize:每 3 个数字构成一个顶点。上面这段代码定义了一个三角形。

索引:让顶点复用

两个三角形拼一个正方形,只需要 4 个顶点。但三角形各自独立描述,不写索引的话要列 6 个顶点,其中两个重复。

解法是用索引:顶点只存一份,再用索引数组指定每个三角形用哪三个顶点:

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

geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex([0, 1, 2, 0, 2, 3]); // 两个三角形共用一条边

第一个三角形用顶点 0、1、2,第二个用 0、2、3。顶点越多、复用越多,索引省的内存越可观。内置几何体基本都用索引。

还有一个细节:三角形的顶点顺序决定正反面。three.js 约定顶点按逆时针排列的面是正面,默认只渲染正面,背面会”消失”。所以示例里开了 side: THREE.DoubleSide,或者也可以调换顶点顺序把面转过来。

Note

网格物体只能用三角形。想画线、画点,要分别用 Line 和 Points,第 32 章讲粒子时会用到。

法线与 UV:另外两个常用属性

除 position 外,几何体还经常带两个属性:

  • normal:法线,每个顶点的朝向。光照计算要靠它判断”这个面朝哪”。没写法线时,可以调用 geometry.computeVertexNormals() 自动计算。
  • uv:纹理坐标,决定贴图怎么铺到表面。第 15、16 章讲纹理时细说。

所有属性都存在 geometry.attributes 这个字典里,用 getAttribute('position') 可以随时取出来:

console.log(geometry.attributes); // 查看几何体有哪些属性
console.log(geometry.getAttribute('position').count); // 顶点个数

内置几何体也是 BufferGeometry

BoxGeometrySphereGeometry 这些内置几何体,同样是 BufferGeometry 的实例,只是由 three.js 按参数帮你生成好顶点:

const box = new THREE.BoxGeometry(2, 1, 1);      // 宽 2、高 1、深 1
const sphere = new THREE.SphereGeometry(1, 32, 16); // 半径 1,32×16 段

内置几何体有十几个,参数各不相同。常用的几个先混个脸熟:

  • BoxGeometry:长方体,最常用的基础形状。
  • SphereGeometry:球体,半径加分段数。
  • CylinderGeometry:圆柱体,上下半径可不同。
  • ConeGeometry:圆锥体,圆柱的特例。
  • TorusGeometry:圆环(甜甜圈)。
  • PlaneGeometry:平面,适合做地面、墙面。

它们各自有哪些参数、怎么调出理想形状,第 11 章逐个介绍。

手写顶点只适合简单形状。复杂模型一般用建模软件(Blender、3ds Max)做好导出,再用加载器读进来,第 28 章讲 glTF 模型加载。

Tip

分段数(segments)越多,顶点越多,模型越精细也越耗性能。够用就好,别盲目开满。

可运行示例:手写三角形和正方形

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>10. 几何体基础</title>
  <style>
    * { margin: 0; padding: 0; }
    body { overflow: hidden; background: #1a1a2e; }
    canvas { display: block; }
    #info {
      position: fixed; left: 12px; top: 12px; color: #fff;
      font: 13px/1.7 sans-serif; background: rgba(0, 0, 0, 0.5);
      padding: 8px 12px; border-radius: 6px;
    }
  </style>
</head>
<body>
  <div id="info">
    左边:3 个顶点拼成的三角形(无索引)<br />
    右边:4 个顶点 + 索引拼成的正方形
  </div>

  <script type="importmap">
  {
    "imports": {
      "three": "https://cdn.jsdelivr.net/npm/three@0.185.0/build/three.module.js",
      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.185.0/examples/jsm/"
    }
  }
  </script>

  <script type="module">
  import * as THREE from 'three';

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x1a1a2e);

  // 三角形:3 个顶点,一个面
  const triangleGeometry = new THREE.BufferGeometry();
  const triangleVertices = new Float32Array([
    -1.0, -0.8, 0.0,
     1.0, -0.8, 0.0,
     0.0,  0.9, 0.0
  ]);
  triangleGeometry.setAttribute('position', new THREE.BufferAttribute(triangleVertices, 3));

  const triangle = new THREE.Mesh(
    triangleGeometry,
    new THREE.MeshBasicMaterial({ color: 0xff6b6b, side: THREE.DoubleSide })
  );
  triangle.position.x = -1.6;
  scene.add(triangle);

  // 正方形:4 个顶点 + 索引,复用顶点组成两个三角形
  const squareGeometry = new THREE.BufferGeometry();
  const squareVertices = new Float32Array([
    -1.0, -0.8, 0.0, // 顶点 0:左下
     1.0, -0.8, 0.0, // 顶点 1:右下
     1.0,  0.8, 0.0, // 顶点 2:右上
    -1.0,  0.8, 0.0  // 顶点 3:左上
  ]);
  squareGeometry.setAttribute('position', new THREE.BufferAttribute(squareVertices, 3));
  squareGeometry.setIndex([0, 1, 2, 0, 2, 3]);

  const square = new THREE.Mesh(
    squareGeometry,
    new THREE.MeshBasicMaterial({ color: 0x4dabf7, side: THREE.DoubleSide })
  );
  square.position.x = 1.6;
  scene.add(square);

  const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100);
  camera.position.set(0, 0, 5);

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

  const clock = new THREE.Clock();
  function animate() {
    requestAnimationFrame(animate);
    const t = clock.getElapsedTime();
    triangle.rotation.z = t * 0.6;
    square.rotation.z = -t * 0.6;
    renderer.render(scene, camera);
  }
  animate();
  </script>
</body>
</html>

运行后可以看到两个自转的平面图形。它们和内置几何体一样,能旋转、缩放、换材质——因为本质上就是同一个东西。注意材质里开了 side: THREE.DoubleSide,否则转到背面时会看不见(默认只画正面)。

如果把代码里的三角形顶点顺序改成顺时针,再关掉 DoubleSide,就能直观地看到”背面消失”的效果。

常用操作

修改顶点:直接改数组,然后标记需要重新上传:

const positions = geometry.attributes.position;
positions.array[0] = 2;       // 移动顶点 0 的 x
positions.needsUpdate = true; // 通知 GPU 重新上传

注意:每帧改顶点,数据就要重新上传一次,顶点多的几何体开销不小。做形变动画时,更高效的路子是顶点着色器,第 33 章会讲。

释放资源:物体从场景移除后,调用 dispose 释放 GPU 内存:

geometry.dispose();
material.dispose();

其他常用方法

  • computeBoundingSphere():计算包围球,视锥剔除和射线检测会用到。
  • center():把几何体居中到原点。
  • toNonIndexed():转成无索引版本(某些后处理需要)。
Note

一个几何体可以同时给多个网格用:

const meshA = new THREE.Mesh(geometry, materialA);
const meshB = new THREE.Mesh(geometry, materialB);

两个网格共享同一份顶点数据,不占双份内存。这是 three.js 的常见优化手段。