首页
/ three.js BoxGeometry 几何类完全解析:构造参数、六面分组构建与 JSON 序列化机制

three.js BoxGeometry 几何类完全解析:构造参数、六面分组构建与 JSON 序列化机制

2026-09-06 11:31:34作者:宗隆裙

本文围绕 three.js 官方文档页 docs/pages/BoxGeometry.html.md 讲解 BoxGeometry 类:如何创建以原点为中心的长方体几何、六个构造参数(含分段数)的含义与默认值、.parameters 属性与 copy() 的行为,并结合 src/geometries/BoxGeometry.js 源码深入剖析其六面构建算法、法线与 UV 生成、多材质分组机制,以及 toJSON()/fromJSON() 参数化序列化与“变换后降级”策略,帮助你在项目中正确使用并调试这一最基础的几何体。

概述与继承关系

BoxGeometry 是一个用于创建指定宽、高、深的长方体(rectangular cuboid)的几何类。根据 src/geometries/BoxGeometry.js 中的类注释与类声明,它在创建时以原点为中心(centred on the origin),且每条边都与某个坐标轴平行:

继承链:EventDispatcher → BufferGeometry → BoxGeometry

最小可用示例

官方文档给出的标准用法(同样出现在源码文件的 JSDoc 注释中):

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );

