DreamShaderLang
Graph 语言

运算符与转换

四个运算符及其优先级、字面量、构造器、swizzle,以及决定一个值何时被静默改变宽度的转换规则。

Graph 语言产生值的那一半:字面量、标识符、调用、成员访问和四个算术运算符,最终求值成 UMaterialExpression 节点。本页内容同样适用于 Outputs 绑定表达式和 Outputs 声明默认值——它们就是 同一套文法下的单条表达式。

语法概要

<expression>     := <additive>
<additive>       := <multiplicative> [ { + | - } <multiplicative> ] …
<multiplicative> := <unary> [ { * | / } <unary> ] …
<unary>          := { + | - } <unary> | <postfix>
<postfix>        := <primary> [ <postfix-op> ] …
<postfix-op>     := . <identifier> | :: <identifier> | ( [ <argument> [ , <argument> ] … ] )
<primary>        := <identifier> | <number-literal> | <string-literal> | ( <additive> )
<argument>       := [ <identifier> = ] <additive>

这就是全部文法。( ) , . :: = + - * / 是它认识的全部标点。

优先级与结合性

绑定最紧的排在最前。解析器实现的就是这四层,没有第五层。

层级运算符元数结合性说明
1f(…) 调用、.member::name后缀可自由串联:A::F(x).rgb.b
2+ -一元前缀可递归,所以 --x 能解析且合法
3* /二元a / b / c(a / b) / c
4+ -二元a - b - c(a - b) - c
( … ) 分组覆盖上面各层;不产生节点

不存在的运算符

类别文法中不存在的写法
取模% —— 请用 fmod / mod 内置
比较== != < > <= >= —— if 条件里合法
逻辑&& || !
位运算 / 移位& | ^ ~ << >>
条件? :
自增 / 自减++ --
复合赋值+= -= *= /=
表达式内赋值=,命名调用参数除外
下标[ ] —— 请用 swizzle
逗号运算符,,调用参数表除外
矩阵类型与运算本语言任何地方都不存在

无法识别的字符会静默终止表达式。 分词器把每个不认识的字符都映射成输入结束记号,而解析器接受 "一条表达式后面跟着输入结束"。于是 a % b 编译成 aa && b 编译成 av[0] 编译成 v—— 没有任何诊断。有两个位置会报错:位于开头的未知字符(!x)会报错,括号内的未知字符((a % b))也会 报错,因为找不到 )。把可疑表达式套上括号,是让截断暴露出来的可靠办法。完整清单见 不支持的写法

操作数规则

+ - * / 的两个操作数按此顺序检查。

#检查失败时
1两个操作数都不是纹理对象Arithmetic operators cannot be applied to texture values.
2两个操作数都不是 MaterialAttributesArithmetic operators cannot be applied to MaterialAttributes values.
3两个操作数都不是 SubstrateArithmetic operators cannot be applied to Substrate values.
4分量数相等,其中一个是标量尝试一次挽救(规则 5),然后走规则 6
5挽救:若恰好一个操作数带权威分量数,另一个可以被加宽到它跳过挽救
6重新检查兼容性Operator '{Op}' requires matching vector sizes or a scalar/vector pair, got {Left} and {Right} component(s).

规则 5 的完整含义:被加宽的必须是不带权威分量数的那一个,权威分量数必须大于零,另一个操作数的分量数 必须小于或等于它。运算符处从不尝试收窄——在这里丢通道是被刻意拒绝的,宁可让尺寸错误暴露出来。

这里没有单独的标量提升步骤。标量操作数原样传给材质节点,由 Unreal 自行复制,所以 Avec3KfloatA * K 只产生一个 Multiply 节点,而不是先 AppendVector splat。

二元运算符的结果

属性取值
节点Add / Subtract / Multiply / Divide
分量数规则 5 之后的 max(left, right)
权威分量数只要任一操作数带有就置位
输入通道掩码清空——结果是全宽度值
整数标记传播;任何运算符的结果都不是整数

一元运算符

形式降级为节点开销结果
+x恒等x 原样返回,所有标志位保留
-xMultiply(x, Constant(-1))一个 Multiply,外加一个与资产内所有一元负号共享的 Constantx 的分量数

