几何体基础
本教程共 40 篇 · 第 10 篇 · 更新于 2026-08-14 · 约 7 分钟阅读
本节目标:理解几何体的本质是顶点数据,学会用 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
BoxGeometry、SphereGeometry 这些内置几何体,同样是 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 的常见优化手段。