仓库中官方文档页还内嵌了一个交互式场景查看器,可直接在浏览器中观察 BoxGeometry 的效果,入口见 docs/pages/BoxGeometry.html 中引用的 geometry-browser 场景(对应源码 @demo scenes/geometry-browser.html#BoxGeometry 标注)。

构造函数:六个参数与默认值

构造函数签名为:

new BoxGeometry(
  width = 1, height = 1, depth = 1,
  widthSegments = 1, heightSegments = 1, depthSegments = 1
)
参数 含义 默认值
width 宽度,即平行于 X 轴的边长 1
height 高度,即平行于 Y 轴的边长 1
depth 深度,即平行于 Z 轴的边长 1
widthSegments 沿宽度方向(侧面)的矩形分段数 1
heightSegments 沿高度方向(侧面)的矩形分段数 1
depthSegments 沿深度方向(侧面)的矩形分段数 1

一个细节来自源码:三个分段参数在构造内部都会经过 Math.floor() 截断(见 src/geometries/BoxGeometry.js#L58-L60),因此传入 2.7 等价于 2,分段数必须是正整数才会产生预期的网格划分。

widthSegments / heightSegments / depthSegments 控制的是面被划分成的网格数量,而非顶点数。例如 widthSegments: 2, heightSegments: 3 会把相应侧面向宽、高两个维度各切 2 份、3 份。分段数直接影响每面的顶点与三角面数量,也决定了 UV 网格密度——这对纹理细分、位移贴图(displacement map)表现很重要:只有分段数足够,曲面细节才能在长方体表面上采样出来。

构建原理:六次 buildPlane 与分组(groups)

BoxGeometry 的构造体由一次 super() 调用后的一系列 buildPlane 生成。源码按“px、nx、py、ny、pz、nz”六个面依次调用(见 src/geometries/BoxGeometry.js#L76-L81):

buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height,  width,  depthSegments, heightSegments, 0 ); // px
buildPlane( 'z', 'y', 'x',  1, - 1, depth, height, - width,  depthSegments, heightSegments, 1 ); // nx
buildPlane( 'x', 'z', 'y',  1,  1, width, depth,  height,  widthSegments, depthSegments,  2 ); // py
buildPlane( 'x', 'z', 'y',  1, - 1, width, depth, - height,  widthSegments, depthSegments,  3 ); // ny
buildPlane( 'x', 'y', 'z',  1, - 1, width, height, depth,   widthSegments, heightSegments, 4 ); // pz
buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz

buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) 的参数含义可以从源码结构看:

  • u / v / w:该面所在平面上用于生成局部网格的两个轴名,以及法线所在的轴名;
  • udir / vdir:局部坐标方向的翻转标志(-1 表示反向),用来保证六个面的顶点绕序统一为外法线朝向,这也是为什么负方向面(nx、ny、nz)会传入相反尺寸(- width 等);
  • gridX / gridY:该面两个方向的网格段数;
  • materialIndex:该面对应的材质索引(0~5)。

每个面的生成逻辑(见 src/geometries/BoxGeometry.js#L90-L190)分三部分:

  1. 顶点:在 (gridX+1) × (gridY+1) 的顶点网格上,局部坐标 x = ix * segmentWidth - widthHalfy = iy * segmentHeight - heightHalf,再写入 vector[u]vector[v],而 vector[w] = depthHalf,从而把网格贴在对应半深位置,实现“以原点为中心”;
  2. 法线:每个面的法线统一沿 w 轴,方向由 depth > 0 ? 1 : - 1 决定(负深度时反转,保证法线仍朝外);
  3. UVuvs.push( ix / gridX, 1 - ( iy / gridY ) ),即每个面都完整覆盖 [0,1]×[0,1] 的 UV 空间,v 轴做了翻转。

索引与分组是理解多材质支持的关键。每个网格单元生成 6 个索引(2 个三角面 × 3 个顶点),累加进 groupCount;每个面完成后调用:

scope.addGroup( groupStart, groupCount, materialIndex );
groupStart += groupCount;

因此一个 BoxGeometry 天然带有 6 个 groupmaterialIndex 为 0~5,分别对应 px、nx、py、ny、pz、nz 面。源码注释明确写道:“this will ensure multi material support”。这意味着你可以直接给 Mesh 传入 6 个材质的数组,让每个面使用不同材质——这是 BoxGeometry 相对手写 BufferGeometry 的一大便利。

构造结束后,几何体通过 setIndexsetAttribute 装配四块数据(见 src/geometries/BoxGeometry.js#L85-L88):

this.setIndex( indices );
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );

可以推断:由于顶点不共享(每个面独立生成自己的顶点网格),六个面在棱边上各有独立顶点,法线才能保持面严格平直,不会因为共享顶点而被平滑。

.parameters 属性与 copy()

实例属性 .parameters : Object 保存构造时使用的六个参数:

this.parameters = {
  width, height, depth,
  widthSegments, heightSegments, depthSegments
};

官方文档强调:实例化之后再修改 parameters 不会改变已生成的几何数据,它只是一份“出生证明”,主要用于序列化(见下文)与调试。

copy( source ) 方法在调用基类拷贝后额外浅拷贝 parameters(见 src/geometries/BoxGeometry.js#L194-L202):

copy( source ) {
  super.copy( source );
  this.parameters = Object.assign( {}, source.parameters );
  return this;
}

注意这里是 Object.assign 新对象,clone() 出来的几何体修改自己的 parameters 不会影响源几何体。

静态方法 .fromJSON() 与序列化机制

static fromJSON( data ) {
  return new BoxGeometry(
    data.width, data.height, data.depth,
    data.widthSegments, data.heightSegments, data.depthSegments
  );
}

(见 src/geometries/BoxGeometry.js#L211-L215)它是从 JSON 对象重建实例的工厂方法,通常配合 src/loaders/ObjectLoader.js / src/loaders/BufferGeometryLoader.js 在加载 .json 场景或几何文件时使用。

fromJSON 配对的 toJSON() 继承自 BufferGeometry,但 BoxGeometryparameters 让序列化拥有了一个非常划算的优化路径。从 src/core/BufferGeometry.js#L1230-L1258 可以看到核心策略:

data.type = ( this.parameters !== undefined && this._transformed === true )
  ? 'BufferGeometry' : this.type;
...
if ( this.parameters !== undefined && this._transformed !== true ) {
  // 仅把 parameters 逐项写入 JSON,直接 return,
  // 不序列化 attributes 数据
  return data;
}

即:

  • 未变换的 BoxGeometry_transformed === false):JSON 中只记录 type: 'BoxGeometry' 和六个参数,不写入顶点/法线/UV 数组,体积极小,加载时由 fromJSON 重新生成几何;
  • 发生过顶点变换后(如调用过 translate()rotateX() 等会置 _transformed = true 的方法,见 src/core/BufferGeometry.js#L422):type 会“降级”为 'BufferGeometry',并把全部 attribute 数据落盘——因为参数已经不能还原变换后的形状了。

单元测试 test/unit/src/geometries/BoxGeometry.tests.js 对这两条路径都有显式断言:

  • new BoxGeometry(10, 20, 30)toJSON()json.width === 10json.data === undefined(参数化序列化,属性数据不写出);
  • geometry.translate( 1, 2, 3 )toJSON()json.type === 'BufferGeometry'json.width === undefined,且 json.data.attributes.position 存在;
  • clone() 的几何体 _transformed 标志会通过 copy() 传播,序列化行为保持一致。

此外,通用工具 runStdGeometryTests(见 test/unit/utils/qunit-utils.js#L188-L202)对默认参数、10×20×30、以及 10×20×30 + 2/3/4 分段 三组 BoxGeometry 执行 clone 一致性与 JSON 往返(round-trip)校验,覆盖了上述构建与序列化路径。

实战要点小结

  • 以原点为中心:几何体顶点分布在 ±width/2±height/2±depth/2,若需要底边贴地,可在 Mesh 上平移(注意对几何体调用 translate 会触发 _transformed,导致序列化体积增大);
  • 多材质:利用内置的 6 个 group,new THREE.Mesh( geometry, [mat0, mat1, mat2, mat3, mat4, mat5] ) 即可为六个面分别指定材质;
  • 分段数:只在需要位移贴图、逐面纹理细分或更密的逐顶点动画时才增大,分段是乘法级增长顶点数(每面 (gridX+1) × (gridY+1) 个顶点);
  • 序列化:保持几何体“未变换”时 JSON 只含 6 个参数,fromJSON 依赖 data.width 等字段完整重建;如果你从 JSON 加载后得到的是 BufferGeometry 而非 BoxGeometry,说明源数据发生过顶点变换,属于预期行为;
  • 参数只读.parameters 仅用于记录与序列化,运行时修改它不会重建几何。

参考文件

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388