首页
/ three.js 节点系统(TSL)探秘:NodeFunctionInput 输入描述器与 GLSL/WGSL 函数解析机制

three.js 节点系统(TSL)探秘:NodeFunctionInput 输入描述器与 GLSL/WGSL 函数解析机制

2026-09-07 11:39:54作者:裘旻烁

在 three.js 的节点材质/TSL(Three Shading Language)体系中,NodeFunction 描述一段“原生着色器函数”,而 NodeFunctionInput 则是该函数每个形参(输入参数)的元数据描述器——记录参数的类型、名字、数组长度以及 GLSL 特有的限定符信息。本篇以 docs/pages/NodeFunctionInput.html.md 文档为骨架,结合 src/nodes/core/NodeFunctionInput.js 及其解析器、调用端源码,深入讲解该类的构造参数、全部属性,以及它是如何被 GLSL/WGSL 解析器生成、又被 FunctionNode/FunctionCallNode 消费并最终拼装成真实着色器代码的。读完你将理解 glslFn/wgslFn 背后参数绑定机制的底层原理,并能准确排查自定义着色器函数在接入节点材质时遇到的类型与入参问题。

一、NodeFunctionInput 在节点体系中的定位

NodeFunctionInput 是 three.js 节点系统里的一个轻量数据类(不继承 Node),它本身不参与渲染计算,只在“函数解析 / 着色器代码生成”阶段作为数据结构传递。官方对其的一句话定义是:

Describes the input of a NodeFunction(描述一个 NodeFunction 的输入)。

它与相关组件的关系可以概括为:

组件 源码位置 角色
NodeFunction src/nodes/core/NodeFunction.js 表示一段原生着色器函数的抽象基类,持有返回类型 typeinputs(即 NodeFunctionInput[])、函数名与精度限定符
NodeFunctionInput src/nodes/core/NodeFunctionInput.js 描述 NodeFunction 的每一个输入参数
GLSLNodeFunction / WGSLNodeFunction src/nodes/parsers/GLSLNodeFunction.js / src/renderers/webgpu/nodes/WGSLNodeFunction.js 两种原生语言解析器,解析着色器签名并产出 NodeFunctionInput[]
NodeParser src/nodes/core/NodeParser.js 解析器基类,parseFunction( source ) 返回一个 NodeFunction

NodeFunction 的源码注释可以看到,这类模块“只在构建(building)过程中相关,用户层代码不会直接使用”,NodeFunctionInput 同样如此——普通用户通常经由 TSL 的 glslFnwgslFn 间接与它打交道。

二、构造函数与参数详解

依据 NodeFunctionInput.js 的实现,构造函数签名与文档一致:

constructor( type, name, count = null, qualifier = '', isConst = false )

各参数说明如下:

参数 类型 默认值 含义
type string 必填 输入参数的类型(见下文类型说明)
name string 必填 输入参数的名称,是后续函数调用时按名绑定参数的键
count ?number null 当参数是数组时,表示数组长度;非数组参数为 null
qualifier 'in' | 'out' | 'inout' '' 参数限定符,仅 GLSL 相关
isConst boolean false 参数是否带 const 限定符,仅 GLSL 相关

type:遵循 three.js 节点类型命名

这里的 type 并不是 GLSL/WGSL 原始拼写,而是 three.js 节点系统内部的类型字符串(与 NodeBuilder 认识的类型一致)。例如在 WGSLNodeFunction.jswgslTypeLib 中定义了一整套映射:

  • 标量:f32 → floati32 → intu32 → uintbool → bool
  • 向量:vec3<f32> → vec3vec4<i32> → ivec4bvec2/bvec3/bvec4 等;
  • 矩阵:mat2x2<f32> → mat2mat3x3<f32> → mat3mat4x4<f32> → mat4
  • 资源类型:sampler → samplertexture_2d → texturetexture_cube → cubeTexturetexture_3d → texture3D、各类 texture_storage_* → storageTexture 等。

而 GLSL 本身命名与节点类型基本一致,因此在 GLSLNodeFunction.js 中直接取标识符作为 type 即可(如 floatvec3sampler2D 视实际情况而定)。

qualifier / isConst:为什么“仅对 GLSL 相关”

GLSL 支持 inoutinoutconst 等参数限定符,用于声明参数的传递方向与只读性;而 WGSL 没有这套语法,取而代之的是 fn 参数默认按值传递、需要写回时使用 ptr 指针类型。所以源码中把这两个字段明确标注为“only relevant for GLSL”。默认值 ''false 恰好对应“普通按值传入、不带任何限定符”的常见情形。

三、属性一览

该类实例化后暴露 5 个实例属性,全部来自构造参数:

属性 类型 默认值 说明
.type string 输入参数的类型
.name string 输入参数的名称
.count ?number null 数组输入的长度;非数组为 null
.qualifier 'in' | 'out' | 'inout' '' 参数限定符(仅 GLSL 相关)
.isConst boolean false 是否带 const 限定符(仅 GLSL 相关)

类定义末尾还有一个静态标记(NodeFunctionInput.js):

NodeFunctionInput.isNodeFunctionInput = true;

这一约定与 Node.isNodeNodeFunction.isNodeFunction 等一脉相承,用于在构建链路中做廉价、可靠的类型判别。

四、源码实现速览

NodeFunctionInput.js 全部实现不足 60 行,构造函数仅做字段赋值,逻辑非常干净:

constructor( type, name, count = null, qualifier = '', isConst = false ) {
    this.type = type;
    this.name = name;
    this.count = count;
    this.qualifier = qualifier;
    this.isConst = isConst;
}

