DreamShaderLang
内置节点

数学内置

Graph 块直接降级为算术材质节点的 29 个无前缀 HLSL 风格调用名。

有 29 个无前缀、按 HLSL 拼写的调用名会被直接降级为算术 UMaterialExpression 节点。它们覆盖 26 种 运算 —— 其中三个名字是别名。除 reflectrefract 外,每次调用生成一个节点;这两个 Unreal 没有对应 表达式,会展开成一小张子图。

float s     = sin(X);
float sa    = saturate(X);
vec3  mixed = lerp(A, B, sa);
vec3  unit  = normalize(mixed);
float d     = dot(unit, A);

它们是裸调用 —— 写 saturate(x),不是 UE.saturate(x) —— 也是唯一一个参数为位置参数的内置调用面。 可以写在 Graph 体内、Outputs 的绑定表达式中,或 Outputs 声明的初始化式中。

所有名字都不区分大小写SATURATE(x)Lerp(a, b, t)Sin(x) 都能解析。

这 29 个名字是保留名,会静默遮蔽你自己的代码。 命名为 lerpclampdotminmaxpowabs 或目录中任何其他写法的 FunctionGraphFunctionShaderFunctionVirtualFunction 或 property,在 Graph 块中都无法被调用到。名称解析时内置节点胜出,而且不会产生任何诊断

声明本身仍能编译、仍会生成对应资产;被改道的只是 Graph 调用点。要么给用户符号改名,要么从 Function 体内调用它,而不是从 Graph 块调用。

1.6.0 把这个命名空间从 19 个拓宽到 29 个 since 1.6.0。既有源码里声明了 lengthstepcrossatan 等同名符号的,需要改名。

构造器名(float3vec4int2…)也是同样的保留方式,而且检测得更早。

语法概要

记号含义示例
<x>占位符——替换成实际内容,尖括号本身不写出来。Name = <string>
[ x ]可选——整段可以整体省略。[, Root = <string>]
{ a | b }多选一——从竖线分隔的写法里取其中一个。{ Node( … ) | Comment( … ) }
可重复——前一项可以出现任意多次。<property-declaration> …
{ abs | acos | asin | atan | ceil | cos | floor | frac | fract | length
| normalize | saturate | sin | sqrt } ( <x> )
{ atan2 | cross | dot | fmod | max | min | mod | pow | reflect | step } ( <x> , <y> )
{ clamp | lerp | mix | refract | smoothstep } ( <x> , <y> , <z> )

目录

每个可接受写法一行。返回宽度是生成器赋给结果的分量数;权威表示该宽度是否参与 运算符与转换中的加宽规则。

写法参数个数降级为按序连接的输入 pin返回宽度权威
abs1UMaterialExpressionAbsInput参数的宽度继承自参数
acos since 1.6.01UMaterialExpressionArccosineInput参数的宽度继承自参数
asin since 1.6.01UMaterialExpressionArcsineInput参数的宽度继承自参数
atan since 1.6.01UMaterialExpressionArctangentInput参数的宽度继承自参数
atan2 since 1.6.02UMaterialExpressionArctangent2Y ← 参数 1,X ← 参数 2两个参数的 max任一参数为权威时设置
ceil1UMaterialExpressionCeilInput参数的宽度继承自参数
clamp3UMaterialExpressionClampInputMinMax参数 1 的宽度取自参数 1
cos1UMaterialExpressionCosineInput参数的宽度继承自参数
cross since 1.6.02UMaterialExpressionCrossProductAB恒为 3恒为权威
dot2UMaterialExpressionDotProductAB恒为 1恒为权威
floor1UMaterialExpressionFloorInput参数的宽度继承自参数
fmod since 1.5.02UMaterialExpressionFmodA ← 被除数,B ← 除数参数 1 的宽度取自参数 1
frac1UMaterialExpressionFracInput参数的宽度继承自参数
fract since 1.5.01UMaterialExpressionFracInput参数的宽度继承自参数
length since 1.6.01UMaterialExpressionLengthInput恒为 1恒为权威
lerp3UMaterialExpressionLinearInterpolateABAlpha参数 1 和 2 的 max参数 1、2 中任一为权威时设置
max2UMaterialExpressionMaxAB两个参数的 max任一参数为权威时设置
min2UMaterialExpressionMinAB两个参数的 max任一参数为权威时设置
mix3UMaterialExpressionLinearInterpolateABAlpha参数 1 和 2 的 max参数 1、2 中任一为权威时设置
mod since 1.5.02UMaterialExpressionFmodA ← 被除数,B ← 除数参数 1 的宽度取自参数 1
normalize1UMaterialExpressionNormalizeVectorInput参数的宽度继承自参数
pow2UMaterialExpressionPowerBaseExponent参数 1 的宽度取自参数 1
reflect since 1.6.02一张 4 节点子图 —— 见下面的说明——两个参数的 max任一参数为权威时设置
refract since 1.6.03一张 14 节点子图 —— 见下面的说明——参数 1 和 2 的 max参数 1、2 中任一为权威时设置
saturate1UMaterialExpressionSaturateInput参数的宽度继承自参数
sin1UMaterialExpressionSineInput参数的宽度继承自参数
smoothstep since 1.6.03UMaterialExpressionSmoothStepMinMaxValue三个参数的 max任一参数为权威时设置
sqrt1UMaterialExpressionSquareRootInput参数的宽度继承自参数
step since 1.6.02UMaterialExpressionStepY ← 参数 1(edge),X ← 参数 2(value)两个参数的 max任一参数为权威时设置