表达式求值器不会-2.0 折叠成负字面量:它变成 Constant(2.0) * Constant(-1)。但在构造器参数 内部会被折叠,因为常量折叠接受字面量前的一元 +-——vec3(-1.0, 0.0, 1.0) 只产生一个 Constant3Vector

整数除法

/ 是唯一带额外类型规则的运算符:

Integer division is not supported by the material graph; use float() or floor(a/b).

它在两个操作数都带整数标记时触发。这个标记只由一件事设置:直接调用整数构造器(intint2..4ivec2..4uintuint2..4uvec2..4)。字面量不会设置它,变量不会,3u 这类带后缀的字面量不会, 任何运算符的结果也不会。

表达式结果
int(7) / int(2)报错
int(7) / 2允许——2 是字面量,不带标记
7 / 2允许——浮点 Divide,结果 3.5
float(int(7)) / int(2)允许——每次构造器调用都按自身名字设置标记,所以 float(…) 清掉了它
int a = int(7); int b = int(2); a / b报错——标记随变量传递
int a = 7; int b = 2; a / b允许——int 声明类型不设标记,只有 int(…) 调用才设

这个标记存在的唯一目的就是拒绝这一种情况。除此之外 intuintboolhalf 在生成图里与 float 毫无区别——没有整数运算,也没有截断。

字面量

数值字面量

<number-literal> := { <digit> | . } … [ { e | E } [ { + | - } ] <digit> … ] [ <suffix> ]
<suffix>         := { f | F | h | H | u | U | l | L }

每个数值字面量都是1 分量浮点值。不存在整数字面量类型。

写法结果
11.0Constant,1 分量
1.01.01 同一个 Constant 节点
.50.5Constant——允许前导 .
1e-30.001Constant
2.5E+2250.0Constant
0.55f0.55Constant——后缀被剥掉
3u3.0Constant——不是整数值
0.5.50.5Constant——转换保留最长的合法前缀,静默丢弃其余部分
0x10报错:Unexpected token 'x10' in Graph expression.
1.0fx报错:Unexpected token 'fx' in Graph expression.
3ul报错:Unexpected token 'ul' in Graph expression.

词法细节:

  • 数字和 . 会被贪婪地吞进同一个记号。多个 . 是被接受的,而数值转换很宽容:它取最长的合法前缀, 把余下部分丢掉且不报诊断,所以 0.5.50.50..50
  • 最多消耗一个指数。第二个 e / E 会终止该记号并开始一个标识符。
  • 没有十六进制、二进制或八进制写法,也没有数字分隔符。
  • 前导 - 不属于字面量,它是一元负号运算符。

f F h H u U l L 这八个后缀会被消耗并排除在记号文本之外。它们没有任何语义效果——不改类型、不改宽度、 不改整数标记。只允许一个后缀字符,而且只有当它后面的字符既非字母数字也非 _ 时才被消耗。这就是 0.55f;1.0fx 的区别。

字符串字面量

字符串字面量在分词时解码转义,且永远不会求值成一个值。它只在参数处理器读取字面文本而非对其求值的 地方合法:UE.*Substrate.* 的命名参数、Output= / OutputName= 选择器,或 Path(…) 资产引用。

转义产生
\n换行
\r回车
\t制表符
\""
\\\
\ + 任意其他字符该字符本身,反斜杠丢弃
vec4 Scene = UE.SceneTexture(Id = "PostProcessInput0");   // 可以
float x    = "0.5";   // String literals can only be used in named UE builtin arguments.

未闭合的字符串字面量不会被诊断。词法器一直吞到输入结束,把收集到的内容作为字符串记号发出;错误随后 以缺少 ) 或语句被截断的形式浮现。

布尔字面量

truefalse (未发布) 按普通标识符词法处理,并且很晚才解析——排在 Graph 变量和已声明属性 之后。它们产生一个 1 分量的 UMaterialExpressionStaticBool 节点,匹配不区分大小写,也是从 Graph 块里 给 StaticSwitch 输入或 StaticBool 函数输入供值的唯一方式。

因为它们排在变量和属性之后解析,一个叫 TrueFalse 的 Graph 变量或已声明属性会静默遮蔽这个字面量。 请避免使用这些名字。

标识符解析

裸标识符按顺序在三个表面上查找:

