three.js PolyhedronGeometry 深入解析:顶点投影与球面细分算法实战指南
本篇指南围绕 three.js 的核心几何类 PolyhedronGeometry 展开,介绍如何通过"扁平顶点数组 + 索引数组"描述任意多面体,并利用球面投影与细分算法将其平滑化为球状网格。读完你将掌握 PolyhedronGeometry 的构造参数语义、底层三角剖分流程、法线与 UV 生成策略,以及派生多面体类(Tetrahedron/Octahedron/Icosahedron/Dodecahedron)的实现方式与适用场景。
PolyhedronGeometry 是什么
PolyhedronGeometry 是 three.js 中用于生成"正/任意多面体"的几何基类。多面体(Polyhedron)是三维空间中由平面面片围成的立体,其几何数据源自 src/geometries/PolyhedronGeometry.js。
该类的核心思想并不复杂:接收一组描述基础形状的顶点与面索引,先把它们全部"投影到球面上",再按需求细分为指定细节级别的网格。这也是它区别于 BoxGeometry、SphereGeometry 等确定性几何的关键——输入数据完全由调用方定义,因此既可以构造正四面体、正十二面体等柏拉图立体,也可以构造任意自定义的多面体形状。
在继承体系上,类注释标明 @augments BufferGeometry,即直接继承自 BufferGeometry(其顶层父类为 EventDispatcher)。构造完成后它最终会输出 position、normal、uv 三个 buffer attribute,供 Mesh 直接渲染使用。
构造函数与参数说明
new PolyhedronGeometry( vertices : Array.<number>, indices : Array.<number>, radius : number, detail : number )
源码中构造函数的四个参数均有默认值(见 src/geometries/PolyhedronGeometry.js):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
vertices |
Array.<number> |
[] |
描述基础形状的扁平顶点数组,按 [x0,y0,z0, x1,y1,z1, …] 排布,每组 3 个数构成一个三维坐标 |
indices |
Array.<number> |
[] |
描述基础形状的扁平索引数组,每 3 个数构成一个三角形面片的三个顶点下标 |
radius |
number |
1 |
形状的半径;所有顶点最终会被投影到以原点为球心、半径为 radius 的球面上 |
detail |
number |
0 |
细分层级(整数),数值越大,细分产生的面与顶点越多,形状越平滑 |
vertices 与 indices 的组织约定
vertices 必须以三个一组连续存放顶点坐标。例如下方这份来自单元测试 test/unit/src/geometries/PolyhedronGeometry.tests.js 的数据描述了一个四面体:
const vertices = [
1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1
];
const indices = [
2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1
];
注意索引是三角形面片级的:每 3 个下标构成一个三角面,上例 12 个索引即代表 4 个三角面,恰好拼出一个四面体的闭合表面。索引引用的下标以 vertices 中"顶点序号 × 3"作为数据偏移,对应源码中按 index * 3 读取坐标的 getVertexByIndex 逻辑(见 src/geometries/PolyhedronGeometry.js)。
源码注释与 JSDoc 均强调:detail 表示"对该几何细分多少个层级",细分越多形状越平滑。此外从源码看 detail 在细分流程中实际按整数使用(生成 detail + 1 列网格),工程实践中请传入非负整数。
构造流程与底层算法剖析
构造器内部并非简单地把输入顶点拷成 buffer,而是走了一条"细分 → 球面投影 → 生成 UV"的完整流水线(见 src/geometries/PolyhedronGeometry.js):
subdivide( detail )—— 将输入的多边形面按 detail 逐面细分,展开为小三角面片并写入顶点缓冲区;applyRadius( radius )—— 对所有新生成顶点做归一化并乘上半径,将其"贴"到目标球面上;generateUVs()—— 依据球面方位角/倾角计算 UV 坐标并修复接缝;- 设置
position、normal、uv三个 attribute; - 依据
detail取值决定法线策略。
细分:把每个三角面插值成网格
subdivide 遍历 indices 中每个三角面,逐面调用 subdivideFace(见 src/geometries/PolyhedronGeometry.js)。其插值算法为:
- 取
cols = detail + 1,对三角形两条边(a→c、b→c)分别做参数i/cols的线性插值得到aj、bj,再用j/rows在aj→bj之间二次插值,构造出三角网格的顶点阵; - 行列循环中按"上三角行数递减"的方式切分出两套小三角形(
j为偶数/奇数时三角形朝向不同),从而把原始大三角替换为更细密的面片。
当 detail = 0 时该流程不新增中间顶点,输出即为原始多边形表面。
球面投影:normalize 后再缩放
applyRadius(见 src/geometries/PolyhedronGeometry.js)逐顶点执行:
vertex.normalize().multiplyScalar( radius );
normalize() 使每个方向向量落在单位球上,再乘以 radius 放大到目标尺寸。这是"把立方体棱角磨圆成球"的关键一步——所有细分出的顶点最终都精确地位于半径 radius 的球面上,这也是为什么该几何常被当作各类球状网格的低多边形变体。
法线策略:flat 还是 smooth
源码末尾(见 src/geometries/PolyhedronGeometry.js)按 detail 分流:
detail === 0:调用computeVertexNormals(),按面片计算平面法线(flat shading),保留多面体棱角分明的硬边外观;detail > 0:调用normalizeNormals(),使用细分后的顶点方向向量直接作为法线(本质是球面径向方向),得到**平滑(smooth)**的球形明暗过渡。
这也解释了一个常见直觉:detail 越大,不仅几何更平滑,光照过渡也随之变平滑。
UV 生成与接缝修复
细分与投影完成后,generateUVs(见 src/geometries/PolyhedronGeometry.js)根据每个顶点的球面坐标计算 UV:
u = azimuth(vertex) / 2π + 0.5,其中方位角azimuth = atan2(z, -x),是"绕 Y 轴、从上方俯视逆时针"的角度;v = inclination(vertex) / π + 0.5,其中倾角inclination表示"相对 XZ 平面的仰角";- UV 纵轴最终存为
1 - v。
随后依次执行两个校正函数:
correctUVs():对每个三角面,先求面片质心的方位角,再逐顶点判断是否需要把u环绕回[0,1],避免跨2π边界时 UV 大幅跳变;correctSeam():修复跨越 UV 接缝(seam)的面——当某面三个u值的最大值超过 0.9 而最小值低于 0.1 时,把小于 0.2 的u值整体加 1,使纹理不会在背面缝合线处被错误拉伸。源码注释标明该修复针对的是 #3269 号历史问题(见 src/geometries/PolyhedronGeometry.js)。
Properties 与 Methods
.parameters : Object
构造完成后实例会保存一份构造参数字面量:
this.parameters = {
vertices: vertices,
indices: indices,
radius: radius,
detail: detail
};
如文档所述:该对象保存"生成几何所用的构造参数",实例化之后的任何修改都不会改变已生成的几何。需要改变形状时必须重新构造实例。源码中该方法相关片段见 src/geometries/PolyhedronGeometry.js。该几何同时覆写了 copy( source ),会把源实例的 parameters 一并浅拷贝(src/geometries/PolyhedronGeometry.js)。
.fromJSON( data : Object ) : PolyhedronGeometry
静态工厂方法,从序列化 JSON 重建几何(见 src/geometries/PolyhedronGeometry.js):
static fromJSON( data ) {
return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.detail );
}
反序列化时机、四个字段与构造参数一一对应。派生类(如 IcosahedronGeometry)各自覆写了 fromJSON 以仅传递自己暴露的参数(radius/detail),因此反序列化时会保持子类实例类型。
派生多面体类:无需自备数据的开箱方案
手动书写 vertices 与 indices 较繁琐,因此 three.js 基于 PolyhedronGeometry 预置了四类正多面体,全部通过 extends PolyhedronGeometry 并在构造函数里传入各自预计算的顶点/索引表实现:
- src/geometries/TetrahedronGeometry.js —— 正四面体
- src/geometries/OctahedronGeometry.js —— 正八面体
- src/geometries/IcosahedronGeometry.js —— 正二十面体
- src/geometries/DodecahedronGeometry.js —— 正十二面体
以 IcosahedronGeometry 为例(src/geometries/IcosahedronGeometry.js):构造器先用黄金比例 t = (1 + √5) / 2 预定义 12 个顶点坐标与 20 个三角面的索引表,再调用 super( vertices, indices, radius, detail )。它只对外暴露 radius 与 detail 两个参数,构造后会将 this.type 覆写为 'IcosahedronGeometry'、parameters 收敛为仅含这两个字段,这与基类在几何形状上保持一致而接口更友好。该文件开头的 JSDoc 示例展示了典型用法:
const geometry = new THREE.IcosahedronGeometry();
const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } );
const icosahedron = new THREE.Mesh( geometry, material );
scene.add( icosahedron );
对派生类设置 detail > 0 时,细分会使其"不再是严格的正二十面体",而趋向平滑球体——这一点在派生类的 JSDoc 中被特别注明。
实战:用 PolyhedronGeometry 生成球化立方体
官方手册示例 manual/examples/primitives.html 演示了把立方体八顶点通过 PolyhedronGeometry 球化的用法:
// 以立方体的 8 个顶点为输入
addSolidGeometry( -1, 0,
new THREE.PolyhedronGeometry( verticesOfCube, indicesOfFaces, radius, detail ) );
这类自定义多面体的完整落地流程是:
- 用常规
BoxGeometry或手工数组准备基础多面体的顶点坐标,并对每个四边形面拆分为两个三角形索引; - 传入
new PolyhedronGeometry( vertices, indices, radius, detail ); - 配合
MeshStandardMaterial、MeshPhongMaterial等创建Mesh加入场景。
当 radius 固定时,从结构上可以推断:detail = 0 得到棱角分明的多面体硬边效果,detail = 1、2、3… 则逐步逼近光滑球面。由于构造过程一次性完成顶点投影与 UV 计算,这类几何适合作为静态网格使用,动态逐帧修改顶点位置并不在 PolyhedronGeometry 的设计目标内。
单元测试印证
PolyhedronGeometry 的公开契约由单元测试覆盖,见 test/unit/src/geometries/PolyhedronGeometry.tests.js,入口在 test/unit/three.source.unit.js:
- 继承关系:断言
PolyhedronGeometry instanceof BufferGeometry === true; - 实例化:无参构造也可创建对象(得益于默认参数);
- type 标记:断言
object.type === 'PolyhedronGeometry'; - 标准几何测试:基于前面给出的四面体顶点/索引对运行
runStdGeometryTests,覆盖 position/normal/uv attribute、包围盒等常规几何正确性。
同样地,test/unit/src/geometries/IcosahedronGeometry.tests.js、Tetrahedron、Octahedron、Dodecahedron 的测试均以 instanceof PolyhedronGeometry 断言继承自基类。运行相关测试可执行仓库单元测试套件中的对应文件,例如通过 QUnit 加载 three.source.unit.js 后筛选 Geometries 模块。
小结
PolyhedronGeometry 是 three.js 中"输入任意多面体描述、输出球面化细分网格"的通用基类:其四参数(vertices、indices、radius、detail)约定清晰,内部则依次完成三角细分、球面归一化投影、UV 方位角映射与接缝修复三条流水线,并在 detail 为 0 与大于 0 时切换平面/平滑法线。若不希望手工准备顶点索引数据,可直接使用继承它的 Tetrahedron、Octahedron、Icosahedron、Dodecahedron 四个预置类。作为网格生成的"最底层拼图",理解其机制也有助于理解其他三类几何库中基于顶点投影/细分的实现思路。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00