首页
/ three.js Int32BufferAttribute 详解:32 位整数顶点属性的创建、继承原理与 GPU 映射

three.js Int32BufferAttribute 详解:32 位整数顶点属性的创建、继承原理与 GPU 映射

2026-09-07 11:40:57作者:羿妍玫Ivan

导读

Int32BufferAttribute 是 three.js(JavaScript 3D 库)中用于创建 32 位有符号整数(Int32)顶点缓冲区属性的便捷类,它允许开发者直接传入普通 JavaScript Array 实例即可构造 GPU 属性数据,省去手动创建 Int32Array 的步骤。本文以 docs/pages/Int32BufferAttribute.html.md 为核心骨架,结合 src/core/BufferAttribute.js 的源码实现、单元测试以及渲染器层对整数属性的处理逻辑,完整讲解其构造函数参数、继承体系、GPU 数据类型映射、归一化语义与典型应用场景。读完本文,你将掌握 Int32BufferAttribute 及同族整数属性类的正确用法,理解顶点属性在 three.js 中从「JS 数组」到「GL 缓冲区」的完整数据通路。

Int32BufferAttribute 是什么

Int32BufferAttribute 继承自 BufferAttribute(文档首行的 *Inheritance: BufferAttribute →* 即表明这一继承关系),官方文档对其定位如下:

Convenient class that can be used when creating a Int32 buffer attribute with a plain Array instance. (使用普通 Array 实例创建 Int32 缓冲区属性时的便捷类。)

「便捷(Convenient)」是理解这个类的关键词。回顾其基类 BufferAttribute 的构造行为(见 src/core/BufferAttribute.js):

if ( Array.isArray( array ) ) {
	throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );
}

也就是说,直接使用基类 new BufferAttribute()必须传入类型化数组(TypedArray),传入普通 Array 会被直接抛出 TypeError。而 Int32BufferAttribute 的整个构造器只有一行实现(src/core/BufferAttribute.js):

class Int32BufferAttribute extends BufferAttribute {

	constructor( array, itemSize, normalized ) {

		super( new Int32Array( array ), itemSize, normalized );

	}

}

它所做的唯一一件事,就是把用户传入的普通 Array 内部转换为 Int32Array,再转交给基类。如果你手头本身就已经持有 Int32Array,那么直接使用基类 BufferAttribute 即可;只有当你手头是普通数组时,这个便捷子类才有意义。在 three.js 中,同族便捷类还包含 Int8BufferAttributeInt16BufferAttributeUint8BufferAttributeUint16BufferAttributeUint32BufferAttributeUint8ClampedBufferAttributeFloat16BufferAttributeFloat32BufferAttribute 以及带实例化能力的 InstancedBufferAttribute,它们都遵循「传入普通数组、内部转换为对应 TypedArray」的相同设计,逐一列于 src/core/BufferAttribute.js 的导出表中。

构造函数与参数说明

new Int32BufferAttribute( array : Array. | Int32Array, itemSize : number, normalized : boolean )

三个参数含义如下:

参数 类型 是否必填 说明
array Array.<number>Int32Array 必填 存放属性数据的数组。若传入普通数组,构造器内部会用 new Int32Array( array ) 自动完成类型转换
itemSize number 必填 每个顶点由数组中的多少个连续数值组成(item size)
normalized boolean 选填 数据是否进行归一化,默认值为 false

依据基类中 arrayitemSize 的语义(src/core/BufferAttribute.js),属性数组的长度必须等于 itemSize * 顶点数,而只读属性 count 会在构造阶段由 array.length / itemSize 内部计算得出,代表该属性实际存储的元素(顶点)个数。

关于数组长度约束

由于内部使用强类型的 Int32Array 存储数据,传入数组中的所有值都会被截断转换为 32 位有符号整数(取值范围约 -21474836482147483647)。这一点与浮点型属性差异巨大:对于 Float32BufferAttribute,小数会被保留;而对于 Int32BufferAttribute,小数部分会在类型转换时被丢弃,非整数值在使用前应自行做取整处理,以免产生与预期不符的几何数据。

normalized 归一化参数的底层含义