顺序表面大小写结果
1已存在的 Graph 值——声明的、赋值的,或本作用域内已物化的参数不敏感存储的值
2已声明的属性或参数——先函数局部 Properties,再所在块的 Properties不敏感参数节点,首次读取时创建并按属性声明名缓存
3true / false不敏感一个 StaticBool 节点
4无匹配Unknown Graph identifier '{Name}'.

步骤 1 先比对精确拼写,再退化为不区分大小写扫描。步骤 2 是参数变成节点的地方:它在首次读取时惰性创建 并插入值表,之后每次读取都由步骤 1 服务。

StaticSwitchParameter 属性刻意作为裸标识符解析——它需要两个分支。按名字读取会失败并报 Unknown Graph identifier '{Name}'.,请改用调用形式。见 调用

Graph 变量查找不区分大小写,但值表是按赋值时使用的拼写建键的。float3 Color = …; 后面跟 color = …; 会写入第二个条目;之后的读取取精确匹配找到的那个,否则退回第一个不区分大小写命中项。 每个变量请只用一种拼写。

构造器

<constructor-call> := <constructor-name> ( <expression> [ , <expression> ] … )

34 个名字,全部不区分大小写。分量数完全由名字的最后一个字符决定:2 → 2,3 → 3, 4 → 4,其余 → 1。

名字分量数带整数标记家族
float1HLSL float
float11HLSL float
float22HLSL float
float33HLSL float
float44HLSL float
half1HLSL half——行为与 float 完全相同
half11HLSL half
half22HLSL half
half33HLSL half
half44HLSL half
vec22GLSL 浮点向量
vec33GLSL 浮点向量
vec44GLSL 浮点向量
int1HLSL 有符号整数
int22HLSL 有符号整数
int33HLSL 有符号整数
int44HLSL 有符号整数
ivec22GLSL 有符号整数向量
ivec33GLSL 有符号整数向量
ivec44GLSL 有符号整数向量
uint1HLSL 无符号整数
uint22HLSL 无符号整数
uint33HLSL 无符号整数
uint44HLSL 无符号整数
uvec22GLSL 无符号整数向量
uvec33GLSL 无符号整数向量
uvec44GLSL 无符号整数向量
bool1HLSL 布尔
bool22HLSL 布尔
bool33HLSL 布尔
bool44HLSL 布尔
bvec22GLSL 布尔向量
bvec33GLSL 布尔向量
bvec44GLSL 布尔向量

看起来该有、实际没有的名字:vec1ivec1uvec1bvec1(GLSL 家族从 2 开始);int1uint1bool1(只有 float1half11 的拼法);hvec2..4(没有 GLSL half 家族);doubledvec2..4float2x2float3x3float4x4mat2mat3mat4(本语言任何地方都没有矩阵 类型);以及已移除的别名 ScalarVectorColor。它们都按普通标识符词法处理,落到用户函数查找, 最后报 Unknown Graph function '{Name}'.

构造器名字会遮蔽一切。 构造器检测是调用解析的第一步,排在 UE.*Substrate.*、数学内置、参数和 所有用户声明之前。一个叫 float3intbool4vec2FunctionGraphFunctionShaderFunctionVirtualFunction 可以毫无诊断地声明出来,却永远无法被调用——每个调用点构建的都是 构造器。请改名。

参数规则

参数从左到右求值,并在任何元数检查之前先过筛。

#条件行为
1任一参数是命名参数,如 float3(x = 1.0)报错:Constructor '{Name}' does not accept named arguments.
2任一参数是纹理对象报错:Constructor '{Name}' cannot use Texture2D arguments.
3任一参数是 MaterialAttributes报错:Constructor '{Name}' cannot use MaterialAttributes arguments.
4任一参数是 Substrate报错:Constructor '{Name}' cannot use Substrate arguments.

然后,记构造器分量数为 N

#情况行为节点
5N == 1,恰好一个参数,且该参数正好 1 分量原样返回,再按名字设置整数标记
6N == 1,其他情况报错:Constructor '{Name}' expects a single scalar input.
7N > 1,恰好一个标量参数splat:复制 NN−1 个 AppendVector
8N > 1,恰好一个已经是 N 分量的参数原样返回
9N > 1,其他情况各参数分量数之和必须正好等于 NN−1 个 AppendVector
10N > 1,和 ≠ N报错:Constructor '{Name}' expects {N} total components but got {Total}.