别名

三对别名。每对中的两种写法可以互换,生成的节点完全相同 —— 都没有被弃用。

别名对节点说明
lerp / mixLinearInterpolatemix 是 GLSL 写法
frac / fractFracfract 是 GLSL 写法 since 1.5.0
fmod / modFmodmod 是 GLSL 写法 since 1.5.0

Function 的 HLSL 体内部,标识符 mod 会被 GLSL 别名处理改写成 fmod。在 Graph 块中两种写法都被直接接受,不做改写。

参数规则

下列规则对上面每个内置节点都完全一致。

#规则违反时的后果
1参数个数精确 —— 没有默认值、没有可选参数、没有变参Math function '{Name}' expects exactly {N} argument(s).
2所有参数都是位置参数;不接受命名参数报成参数个数错误,见下
3每个参数都作为完整 Graph 表达式求值,允许嵌套内置调用内层错误被包装成 Math function '{Name}' argument {Index}: {Error}
4拒绝纹理对象值Math function '{Name}' only accepts numeric scalar/vector arguments.
5拒绝 MaterialAttributes同上
6拒绝 Substrate同上
7检查、不加宽、不广播参数的分量数这里什么都不发生;不匹配会在之后表现为 Unreal 材质翻译错误

规则 7 是最需要留意的。dot(vec3Value, floatValue) 在 DreamShader 这里没有任何诊断就通过了, 之后在 Unreal 自己的 shader 编译阶段失败。和算术运算符不同,这条路径没有标量/向量兼容性检查 —— 对比运算符与转换

命名参数

每个参数个数检查的判断都是「参数个数不对或者某个参数带名字」,两种情况都发出同一条个数错误消息。

给数学内置传命名参数报的是参数个数错误,而不是「不该带名字」。saturate(Input = X) —— 一个参数,名字还和节点 pin 完全对得上 —— 会失败于 Math function 'saturate' expects exactly 1 argument.

解决办法是去掉名字:saturate(X)。命名参数是 UE.* / Substrate.* 的特性,不是数学内置的特性。

名称解析

数学内置的名字在任何用户声明的名字之前解析。Graph 的调用分派器按下列顺序判断:

#候选参考
1向量/标量构造器名(float3vec4int2…)运算符与转换
2UE.SceneTextureUE.* 节点
3任何以 UE. 开头的被调用者UE.* 节点
4任何以 Substrate. 开头的被调用者Substrate
5数学内置 —— 本页
6SampleTexture2DUE.Expression
7已声明的 property,按参数 pin 调用形式Properties 类型
8FunctionGraphFunctionShaderFunctionVirtualFunction调用

拼错的内置名不会被报成数学错误。saturte(x) 会穿过全部八步,由调用路径报成 Unknown Graph function 'saturte'.

逐个内置节点的说明

clamp

clamp(Input, Min, Max) 会连接全部三个参数,并把节点的 ClampMode 保持在默认值 CMODE_Clamp。 要用 CMODE_ClampMinCMODE_ClampMax,走通用形式:

UE.Expression(Class = "Clamp", OutputType = "float1", Input = x, Min = a, ClampMode = "CMODE_ClampMin")

