three.js TSL 中的 ArrayNode:数组节点的创建、属性与着色器生成机制
ArrayNode 是 three.js 节点着色语言(TSL)中用于表示"节点集合"的核心数据类型,通过 array() 函数即可在着色器逻辑中声明带类型与初值的数组,并用 .element() 按下标取值。本文基于 docs/pages/ArrayNode.html.md 与 src/nodes/core/ArrayNode.js 的实现源码,完整覆盖 ArrayNode 的构造参数、属性、方法,并向下追溯其 shader 字符串生成链路与元素访问节点,帮助你在自定义材质、存储缓冲等场景中正确使用数组节点。
继承链与定位
文档给出的继承关系为:
EventDispatcher → Node → TempNode → ArrayNode
从源码结构看,ArrayNode 直接继承自 TempNode(见 src/nodes/core/ArrayNode.js 的 class 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 = null、count = 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 |
构造函数内将 count 与 values 存为实例属性,并置 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 ],其中 colors 是 vec3 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 完成下标访问与类型回溯。
继续深入可参考:
- src/nodes/core/ArrayNode.js —
ArrayNode类与array()、.toArray()实现; - src/nodes/core/NodeBuilder.js —
generateArrayDeclaration/generateArray的字符串生成逻辑; - src/nodes/utils/ArrayElementNode.js — 元素访问节点与下标类型处理;
- src/nodes/tsl/TSLCore.js —
elementTSL 函数定义; - src/nodes/tsl/TSLBase.js — TSL 命名空间对
array()的导出。
需注意的适用前提:本文所述行为基于当前仓库源码(TSL 位于 src/nodes/,随 WebGPU 渲染管线一起演进),nodeType 为 null 时"取 values[0] 推断类型"的实现意味着元素列表不能为空数组,否则会触发 values[ 0 ] 访问错误。
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 StartedRust0624
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