规则 9 就是从左到右的通道拼接;只要总数精确,混合宽度完全没问题。

调用总数合法结果
float4(rgb, 1.0)3 + 1float4
float4(uv, uv)2 + 2float4
float3(x, yz)1 + 2float3
vec3(0.5)标量 splat(0.5, 0.5, 0.5)
float3(rgba)4Constructor 'float3' expects 3 total components but got 4.
float3(uv)2Constructor 'float3' expects 3 total components but got 2.
float(rgb)3Constructor 'float' expects a single scalar input.
float3()0Constructor 'float3' expects 3 total components but got 0.

构造器从不收窄,也从不补零。float3(rgba) 是错误而不是截断——请写 rgba.rgbfloat3(uv) 同样是 错误——请写 float3(uv, 0.0)。加宽只发生在标量上。收窄只发生在转换一节列出的那些位置。

常量折叠

N >= 2、名字不是整数构造器、且每个参数都是无名的数值字面量(可以带一个前导一元 +-)时, 整个调用会塌缩成一个常量向量节点。

写法折叠为
vec2(0.5, 1.0)Constant2Vector(0.5, 1.0)
vec3(0.5)Constant3Vector(0.5, 0.5, 0.5)——单个字面量先被复制到所有通道
float4(1.0, 2.0, 3.0, -1.0)Constant4Vector(1, 2, 3, -1)
int3(1, 2, 3)不折叠——整数构造器被排除在外,以保住整数标记
vec3(K, 0.0, 0.0)不折叠——K 不是字面量

折叠出来的节点是构造器结果中唯一带权威分量数的一种。折叠向量按值去重,所以 vec3(0.5) 写五次也只有 一个 Constant3Vector

Swizzle

<swizzle>  := <expression> . <channels>
<channels> := <channel> [ <channel> ] [ <channel> ] [ <channel> ]
<channel>  := { x | y | z | w | r | g | b | a }
字符通道索引
xXrR0
yYgG1
zZbB2
wWaA3

只存在两套字符集:xyzwrgba。没有 stpq,也没有 uv——uv 都无法解析,所以 .uv 会失败。 通道字符不区分大小写,而且在一个 swizzle 里混用两套字符集是被接受的.xg 就是通道 0 和 1, 和 .xy 完全一样。

长度与边界

规则违反时
一到四个通道字符5 个或更多:Unsupported swizzle '{Channels}'.
每个字符都必须解析出通道索引Swizzle '{Channels}' is invalid for a value with {Count} components.
每个通道索引都必须小于基值的分量数同一条信息

swizzle 只能收窄或重排,绝不能读到基值宽度之外。

基值Swizzle结果
float4.rgbfloat3
float3.a报错——通道 3 不小于 3
float2.xyz报错
float.x.r基值原样返回
float.xx.rrrsplat 成 float2 / float3
float.y.z.w.g.b.a报错

标量不会通过越界通道 splat。 对标量参数写 Roughness.yz 是硬错误,而不是双分量广播: Swizzle 'yz' is invalid for a value with 1 components. 要广播标量,请重复通道 0(Roughness.xx)、 使用构造器(float2(Roughness)),或者依赖赋值处的标量加宽。

降级策略

三种策略,按此顺序尝试。

#适用条件产生的节点结果
1基值有表达式节点通道索引严格递增无重复该选择被记录为通道掩码,在值接到引脚时应用
2基值 2–4 分量,且掩码有重排或重复N−1 个 AppendVector每个通道各自变成单通道掩码值,再拼接起来
3基值 1 分量且所有通道都在范围内N−1 个 AppendVector标量被复制 N 次
Swizzle通道策略节点开销
.r / .x010
.a310
.rg / .xy0,110
.rgb / .xyz0,1,210
.ga1,31——递增,允许跳跃0
.xg0,11——混用字符集仍然递增0
.rgba0,1,2,310
.gr1,021
.bgr2,1,02 since 1.3.32
.xxx0,0,022
.rrgg0,0,1,123
float 上的 .rr0,031

"严格递增"判断的是底层节点的源通道,而不是写出来的字母。

复合 swizzle