它没有方法、不参与计算,是典型的“描述性结构体”。也正因如此,理解它的关键在于弄清两个问题:它由谁创建它被谁消费

五、它从哪里来:GLSL / WGSL 解析器如何构造输入描述

NodeFunctionInput 实例是在解析原生着色器签名时被批量创建的。

5.1 GLSL 路径

在 WebGL 后端,GLSLNodeBuilder 会注入 new GLSLNodeParser(),最终调用 GLSLNodeFunction.js 中的 parse() 完成签名 token 化与解析。其解析循环(L38-L73)依次识别:

  1. 前置 const 关键字 → 置 isConst = true
  2. in / out / inout 限定符 → 记录 qualifier,否则为空串;
  3. 类型标识符 → 作为 type
  4. 可选的数字 token → 作为数组长度 count(例如 float data[4] 中的 4),parseInt 失败则为 null
  5. 参数名 → 作为 name

最后在 L71 组装:

inputs.push( new NodeFunctionInput( type, name, count, qualifier, isConst ) );

可以看出,GLSL 解析器是唯一会为 qualifierisConstcount 填充非默认值的路径。

5.2 WGSL 路径

在 WebGPU 后端,WGSLNodeBuilder 注入 WGSLNodeParser,走 WGSLNodeFunction.js。它用正则从 fn name( ... ) -> type 中提取形参片段,逐项解析出名字与类型,做 WGSL→节点类型的查表映射后构造实例(L121):

inputs.push( new NodeFunctionInput( resolvedType, name ) );

注意此处只传 typename,其余参数保持默认值——因为 WGSL 没有 const/in/out/inout 语法;对于指针类参数(ptr 开头)会被解析为类型 pointer,用于调用端生成取址符 &

5.3 一个参数描述器对应一条真实形参

综上,无论哪种后端,最终一个 NodeFunctionInput 都忠实对应原始着色器函数中的一条形参。例如下面的 GLSL 函数:

vec3 brighten( const vec3 color, in float amount, out vec3 result, float weights[4] )

会被解析为 4 个 NodeFunctionInput

name type count qualifier isConst
color vec3 null '' true
amount float null 'in' false
result vec3 null 'out' false
weights float 4 '' false

六、它到哪里去:FunctionNode 与 FunctionCallNode 的参数绑定

解析产出的 NodeFunctionInput[] 会被上层的 FunctionNode.jsFunctionCallNode.js 消费,完成“TSL 参数 → 原生着色器调用”的转换。

6.1 惰性解析与输入列表暴露

FunctionNode.getNodeFunction( builder )L98-L113)通过 builder.parser.parseFunction( this.code ) 解析当前代码并缓存在节点数据中;getInputs( builder )L86-L90)直接返回 nodeFunction.inputs。解析器本身由 NodeBuilder 持有,从而保证代码生成时使用与当前渲染后端一致的语言解析规则。

6.2 按名与按位置传参

真正的参数绑定发生在 FunctionCallNode.generate()L98-L172)中,它遍历 inputs(即 NodeFunctionInput[]):

  • 对象形式调用(TSL 常用方式)时,用 parameters[ inputNode.name ] 按参数名查找传入的节点,找不到会报 TSL: Input 'xxx' not found in 'Fn()'. 错误;
  • 数组形式调用时按位置匹配,个数不足会自动补 float(0),个数超限会截断并告警;
  • 每个实参通过 generateInputinputNode.type 构建,即 node.build( builder, type ),把节点结果规整为函数形参声明的类型;当形参类型为 pointer 时则生成 '&' + node.build( builder )(WGSL 引用传递)。

这正是 NodeFunctionInput.typename 两个字段在实际着色器代码生成中最关键的用途——它决定了调用参数如何被强制转型、如何被命名寻址。

七、典型使用场景:以 glslFn 接入自定义函数

普通用户接触这套机制的最常见入口是 TSL 提供的 glslFnwgslFn(定义见 FunctionNode.js)。例如:

import { glslFn, texture } from 'three/tsl';

// 原生 GLSL 函数:对颜色做亮度缩放
const scaleColor = glslFn( `
    vec3 scaleColor( vec3 color, float amount ) {
        return color * amount;
    }
` );

// 使用:实参以对象形式按形参名绑定
material.colorNode = scaleColor( {
    color: texture( mapNode ),
    amount: 0.5
} );

内部流程即:glslFn 构建 FunctionNode → 构建期 GLSLNodeParservec3 color, float amount 解析成两个 NodeFunctionInput → 调用 scaleColor({...})FunctionCallNode 依据 inputNode.namecolor/amount)把 TSL 节点实参逐一绑定、按 inputNode.typevec3/float)格式化后拼出 scaleColor( tColor, tAmount ) 的调用字符串,并在着色器中登记函数声明。

八、注意事项与边界

  • NodeFunctionInputNodeFunction 一样属于“构建期内部结构”,除非你正在编写渲染器、节点解析器或深度定制 TSL,一般无需直接实例化它;它已通过 src/nodes/Nodes.js 统一导出,作为节点系统的公共 API 面供上层引用。
  • GLSL 数组形参需要带长度字面量,解析器才能正确填出 count;若长度是宏或表达式则无法识别(正则只匹配数字 token),count 会退化为 null
  • 对于 in/out/inout/const,GLSL 解析器只有在限定符出现在类型之前时才识别;书写时请保持 const vec3 colorinout vec3 result 这类标准顺序。
  • 如果调用自定义函数时报 TSL: Input 'xxx' not found in 'Fn()'.,说明对象形式的实参键名与解析出的形参名不一致,可先核对 GLSL/WGSL 源码中的形参拼写。

九、延伸阅读

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

项目优选

收起
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
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
918
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.6 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
517
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389