首页
/ three.js TSL 中的 ArrayNode:数组节点的创建、属性与着色器生成机制

three.js TSL 中的 ArrayNode:数组节点的创建、属性与着色器生成机制

2026-09-04 18:59:41作者:毕习沙Eudora

ArrayNode 是 three.js 节点着色语言(TSL)中用于表示"节点集合"的核心数据类型,通过 array() 函数即可在着色器逻辑中声明带类型与初值的数组,并用 .element() 按下标取值。本文基于 docs/pages/ArrayNode.html.mdsrc/nodes/core/ArrayNode.js 的实现源码,完整覆盖 ArrayNode 的构造参数、属性、方法,并向下追溯其 shader 字符串生成链路与元素访问节点,帮助你在自定义材质、存储缓冲等场景中正确使用数组节点。

继承链与定位

文档给出的继承关系为:

EventDispatcher → Node → TempNode → ArrayNode

从源码结构看,ArrayNode 直接继承自 TempNode(见 src/nodes/core/ArrayNode.jsclass ArrayNode extends TempNode)。这意味着数组节点被声明为临时节点:当它参与更大的节点表达式时,TSL 编译器会为其分配临时变量并内联初始化,而不是要求你手动声明 GLSL 变量。

ArrayNode 表示一组节点的集合,通常通过 TSL 的 array() 函数创建。该函数与 ArrayNode 类一同从 src/nodes/core/ArrayNode.js 导出,并经 src/nodes/tsl/TSLBase.js 统一汇入 TSL 命名空间(export * from '../core/ArrayNode.js'; // array(), .toArray())。

用 array() 创建数组节点

array() 是一个接受可变参数的 TSL 函数(src/nodes/core/ArrayNode.js),源码中支持两种调用形式:

形式一:直接传入元素列表(自动推断类型与长度)

const colors = array( [
	vec3( 1, 0, 0 ),
	vec3( 0, 1, 0 ),
	vec3( 0, 0, 1 )
] );
const redColor = colors.element( 0 );

源码逻辑(src/nodes/core/ArrayNode.js):当参数只有一个时,会先对每个元素执行 nodeObject( value ) 把普通值包装成节点,然后以 nodeType = nullcount = values.length 构造 ArrayNode。类型由第一个元素的节点类型在编译期推断,因此文档示例中 colors 自动成为 vec3[ 3 ] 类型的数组节点。

形式二:显式指定类型与数量

const points = array( 'vec3', 8 );

源码逻辑(src/nodes/core/ArrayNode.js):参数为两个时,第一个是元素类型字符串(如 'vec3'),第二个是数组长度,此时 values 缺省为 null

便捷链式方法 .toArray( count )

同一文件末尾还注册了链式方法(src/nodes/core/ArrayNode.js):

addMethodChaining( 'toArray', ( node, count ) => array( Array( count ).fill( node ) ) );

这使得任意节点可以一行展开为同值数组,例如 vec3( 1, 0, 0 ).toArray( 4 ),等价于 array( [ vec3(1,0,0), vec3(1,0,0), vec3(1,0,0), vec3(1,0,0) ] )

构造函数

new ArrayNode( nodeType : string, count : number, values : Array. )

构造一个新的数组节点,各参数说明与文档一致,并结合源码补充默认值细节(src/nodes/core/ArrayNode.js):

参数 类型 说明 默认值
nodeType string 数组元素的数据类型(如 'vec3''float' null(此时类型从 values[ 0 ] 推断)
count number 数组大小(元素个数) 必填
values Array<Node> 数组的默认值节点列表 null

构造函数内将 countvalues 存为实例属性,并置 this.isArrayNode = true 作为类型测试标志。

属性

.count : number

数组大小。getArrayCount() 直接返回它(见下文方法节),是编译器决定 GLSL 数组维度的唯一依据。

.isArrayNode : boolean (readonly)

恒为 true 的类型测试标志,文档与源码(src/nodes/core/ArrayNode.js)均注明可用于 instanceof 之外的快速类型判断,例如在自定义节点中判断上游依赖是否为数组节点。

.values : Array.

数组的默认值节点列表,可能为 null。在 shader 生成阶段,values 中存在的元素会按类型逐个 build 为初值,缺失的位置则由类型默认常量填充(详见下文 generate() 分析)。

方法与着色器字符串生成

文档列出的五个方法均对应 src/nodes/core/ArrayNode.js 中的实际实现:

.generate( builder : NodeBuilder ) : string

构建输出节点并返回数组的着色器字符串,覆盖 TempNode#generate。源码实现(src/nodes/core/ArrayNode.js):

generate( builder ) {

	const type = this.getNodeType( builder );

	return builder.generateArray( type, this.count, this.values );

}

它委托给 NodeBuilder.generateArray,该方法在 src/nodes/core/NodeBuilder.js 中:先由 generateArrayDeclaration( type, count ) 生成 type[ count ] 形式的声明片段(src/nodes/core/NodeBuilder.js),再逐个拼接初值——若 values[ i ] 存在则 value.build( this, type ) 生成带类型的初值表达式,否则回退为 this.generateConst( type ) 生成的类型默认常量。因此 array( 'vec3', 3 )array( [ vec3(1,0,0), vec3(0,1,0), vec3(0,0,1) ] ) 最终都会产出 vec3 3 这类初始化器字符串。

.generateNodeType( builder : NodeBuilder ) : string

返回节点类型,覆盖 TempNode#generateNodeType。源码(src/nodes/core/ArrayNode.js)显示了一个关键行为:当 nodeType === null(即通过元素列表构造)时,返回 this.values[ 0 ].getNodeType( builder ),即元素类型在首次生成时才被确定;否则直接返回构造时显式传入的类型字符串。

.getArrayCount( builder : NodeBuilder ) : number

返回节点数组的元素个数,覆盖 TempNode#getArrayCount。实现即 return this.count;src/nodes/core/ArrayNode.js),是 TSL 类型系统询问"这是一个多大的数组"的入口,例如 NodeBuilder 在生成 vec3[ N ] 声明(src/nodes/core/NodeBuilder.js)时会用到这类信息。

.getElementType( builder : NodeBuilder ) : string

返回元素类型,覆盖 TempNode#getElementType。实现为 return this.getNodeType( builder );src/nodes/core/ArrayNode.js),即数组节点自身的节点类型就是元素类型。

.getMemberType( builder : NodeBuilder, name : string ) : string

返回成员变量的类型,覆盖 TempNode#getMemberType。源码(src/nodes/core/ArrayNode.js)同样体现"类型延迟推断":nodeType === null 时转发给 this.values[ 0 ].getMemberType( builder, name ),否则回退到父类 super.getMemberType

元素访问:.element() 与 ArrayElementNode

文档示例中的 colors.element( 0 ) 由 TSL 的 element 代理函数实现,它在 src/nodes/tsl/TSLCore.js 中定义为:

export const element = /*@__PURE__*/ nodeProxy( ArrayElementNode ).setParameterLength( 2 );

对应的 src/nodes/utils/ArrayElementNode.js 是数组元素访问的基础类,它持有"数组类节点 + 下标节点"两个成员,并在 generate() 中生成标准 GLSL 下标语法(src/nodes/utils/ArrayElementNode.js):

generate( builder ) {

	const indexType = this.indexNode.getNodeType( builder );

	const nodeSnippet = this.node.build( builder );
	const indexSnippet = this.indexNode.build( builder, ! builder.isVector( indexType ) && builder.isInteger( indexType ) ? indexType : 'uint' );

	return `${ nodeSnippet }[ ${ indexSnippet } ]`;

}

可以看到两点值得注意的实现细节:

  • 元素节点的节点类型并不硬编码,而是通过 this.node.getElementType( builder ) 从上游数组节点推断(src/nodes/utils/ArrayElementNode.js),这正是前面 ArrayNode#getElementType 存在的意义——两者构成一条类型推断链:ArrayElementNode → ArrayNode#getElementType → nodeType 或 values[0]
  • 下标构建时,若非向量且是整数类型则保留原类型,否则统一以 uint 构建,保证 GLSL/WGSL 索引合法性。

因此文档示例 const redColor = colors.element( 0 ) 编译产物形如 colors[ 0 ],其中 colorsvec3 3 , vec3( 0.0, 1.0, 0.0 ), vec3( 0.0, 0.0, 1.0 ) )

小结与相关文件

ArrayNode 在 TSL 中承担了"类型化数组常量/初始化器"的角色:array() 负责构造(支持显式类型与元素推断两种形态),TempNode 继承保证其在表达式中的临时变量语义,generate() 委托 NodeBuilder.generateArray 完成 shader 字符串拼装,.element() 则经由 ArrayElementNode 完成下标访问与类型回溯。

继续深入可参考:

需注意的适用前提:本文所述行为基于当前仓库源码(TSL 位于 src/nodes/,随 WebGPU 渲染管线一起演进),nodeTypenull 时"取 values[0] 推断类型"的实现意味着元素列表不能为空数组,否则会触发 values[ 0 ] 访问错误。

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