对已经带掩码的值再做 swizzle,会经由已有掩码重新映射,所以编号永远相对于被 swizzle 的那个值,而不是原始 节点。

写法含义
v.rgb.bv 的通道 2——.rgb 选出 0,1,2,再由 .b 取第 2 项
v.ga.rv 的通道 1——.ga 选出 1,3,再由 .r 取第 0 项
v.ga.gv 的通道 3
v.bgr.rv 的通道 2

只要结果仍然递增,复合就会折叠成单个掩码,所以 v.rgb.rg 完全不产生节点。选取外层列表里不存在的项, 是相对掩码后宽度的普通越界失败:v.ga.bSwizzle 'b' is invalid for a value with 2 components.

因为 . 是普通后缀运算符,任何调用结果都可以直接 swizzle,不需要临时变量:

float u  = UE.TexCoord().x;
vec3  c  = SampleTexture2D(Albedo, uv).rgb;
vec2  yx = vec4(1.0, 2.0, 3.0, 4.0).yx;
float m  = MyFunction(A, B).r;

不能 swizzle 的基值

基值行为
MaterialAttributes不是 swizzle——成员名按材质属性解析,生成一次 BreakMaterialAttributes 读取
纹理对象报错:Texture values do not support swizzle/member access in Code.
Substrate报错:Substrate values do not support swizzle/member access in Graph.

纹理那条信息写的是 "in Code",Substrate 那条写的是 "in Graph"。两者出自同一个构建器;措辞早于 CodeGraph 的 section 改名。

转换

转换不是作者写出来的。它在任何"值遇到期望形状"的地方自动应用,也是生成材质里大多数静默宽度变化的成因。

应用位置

#位置期望形状来自
1带初始值的声明——float3 c = A;声明的类型 token,除非权威逃生口生效
2赋给已有 Graph 变量——c = A;该变量已有值的形状
3赋给匹配某个 Outputs 声明的名字——Color = A;该输出的声明类型
4MaterialAttributes 成员写入——Attrs.BaseColor = A;该属性的分量数
5Function / GraphFunction / ShaderFunction / VirtualFunction 的输入参数该输入的声明类型
6if 分支合并分支前的值,否则 Outputs 声明,否则两个分支必须本来就一致
7二元运算符挽救权威操作数的分量数——只加宽
8Outputs 绑定表达式所绑定输出的声明类型

赋给一个既不是已有变量也不是 Outputs 声明的名字时,不做任何转换:新变量直接带上该值自身的形状。

规则顺序

#条件行为失败信息
1期望是 MaterialAttributes输入必须也是;原样通过Expected a MaterialAttributes value.
2期望是 Substrate输入必须也是;原样通过Expected a Substrate value.
3期望是纹理输入必须是纹理对象Expected a texture object value.
3a两边都是纹理维度必须完全相同Expected a texture object value with a matching texture type.
4输入是 MaterialAttributes,期望是数值拒绝MaterialAttributes values cannot be assigned to numeric outputs.
5输入是 Substrate,期望是数值拒绝Substrate values cannot be assigned to numeric outputs.
6输入是纹理对象,期望是数值拒绝Texture objects cannot be assigned to numeric outputs.
7分量数相等原样通过,所有标志位保留
8收窄——输入分量数大于期望应用前导顺序 swizzle(rrgrgb);静默,不产生节点
9从标量加宽——期望大于 1 且输入为 1AppendVector 复制
10其他情况拒绝Expected {Expected} component(s) but got {Actual}.

纵向是输入宽度,横向是期望宽度:

输入 \ 期望1234
1不变splat,1 个 AppendVectorsplat,2 个 AppendVectorsplat,3 个 AppendVector
2.r,无节点不变报错报错
3.r,无节点.rg,无节点不变报错
4.r,无节点.rg,无节点.rgb,无节点不变

跨种类的情况:

期望 \ 输入数值MaterialAttributesSubstrate纹理对象
MaterialAttributes报错通过报错报错
Substrate报错报错通过报错
Texture(T)报错报错报错维度为 T 时通过
Numeric(N)见上表报错报错报错

四种纹理维度是四种不同的期望形状。Texture2DSamplerState 都表示 Texture2DTexture3DVolumeTexture 都表示 VolumeTextureTextureCubeTexture2DArray 各自独立。TextureCube 永远 不会转换成 Texture2D