normalized 参数默认 false。它仅对整数类型属性有意义,决定的是底层缓冲区数据与 GLSL(着色器)中取值之间的映射方式。基类源码注释对这一语义有非常明确的说明(src/core/BufferAttribute.js):

  • Uint16Array 为例,当 normalized = true 时,数组中的 0 ~ +65535 会被映射为 GLSL 属性中的 0.0f ~ +1.0f
  • normalized = false 时,数值会原样转成浮点使用,例如 65535 在着色器中就对应 65535.0f

对于 Int32BufferAttribute 而言,需要注意 WebGL 的一个重要限制:normalized 归一化在 GPU 端对整数类型使用 gl.VertexAttribPointer(浮点入口),而非归一化整数则通常要配合整数着色器属性(int/ivecN)使用 gl.VertexAttribIPointer。因此在实际工程中,非归一化的 Int32BufferAttribute 往往用于存储顶点索引、骨骼编号、材质 ID、自定义整数标记等「不希望被浮点化」的离散数据,并在自定义 Shader 或 TSL 节点中以整数类型读取。

深入源码:数据如何从数组走进 GPU

WebGLAttributes 中的类型映射

渲染器层通过 src/renderers/webgl/WebGLAttributes.js 把属性数组上传为 GPU 缓冲区。该模块会根据数组的具体类型选择对应的 WebGL 内部格式(见 src/renderers/webgl/WebGLAttributes.js 的分支判断)。与 Int32BufferAttribute 直接相关的映射链是:

  • 基类构造阶段把数据放进 Int32Array
  • WebGLAttributes 检测到 array instanceof Int32Array 后选择 type = gl.INT
  • 同时从 attribute.array.byteLength 取得缓冲区字节大小,并记录 version(详见 src/renderers/webgl/WebGLAttributes.js)。

这也解释了为什么使用普通 Array 无法直达 GPU:Array 没有 byteLength 且无法直接创建 WebGLBuffer 数据源,必须先归一化为某一种 TypedArray。

版本号驱动的增量更新

Int32BufferAttribute 继承自基类的 version 机制支撑了 CPU 侧数据的高效增量上传(src/core/BufferAttribute.js):

set needsUpdate( value ) {
	if ( value === true ) this.version ++;
}

当 JS 侧修改了 attribute.array 内容后,需要将 needsUpdate 置为 true 使 version 自增。在 src/renderers/webgl/WebGLAttributes.js 中,渲染器会比较缓存中的 version 与当前 attribute.version:仅当版本落后(或缓冲区字节大小变化)时才重新上传或更新 GPU 数据。这意味着只要不修改数据、不置 needsUpdate,同一份 Int32 属性数据不会被重复上传

实践示例

顶点数据准备

import * as THREE from 'three';

// 三个顶点构成一个三角形,每顶点 3 个分量
const positions = new Int32BufferAttribute(
	[ 0, 0, 0,  1, 0, 0,  0, 1, 0 ],
	3,
	false
);

const geometry = new THREE.BufferGeometry();
geometry.setAttribute( 'position', positions );

// 另一种更贴近底层等价写法(注意:基类不允许传普通 Array)
const positionsTyped = new Int32BufferAttribute(
	new Int32Array( [ 0, 0, 0, 1, 0, 0, 0, 1, 0 ] ),
	3,
	false
);

从源码角度可以验证,两种写法最终构造出的对象完全相同——第一个分支只是让构造器替你执行了 new Int32Array( array ) 而已。

在 BufferGeometry 中接入

Int32BufferAttribute 最常见的落地方式是配合 BufferGeometry.setAttribute() 使用,例如将其作为三角形索引数据(每顶点 1 个分量,itemSize = 1):

const indexAttr = new Int32BufferAttribute( [ 0, 1, 2, 2, 1, 3 ], 1, false );
geometry.setAttribute( 'index', indexAttr );
// 等价写法:geometry.setIndex( indexAttr );

查看与修改属性值

虽然便捷类本身只负责构造,但通过继承,实例直接可用基类提供的读写 API(见 test/unit/src/core/BufferAttribute.tests.jsFloat16BufferAttribute 的同族测试)以及 getX/setX 系列方法,例如:

const attr = new Int32BufferAttribute( [ 1, 2, 3, 4, 5, 6 ], 3 );
console.log( attr.count );     // 2
console.log( attr.getX( 0 ) ); // 1
attr.setXYZ( 1, - 4, - 5, - 6 );
attr.needsUpdate = true;       // 关键:通知渲染器重新上传

基类还提供了 setUsageonUploadCallbackcopyclonetoJSON 等通用能力;其中 clone() 通过 new this.constructor( this.array, this.itemSize ) 保留了具体子类类型(见 src/core/BufferAttribute.js),因此克隆一个 Int32BufferAttribute 得到的仍是 Int32 版本。

数据加载器中的真实应用

在实际仓库中,Int32BufferAttribute 被多个文件格式加载器广泛用于承载「必须保持整数语义」的顶点数据:

  • examples/jsm/loaders/PLYLoader.js:PLY 格式解析时,根据文件头声明的数据类型做分发,其中 case 'int32'case 'int' 均返回 Int32BufferAttribute
  • examples/jsm/loaders/PCDLoader.js:点云(PCD)加载器同样依赖 Int32BufferAttribute 等便捷类,把 ASCII/二进制中读出的整数顶点属性直接构造成 GPU 可用的缓冲属性。

这从侧面印证了该类的典型定位:当外部数据源(文件解析、物理引擎回读、自定义计算)给出的是普通 JS 数组时,它是把整数数组接入渲染管线的标准桥梁。这类整数顶点属性的渲染示例可进一步参考 webgl_buffergeometry_attributes_integer.html 与 webgpu_buffergeometry_attributes_integer.html。

继承体系与同族类型对比

Int32BufferAttribute 的继承链完整如下:

BufferAttribute(基类,EventDispatcher 子类)
└── Int32BufferAttribute(本类)

单元测试 test/unit/src/core/BufferAttribute.tests.js 中有专门针对它的 Int32BufferAttribute 模块,通过两条断言验证了:

  • new Int32BufferAttribute() 的实例 instanceof BufferAttributetrue(继承关系);
  • 无参构造亦可成功实例化(构造器对空参数有足够的容错能力)。

对比三个最有代表性的同族类型,可以快速理解选择依据:

便捷类 内部 TypedArray 位宽(字节/元素) 典型用途
Int8BufferAttribute Int8Array 1 紧凑存储小范围整数(如 -128 ~ 127 的类别 ID)
Int16BufferAttribute Int16Array 2 顶点索引等中范围整数
Int32BufferAttribute Int32Array 4 大范围整数索引、骨骼权重索引、需要直接对应 GLSL int 的数据
Uint32BufferAttribute Uint32Array 4 非负大整数,如超大索引缓冲
Float32BufferAttribute Float32Array 4 位置、法线、UV 等绝大多数浮点属性

一般情况下,顶点坐标、法线、UV 都应使用 Float32BufferAttribute只有当数据本质上是离散整数(索引、编号、整型自定义属性)时才考虑 Int32BufferAttribute,以避免不必要的存储开销与着色器类型不匹配问题。

小结

  • Int32BufferAttributeBufferAttribute 的一个极简便捷子类,构造器内部仅执行 super( new Int32Array( array ), itemSize, normalized ),作用是把普通 Array 一键转成 Int32 类型化数组并完成属性封装(核心实现见 src/core/BufferAttribute.js)。
  • 三个构造参数中,array 接受普通数组或 Int32ArrayitemSize 为每顶点分量数,normalized 默认为 false,仅对整数归一化映射有意义。
  • 渲染器层通过 src/renderers/webgl/WebGLAttributes.jsInt32Array 映射为 gl.INT,并借助 version/needsUpdate 机制实现增量上传。
  • 非归一化 Int32 属性在 GLSL 中通常以整数类型消费,适用于索引、编号等离散数据;PLY、PCD 等加载器将其作为整数顶点属性的标准出口。
  • 若需为每个顶点额外维护每实例数据,可参考同族的 InstancedBufferAttribute 家族扩展本类设计。

延伸阅读

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