three.js TSL 节点体系核心:InputNode 输入节点基类全解析
InputNode 是 three.js 节点材质/着色器系统(TSL,Three Shading Language)中所有"数据输入节点"的抽象基类,位于 src/nodes/core/InputNode.js。无论是把常量数值写进着色器、声明可动态更新的 uniform,还是把几何属性当作节点输入,最终都由 InputNode 及其派生类完成。本文以官方文档 docs/pages/InputNode.html.md 为骨架,结合源码逐项解析 InputNode 的构造、类型推导、精度控制与序列化机制,并向下追踪 ConstNode、UniformNode、BufferAttributeNode 三个核心派生类的真实用法,帮助你在 TSL 编程中准确选型输入节点。
继承关系与定位
EventDispatcher → Node → InputNode
在 TSL 节点体系中,一切节点(Node)都继承自 Node(其本身是事件派发器 EventDispatcher 的子类),负责保存节点类型、参与构建/缓存、生成着色器代码。而 InputNode 在此基础上专门抽象出"数据输入"这一类职责:它持有一个 JS 侧的真实数值(.value),并负责把该值在着色器生成阶段转译成可用的输入。
从源码注释可知它的核心定位是 "Base class for representing data input nodes",即所有数据输入节点的基类。它的两个固有特征可以被类型测试直接使用:
this.isInputNode = true(只读标志,见 src/nodes/core/InputNode.js#L35);this.value持有 JS 侧值;this.precision控制该值在着色器中的精度。
InputNode 本身是抽象基类,其 generate() 方法直接调用 warn( 'Abstract function.' )(见 src/nodes/core/InputNode.js#L128-L132),提示子类必须实现实际的代码生成逻辑。
构造函数与自动类型推导
new InputNode( value : any, nodeType : string )
| 参数 | 说明 | 默认值 |
|---|---|---|
value |
节点持有的值。可以是任意 JS 原始值(number、boolean、string)、函数、ArrayBuffer,也可以是 three.js 对象(Vector2/3/4、Matrix2/3/4、Color 等) | 无 |
nodeType |
节点类型。若显式给出(如 'vec3'、'float'),则直接采用;若省略,节点会尝试从 value 中推导类型 |
null |
在源码中构造器只是简单地把两个参数上交 super( nodeType ),然后初始化 .value 与 .precision(见 src/nodes/core/InputNode.js#L24-L52),真正的类型逻辑集中在 generateNodeType():
generateNodeType() {
if ( this.nodeType === null ) {
return getValueType( this.value );
}
return this.nodeType;
}
也就是说,显式指定 nodeType 时它优先于自动推导;当不指定时,由 NodeUtils.js 中的 getValueType() 依据 value 的实际形态推断着色器类型。查阅 src/nodes/core/NodeUtils.js#L229-L291 可得到完整的推导映射表:
| value 形态 | 推导出的类型 |
|---|---|
undefined / null |
null |
| number | float |
| boolean | bool |
| string | string |
| function | shader |
isVector2 === true 的对象 |
vec2 |
isVector3 === true 的对象 |
vec3 |
isVector4 === true 的对象 |
vec4 |
isMatrix2 === true 的对象 |
mat2 |
isMatrix3 === true 的对象 |
mat3 |
isMatrix4 === true 的对象 |
mat4 |
isColor === true 的对象 |
color |
ArrayBuffer 实例 |
ArrayBuffer |
| 其他对象 | null |
这套映射在 getValueFromType()(src/nodes/core/NodeUtils.js#L370-L430)中配套存在——它负责把类型名和数值重新构造为对应的 Vector/Matrix/Color 实例,是反序列化路径上的关键辅助函数。
属性详解
.isInputNode : boolean(只读)
类型测试标志,恒为 true。在 TSL 中许多工具函数需要区分节点种类,InputNode 及所有派生类都能通过该标志被快速识别。
.value : any
节点在 JS 侧持有的数据。可以是 JS 原始值、函数、ArrayBuffer 或 three.js 对象。着色器生成阶段会把它按 nodeType 对应的类型写入代码。
.precision : 'low' | 'medium' | 'high'
该值在着色器中使用的精度等级,默认 null。精度控制对 WebGPU 后端(如 f32、i32 等数据类型选择)有意义;设为 null 表示不特别指定,由构建器按默认规则处理。它通常通过 .setPrecision() 方法(链式 API 中对应 precision())设置。
核心方法
.getInputType( builder : NodeBuilder ) : string
返回节点的"输入类型",默认直接返回节点类型(this.getNodeType( builder ))。派生类可能覆盖此方法以返回固定类型或做分析式计算。
文档中给出了最有代表性的例子——纹理:一张普通 RGBA 纹理,其"输入类型"是 texture,而"节点类型"是 vec4。二者的语义差异在于:在着色器代码生成阶段,声明的是采样器(texture),而表达式取出的最终值类型是 vec4。类似的覆盖在源码中也可直接看到,例如 UniformNode 的 getInputType()(src/nodes/core/UniformNode.js#L169-L181)会把 bool 提升为 uint,以满足 WebGPU 对 uniform 存储类型的约束:
getInputType( builder ) {
let type = super.getInputType( builder );
if ( type === 'bool' ) type = 'uint';
return type;
}
.setPrecision( precision : 'low' | 'medium' | 'high' ) : InputNode
设置精度并返回 this,以支持链式调用。若最终精度需要分析式计算,派生类可覆盖此方法。实现位于 src/nodes/core/InputNode.js#L90-L96。
序列化与反序列化机制(源码补充)
官方文档未展开但源码中完整实现的 serialize() / deserialize() 方法(见 src/nodes/core/InputNode.js#L98-L126),保证了 InputNode 能随 TSL 节点树被 JSON 持久化或在 Web Worker 中传递:
serialize()保存value、valueType、nodeType与precision;若 value 具有toArray()方法(Vector/Matrix/Color 皆有)则序列化为数组;若 valueType 为ArrayBuffer,则经arrayBufferToBase64()转成 Base64 字符串后再保存;deserialize()反向还原:数组按valueType交给getValueFromType()重建;具有fromArray()的对象则调用对应方法填充;ArrayBuffer经base64ToArrayBuffer()还原。
这也解释了为何 getValueFromType() 要维护一张与类型映射表反向对应的"构造表"。
派生类:InputNode 如何落地为真实功能
从 src/nodes 目录的结构看,直接继承 InputNode 的核心实现有三处:ConstNode、UniformNode 与 BufferAttributeNode。它们各自解决了"输入数据"在不同作用域下的着色器接入问题。
ConstNode —— 编译期常量
src/nodes/core/ConstNode.js 把值作为着色器内的编译期常量输出。它调用 builder.generateConst() 把 JS 值展开成字面量,并对数值类型做了特殊优化:当节点类型与输出目标同为 float|u?int 时直接生成目标类型的常量,避免多余转换(见 src/nodes/core/ConstNode.js#L51-L63)。适合数学常量、配置值等静态输入。
UniformNode —— 运行时可更新 uniform
src/nodes/core/UniformNode.js 把值提升为着色器 uniform,运行时可被 CPU 侧更新,对应 TSL 中最常用的 uniform() 工厂函数。它引入了完整的作用域与共享机制:
.name与.setName()控制 uniform 在着色器中的命名;.groupNode/.setGroup()决定 uniform 归属于哪个 UniformGroupNode(objectGroup、frameGroup、renderGroup 等),默认按对象管理(objectGroup);getSharedNode()依据 hash 复用 uniform:同一 hash 的 uniform 在构建器中只注册一次,同名节点共享同一份声明(见 src/nodes/core/UniformNode.js#L120-L149);onUpdate()支持注册逐帧回调动态改写.value。
典型用法(配合 NodeMaterial 的节点属性):
const timeUniform = uniform( 0 );
material.colorNode = timeUniform.mul( vec3( 1, 0.5, 0.25 ) );
// 每帧更新
timeUniform.value = performance.now() / 1000;
当把一个节点传入 uniform() 时,它会沿节点树寻找其中的常量值并提取为 uniform 的初始值(见 src/nodes/core/UniformNode.js#L253-L269)。
BufferAttributeNode —— 节点级几何属性
src/nodes/accessors/BufferAttributeNode.js 允许在节点层级直接提供属性数据(而不必挂到 geometry 上),特别适合由 compute shader 生成的几何。构造时接受 BufferAttribute、InterleavedBuffer 或裸 TypedArray,并额外带 bufferType、bufferStride、bufferOffset 三个参数描述内存布局。官方注释中给出的两个典型场景:
// 在节点层直接为材质提供顶点颜色
const colors = [];
for ( let i = 0; i < position.count; i ++ ) colors.push( 1, 0, 0 );
material.colorNode = bufferAttribute( new THREE.Float32BufferAttribute( colors, 3 ) );
// 把计算着色器产出的存储缓冲直接转为属性
material.positionNode = positionBuffer.toAttribute();
其构造器签名见 src/nodes/accessors/BufferAttributeNode.js#L79-L120,若打算逐帧更新数据应使用 .setUsage( THREE.DynamicDrawUsage )。
使用建议与选型小结
- 需要编译期固定的常量(如 PI、通道系数)→ 选 ConstNode(TSL 中常用
float(0.5)等快捷写法); - 需要CPU 逐帧更新 / 跨渲染对象共享的值 → 选 UniformNode(
uniform(...)); - 需要在节点层绑定顶点缓冲数据 → 选 BufferAttributeNode(
bufferAttribute(...)); - 自己实现新节点时,只要本质是"输入某个外部数据",都应继承 InputNode,从而免费获得
.precision、类型自动推导以及serialize/deserialize序列化能力。
当 nodeType 不确定时,记得利用 InputNode 的推导规则(参照上文映射表):传入 Vector3 自动得到 vec3、传入 Color 得到 color;需要强约束时则显式传入第二个参数,例如 new InputNode( someVector, 'vec3' ),此时显式类型优先生效。
补充说明:以上行为均以当前仓库 src/nodes/core/InputNode.js 及其派生实现为准,NodeBuilder 完成的实际类型格式化、uniform 分配等后续流程可继续阅读 src/nodes/core/NodeBuilder.js 与 TSL 入口文档 docs/TSL.md。
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