收窄是静默的。 没有警告,也没有节点开销,因为它是用一个位于连线上的前导顺序 swizzle 实现的。

vec4   Src = vec4(0.1, 0.2, 0.3, 0.4);
float3 Rgb = Src;      // 静默变成 Src.rgb —— A 通道被丢弃
float  R   = Src;      // 静默变成 Src.r

如果生成材质里少了某个通道,请检查所有右侧比目标更宽的赋值。显式写出 swizzle 能表明意图,行为完全相同。

收窄被刻意排除在以下位置之外:二元运算符的操作数(那里改为抛尺寸不匹配错误)、构造器参数、 swizzle 边界。

唯一的加宽规则是标量 splat。没有补零,也没有部分加宽:float3 v = SomeFloat2; 会报 Expected 3 component(s) but got 2. 请用构造器说明多出来的通道装什么。

float、int 与 bool

Graph 语言里根本没有数值表示层面的转换。

Token 家族对转换意味着什么
floatfloat1..4halfhalf1..4vec2..41 / 2 / 3 / 4 分量
intint2..4ivec2..4同样是 1 / 2 / 3 / 4 分量——不截断、不舍入
uintuint2..4uvec2..4同样——不做范围钳制、不处理符号
boolbool2..4bvec2..4同样——不归一化到 0 或 1
StaticBoolStaticBoolParameter1 分量;自身不带任何标记

所以 int x = 7.9; 存的就是 7.9——想截断请用 floor(…)bool b = 0.5; 存的就是 0.5。整数值与浮点值 之间唯一可观察的差别就是整数标记,而它唯一的作用就是整数除法的拒绝。

权威分量数

当一个值的宽度来自引擎而不是来自声明推断时,它就带有权威分量数

来源是否权威
常量折叠的构造器——vec3(0.5)float4(1,0,0,1)——这是构造器唯一能产生该标志的路径
节点类在下面已知宽度表里的 UE.* 内置
dot 数学内置,1 分量
其他数学内置继承——lerp/min/max 取两个值操作数的逻辑或,其余继承第一个操作数
裸数值字面量
任意宽度的已声明 Properties 参数
未折叠的构造器——vec3(K)float4(rgb, A)继承,取各参数的逻辑或
swizzle继承自基值
+ - * / 的结果继承,取两个操作数的逻辑或
被收窄或加宽后的值继承自输入
变量赋给它的值带什么就是什么

已知宽度的内置节点类:

分量数节点类
1PixelDepthTwoSidedSignArctangent2FastLengthMaterialXLuminance
2TextureCoordinatePannerScreenPositionRotatorSceneTexelSize
3WorldPositionObjectPositionWSCameraVectorWSVertexNormalWSVertexTangentWSTransformTransformPositionSkyAtmosphereLightDirectionPixelNormalWSCrossProduct

其他任何节点的输出宽度对生成器都是未知的。

这个标志会改变两件事。

作用 1——运算符挽救。 + - * / 遇到两个不兼容宽度时,可以把其中一个加宽到权威那一个,但只能往上加。 这就是 Tint 是 4 分量 VectorParameterUE.CameraVectorWS() * Tint 会失败的原因:内置是权威的 3, 参数是非权威的 4,而 4 大于 3,挽救被阻止,于是抛出尺寸错误。

作用 2——声明宽度可能被忽略。

当声明的初始值带有的权威分量数与声明类型的宽度不同时,该值被原样存储、不做转换、也不产生任何诊断。 声明宽度实际上被忽略了。

float2 dir = UE.CameraVectorWS();   // 存成 3 分量;float2 被忽略,不报错
Color = dir * Tint;                 // 如果 Tint 是 4 分量,错误在这里才出现

不匹配并没有丢失——它会在第一个真正在意宽度的使用处浮现,届时信息里给出的是真实宽度而不是声明宽度。 其理由是:静默截断一个引擎已知的宽度,比晚一步报错更糟糕。这个逃生口只对声明生效;赋给已有变量或 输出名字时总会做转换,因此即使值是权威的也会静默收窄。

作用 3——分支合并。 判断某个 if 分支是否改变了一个值时,权威标志和整数标志参与比较,所以只在 这两个标志上不同的两个值被视为相同,该名字不会被合并。