dot

唯一返回宽度固定的内置节点。无论参数宽度是多少,dot 总是产生 1 分量的权威结果,所以 float d = dot(A, B); 不需要 swizzle。

fmod、mod

参数 1 是被除数,参数 2 是除数,分别连到节点的 AB pin。结果取被除数的宽度。

反编译器没有处理 UMaterialExpressionFmod 的分支。已有的 Fmod 节点会被 导出成通用的 UE.Expression(Class="Fmod", …) 调用,而不是 fmod(…)。导出的源码是等价的, 只是无法回到内置写法。

lerp、mix

结果宽度是参数 1 和 2 的 max —— Alpha 参数不参与。用标量 Alpha 混合两个 vec3 得到 vec3

min、max

这两个名字共用一套实现,区别只在选择的节点类。两者都取两个参数宽度的 max

normalize

唯一一个输入 pin 不叫 Input 的内置节点。这条路径上的输入按反射属性名绑定,而 UMaterialExpressionNormalize 把它的 pin 命名为 VectorInput。这个差异在调用点是看不见的 —— normalize(N) —— 但会出现在 could not bind input / failed to access input 这两条诊断里。

sin、cos

两者都把节点的 Period 属性保持在默认值。要用非默认周期:

UE.Expression(Class = "Sine", OutputType = "float1", Input = x, Period = 2.0)

step

since 1.6.0

step(edge, x) 返回 x >= edge ? 1 : 0,和 HLSL 一致。但 UMaterialExpressionStep 的 pin 命名是反过来的 —— Y 是 edge、X 是 value —— 所以参数 1 连到 Y、参数 2 连到 X。手写节点形式的等价调用是:

UE.Expression(Class = "Step", OutputType = "float1", Y = edge, X = x)

smoothstep

since 1.6.0

smoothstep(min, max, x),参数顺序同 HLSL,直接连到节点的 MinMaxValue pin。

length、cross

since 1.6.0

dot 之外仅有的两个返回宽度固定的内置:无论参数是什么,length 恒为 1 分量、cross 恒为 3 分量, 两者的宽度都是权威的。这与同样这两个类经 UE.Expression 触达时报告的 宽度一致。

asin、acos、atan、atan2

since 1.6.0

四个反三角节点。atan2(y, x) 的参数顺序同 HLSL,而节点的 pin 本来就叫 YX,所以映射是直接的。

引擎还带了 ArcsineFastArccosineFastArctangentFastArctangent2Fast —— 在有限输入范围上 成立的更便宜的近似。它们没有内置写法,用 UE.Expression(Class = "ArcsineFast", OutputType = "float1", Input = x) 触达。

reflect、refract

since 1.6.0

仅有的两个背后不是一个节点的内置。Unreal 既没有 Reflect 也没有 Refract 表达式,所以两者都被降级成 HLSL 对它们的定义,返回给调用方的是那张子图的最后一个节点。

reflect(i, n) 展开成 i - 2 * dot(i, n) * n —— 四个节点(DotProduct、两个 MultiplySubtract)。 字面量 2 骑在 MultiplyConstB 上,不额外花一个 Constant 节点。

refract(i, n, eta) 展开成完整的 HLSL 定义 —— 十四个节点:

k = 1 - eta*eta * (1 - dot(n, i)^2)
k < 0 ? 0 : eta*i - (eta*dot(n, i) + sqrt(k)) * n

全内反射的判断是一个 If 节点,所以两侧都会被翻译、再选其一;对负 ksqrt 只落在被丢弃的那一侧, 和 HLSL 完全一样。k == 0 仍然满足公式(sqrt(0) == 0)并走折射侧。零分支写成 i * 0 而不是常量, 是为了让它的类型永远等于 i 的类型 —— If 要求两个分支一致,而追踪到的分量数并不总能保证手挑的常量对得上。

两者都是按构造就很贵的,而且在 Graph 块里都不常用到值得画这么大一张图。 周围代码本来就是 HLSL 时,写在 Function 体里一个节点就够了。

节点复用

结果会做公共子表达式缓存。两个文本相同、操作数值也相同的调用 —— 写了两次的 sin(X) —— 只产生一个 Sine 节点,而不是两个。缓存键覆盖内置名、节点类和每个参数值。