节点复用

在相同操作数值上的、文本相同的子表达式会塌缩成一个节点。A + B 写两次只产生一个 Add;一个资产里所有 0.5 都映射到一个 Constant;一元负号背后的 Constant(-1) 被资产内所有一元负号共享。缓存按值身份令牌 建键——节点、输出索引、分量数、全部五个掩码字段,以及纹理、属性、Substrate、权威和整数标志——所以 int(2)float(2) 永远不会撞车,而在两次文本相同的调用之间重新给变量赋值,会正确地产生两个节点。

F_Tint(a, b)F_Tint(b, a) 是不同的键,因为位置参数按下标建键;F_Tint(Color = a)F_Tint(color = a) 是同一个键,因为参数名会被规范化。哪些调用种类会去重,见 调用

诊断

消息触发原因处理
Unexpected token '{Text}' in Graph expression.在主表达式位置出现非主记号(!x、= b),或完整表达式之后残留了一个真实记号(a == b)。位于末尾的未知字符永远不会触发这条。详解
Expected token type {Code} in Graph expression near '{Text}'.需要 ) 或 , 却找不到——通常是括号内或参数表里出现了未知字符。
Expected member name after '.'.. 后面不是标识符。
Empty Graph expression.去空白后表达式文本为空。
Operator '{Op}' requires matching vector sizes or a scalar/vector pair, got {Left} and {Right} component(s).操作数宽度不同,都不是标量,且权威挽救不适用。显式 swizzle 较宽的那个操作数,或用构造器构建较窄的那个。
Integer division is not supported by the material graph; use float() or floor(a/b)./ 的两个操作数都来自整数构造器。
Arithmetic operators cannot be applied to texture values.纹理对象用作 + - * / 的操作数。
Arithmetic operators cannot be applied to MaterialAttributes values.MaterialAttributes 值用作操作数。
Arithmetic operators cannot be applied to Substrate values.Substrate 值用作操作数。
Expected {Expected} component(s) but got {Actual}.把 2 或 3 分量的值加宽到更宽的目标。用构造器:float3(v, 0.0)。
Constructor '{Name}' expects {N} total components but got {Total}.各参数宽度之和不等于构造器宽度。
Constructor '{Name}' expects a single scalar input.1 分量构造器传了零个参数、多个参数,或一个宽于 1 分量的参数。
Constructor '{Name}' does not accept named arguments.构造器调用里出现了 name = value 参数。
Swizzle '{Channels}' is invalid for a value with {Count} components.某个通道字符无法解析,或其索引不小于基值宽度。
Unsupported swizzle '{Channels}'.超过四个通道字符。
Unknown Graph identifier '{Name}'.既不是 Graph 变量、也不是已声明属性、也不是 true/false 的名字——包括按名字读取 StaticSwitchParameter。详解
String literals can only be used in named UE builtin arguments.字符串字面量被当作值求值。
Invalid numeric literal '{Text}'.转换结果为零、但记号里含有任何零的写法都不可能含有的字符——实际上就是下溢的指数,例如 1e-9999。

示例

Shader(Name="Docs/M_Expressions")
{
    Properties {
        vec3 A = vec3(1.0, 0.5, 0.2);
        vec3 B = vec3(0.1, 0.2, 0.3);
        float K = 2.0;
    }
    Settings { Domain = "UI"; ShadingModel = "Unlit"; }
    Outputs { vec3 Color; Base.EmissiveColor = Color; }
    Graph {
        vec3 Sum    = A + B;
        vec3 Diff   = A - B;
        vec3 Scaled = A * K;
        vec3 Ratio  = A / K;
        vec3 Neg    = -Scaled;
        Color = (Sum + Diff - Scaled + Ratio) * 0.25 + Neg;
    }
}
VectorParameter A, VectorParameter B, ScalarParameter K   (property nodes)
Add(A, B)                                        -> Sum
Subtract(A, B)                                   -> Diff
Multiply(A, K)                                   -> Scaled
Divide(A, K)                                     -> Ratio
Multiply(Scaled, Constant(-1))                   -> Neg
Add / Subtract / Add chain, Multiply(..., 0.25), Add(..., Neg)

参见

本页目录