数学内置和通用的 UE.Expression 路径以及 Substrate.* 包装节点共享这一行为。已注册的 UE.* 内置节点是例外:它们每次调用都新建节点。

每个数学节点都创建在编辑器 X 坐标 360 上,Y 取自生成器的布局计数器。

这里没有的东西

矩阵不在其中,而且加不了。 Unreal 材质图根本没有矩阵值类型,所以无论 DSL 怎么做,mul(M, v) 都不会有 Graph 写法。空间转换用 UE.Expression(Class = "Transform")"TransformPosition",其余的写在 Function 的 HLSL 体里。

仍然缺席、但各自都有节点因而可以经 UE.Expression 触达的:指数、对数、 tansignroundtruncdistance。例如:

UE.Expression(Class = "Distance", OutputType = "float1", A = u, B = v)

Function 的 HLSL 体内部,这些名字根本不由这个分派器处理 —— 函数体原样输出, 适用的是 HLSL 自己的内建函数。

反编译器导出已有材质时会输出这些写法:LinearInterpolatelerpClamp(当 ClampMode == CMODE_Clamp 时)→ clampPowerpowDotProductdotNormalizenormalizeMin/Maxmin/maxAbsabsSaturatesaturateFloor/Ceil/Frac/SquareRootfloor/ceil/frac/sqrt,以及 Sine/Cosine (当 Period 为 1.0 时)→ sin/cos

1.6.0 新增的十个写法没有加进导出侧:StepSmoothStepLengthCrossProduct 和四个反三角 节点导出成通用的 UE.Expression(Class="…", …) 调用,和 Fmod 是同一种不对称。reflect / refract 则根本无法 round-trip —— 它们留在图里的是普通算术节点,没有任何东西标记它们的来历。

诊断

{Name} 是作者书写的写法,保留原始大小写。{Index} 从 1 开始。

消息触发原因处理
Math function '{Name}' expects exactly 1 argument.单参数内置节点的参数个数不对,或者任何一个参数带了名字。去掉参数名 —— 这个调用面只接受位置参数。
Math function '{Name}' expects exactly 2 arguments.同上,适用于 dot、pow、min、max、fmod、mod、step、cross、atan2 和 reflect。
Math function '{Name}' expects exactly 3 arguments.同上,适用于 lerp、mix、clamp、smoothstep 和 refract。
Math function '{Name}' is missing argument {Index}.内置节点要求的某个参数槽位不存在。
Math function '{Name}' argument {Index}: {Error}参数表达式求值失败;{Error} 是内层诊断。
Math function '{Name}' only accepts numeric scalar/vector arguments.某个参数是纹理对象、MaterialAttributes 值或 Substrate 值。先采样纹理,或先拆开 attributes,再做运算。
Failed to create math function '{Name}'.材质节点创建失败。
Math function '{Name}' could not bind input '{Input}'.节点类没有暴露预期的输入属性。仅一元内置节点。
Math function '{Name}' failed to access input '{Input}'.输入属性存在但无法访问其存储。仅一元内置节点。
Unknown Graph function '{Name}'.该名字不是内置节点、构造器、property 或用户函数 —— 由调用路径而非本路径发出。拼错的内置名会落到这里。详解

完整清单见错误速查

示例

Shader(Name="Docs/M_MathBuiltins")
{
    Properties = {
        float X = 0.5;
        vec3  A = vec3(1.0, 0.0, 0.0);
        vec3  B = vec3(0.0, 1.0, 0.0);
    }
    Settings = { Domain = "UI"; ShadingModel = "Unlit"; }
    Outputs  = { vec3 Color; Base.EmissiveColor = Color; }
    Graph = {
        float s     = sin(X);
        float c     = cos(X);
        float cl    = clamp(X, 0.0, 1.0);
        float sa    = saturate(X);
        vec3  mixed = lerp(A, B, sa);
        vec3  unit  = normalize(mixed);
        float d     = dot(unit, A);
        Color = mixed * (s + c + cl) + unit * d;
    }
}

生成的节点:

Sine(X)                       -> s
Cosine(X)                     -> c
Clamp(X, 0.0, 1.0)            -> cl
Saturate(X)                   -> sa
LinearInterpolate(A, B, sa)   -> mixed     (3 components: max(3, 3))
Normalize(mixed)              -> unit      (3 components)
DotProduct(unit, A)           -> d         (1 component, always)
Add / Multiply chain          -> Color

接下来

本页目录