DreamShaderLang
语言核心

Section

Properties、Inputs、Outputs、Results、Settings、Options、Graph 与 Layout —— 哪个块接受哪个、重复出现时怎么处理,以及 Group 分组作用域。

带属性的块,其函数体就是一串 section。一个 section 由名字、可选的 =,以及一对花括号里以 ; 分隔的 语句组成。

<section> := <section-name> [=] { <statement>… } [;]
记号含义示例
<x>占位符——替换成实际内容,尖括号本身不写出来。Name = <string>
[ x ]可选——整段可以整体省略。[, Root = <string>]
{ a | b }多选一——从竖线分隔的写法里取其中一个。{ Node( … ) | Comment( … ) }
可重复——前一项可以出现任意多次。<property-declaration> …

Section 名按大小写不敏感匹配,可以任意顺序出现,也可以重复。= 是可选的语法糖 since 1.5.0,闭合 } 之后的 ; 也是可选的。下面四种写法是同一个 section:

Properties = { float A = 1.0; }
Properties   { float A = 1.0; }
properties = { float A = 1.0; };
PROPERTIES   { float A = 1.0 }

支持矩阵

SectionShaderShaderFunctionShaderLayer / BlendVirtualFunction
Properties参数节点参数节点参数节点Inputs 的别名
Inputs未知 section引脚声明引脚声明,受 arity 约束引脚声明
Outputs声明 + 绑定引脚声明引脚声明,受 arity 约束引脚声明
Results未知 sectionOutputs 的别名Outputs 的别名Outputs 的别名
Settings材质设置四个函数键四个函数键Options 的别名
Options未知 section未知 section未知 sectionAsset 引用
Graph必需¹必需必需硬错误
Layout支持支持支持未知 section
Code硬错误硬错误硬错误硬错误
  1. 除非至少有一条输出声明带初始化式 since 1.3.4

FunctionGraphFunction 完全没有 section —— 它们的 { … } 是原始 HLSL。

重复出现时的行为

重复的 section 并不总是追加。搞错这一点会静默丢语句。

Section重复时
PropertiesInputsOutputsResults追加到之前的列表
SettingsOptions合并;后写的键胜出
Graph覆盖之前的函数体
Layout重置 —— 第二个 Layout 会把第一个整个丢掉

Layout 是唯一会扔掉前一个自己的 section。一个函数体里只有最后一个 Layout 块有效。见 Layout 与 #Region

Properties

声明一个块生成到它自己的图里的参数节点、常量节点和 UE.* 内置节点。

property-declaration := [ const ] <type-token> <name> [ = <default> ] [ [ <metadata> ] ] ;

group-scope          := Group( "<group-name>" ) { { <property-declaration> | <group-scope> }… } [ ; ]

<metadata> 外面最内层的那对 [ … ]字面的 DreamShaderLang 标点,外面那对是"可选"的元语法。 所以带元数据的声明写起来是 float Roughness = 0.5 [Group="Surface"];

Properties = {
    const float DebugScale = 1.0;                 // UMaterialExpressionConstant
    float Strength         = 1.0;                 // UMaterialExpressionScalarParameter
    vec3  Tint             = vec3(1.0, 1.0, 1.0); // UMaterialExpressionVectorParameter
    UE.TexCoord(Index = 0) UV;                    // 一个内置节点,不是参数
}

一条语句按固定顺序拆解,正是这个顺序让含空格和括号的类型 token 能工作:

步骤操作后果
1从末尾剥掉尾随的 [ … ]语句必须以 ] 结尾,元数据才会被看见
2()[]"…" 之外的第一个 = 处切分有没有这个 = 是"有默认值"的唯一判据
3左半边在最后一个顶层空白处切分前面是类型,后面是名字
4从类型 token 前端剥掉 constconst 是在类型 / 名字切分之后才被识别的

因为第 3 步感知括号且接受任意空白,UE.TexCoord(Index = 0) UV; 会切成类型 UE.TexCoord(Index = 0) 和名字 UV

属性名只检查非空,不按标识符校验。Properties { float 1Bad = 0; } 解析通过、没有诊断;这条声明 只是永远无法从 Graph 引用。相比之下 Inputs / Outputs 的名字必须符合 [A-Za-z_][A-Za-z0-9_]*

属性节点是惰性创建的,在 Graph 第一次引用时才建 since 1.3.2Graph 从没提过的属性 根本不会产生节点,所以声明顺序不影响名字解析 —— 它只影响生成节点的纵向排列和下面那个自动排序计数器。

类型 token、const、默认值和元数据块见 Properties 类型元数据与分组

Group("Name") { … } 作用域

since 1.5.0

Group 作用域把自己的名字盖到内部每一条声明上,省得在每条声明的元数据里重复写同一个分组。

Properties {
    Group("Surface") {
        ScalarParameter Roughness = 0.5 [Slider(0, 1)];
        VectorParameter BaseColor = float4(1, 1, 1, 1);
    }
}
规则细节
关键字Group,大小写不敏感
参数一对配平的 ( … ),其内部文本去空白后必须以 " 开头;名字随后被反引号处理,且必须非空
函数体{ … };遍历器同时感知花括号、圆括号、方括号和字符串
终止符闭合 } 之后紧跟的单个 ; 会被静默吞掉

Group(…)Properties唯一可以打开 { 的构造。其他任何 { 都会失败于 Unexpected '{' in Properties near '{Statement}'. Only Group("Name") { ... } may open a brace here.

作用域可以嵌套任意深度,嵌套作用域的有效名字是外层名字、一个 |、内层名字 —— 这正是 Unreal 自己的 子分类写法:

Properties {
    Group("Surface") {
        float A = 0;                    // Group = "Surface"
        Group("Detail") {
            float B = 0;                // Group = "Surface|Detail"
            Group("Micro") {
                float C = 0;            // Group = "Surface|Detail|Micro"
            }
        }
    }
}

声明上写的显式键总是胜出。只有当成员的元数据里既没写 Group 也没写 Category 时,继承来的分组才会 生效:

Group("Surface") {
    float A = 0;                        // Group = "Surface"    (继承)
    float B = 0 [Group="Override"];     // Group = "Override"   (显式胜出)
    float C = 0 [Category="Other"];     // Group = "Other"      (Category 是别名)
}

自动 SortPriority

分组作用域里的成员按声明顺序自动编号。

规则取值
计数器起点0
计数器步长10
计数器范围同一个 Properties section 内所有分组共用一个计数器,不是每组一个
显式 SortPriority / Sort胜出,并且不占用槽位
未分组(顶层)的声明既不自动编号,也不会被赋予分组
Properties {
    Group("Surface") {
        ScalarParameter A = 0.5;                    // SortPriority = 0
        VectorParameter B = float4(1, 1, 1, 1);     // SortPriority = 10
    }
    Group("Detail") {
        ScalarParameter C = 1.0 [SortPriority=99];  // SortPriority = 99,不占槽位
        ScalarParameter D = 2.0;                    // SortPriority = 20(计数器接着走)
    }
    ScalarParameter Loose = 3.0;                    // 无分组,不自动排序
}

计数器是Properties section 播种一次的。一个块声明了两次 Properties,第二个 section 会拿到 一个从 0 重新开始的新计数器,于是两个 section 里的两个分组可能撞上相同的排序值。

当分组作用域和显式元数据都没提供值时,根本不会写入 SortPriority,生成的节点保留它自己的类默认值。

InputsOutputsResults

类型化参数 section。它们声明生成的材质函数的引脚,或者 VirtualFunction 所描述的接口。

parameter-declaration := [ opt ] <type> <name> [ = <default-expression> ] [ [ <metadata> ] ] ;

ResultsOutputs 的纯同义词 —— 它追加进同一个列表,没有任何警告。

成员必需说明
opt since 1.2.3在 Unreal 中把输入标记为可选(bUsePreviewValueAsDefault)。
<type>类型与值
<name>必须符合 [A-Za-z_][A-Za-z0-9_]*,会成为引脚名。
= <default>预览值或预览图。对函数的输出无意义。
[ <metadata> ]只有 Description / Desc / TooltipSortPriority / Sort 有效。

空白规则与 Properties 不同

PropertiesInputs / Outputs / Results
类型 / 名字切分点最后一个顶层空白,感知括号最后一个字面空格
用制表符分隔接受接受
类型 token 可含 ( … )可以不可以
名字按标识符校验

opt 被识别为字面三个字母加一个空格opt<TAB>float Strength; 解析时没有任何诊断,却产出一个 必需输入,其类型 token 是 opt + 制表符 + 真正的类型,随后在生成阶段失败于 {Kind} '{Function}' input '{Name}' uses unsupported type '{Type}'.

inout 在这些 section 里不是限定符。它们只存在于 Function / GraphFunction 的签名形式里,那是另一套语法。写 Inputs = { in float X; } 会切成类型 in float 和名字 X,生成阶段报 uses unsupported type 'in float'

输入默认值

情况行为
类型是普通标量 / 向量默认值能解析为数字字面量直接写进 PreviewValue
其他任何情况作为图表达式求值,接到输入的 Preview 引脚上

正是这条图表达式路径,让预览默认值可以引用块自身生成的节点 since 1.2.6

ShaderFunction(Name="Functions/F_Sample")
{
    Properties = {
        const Texture2D PreviewTex = Path(Engine, "EngineResources/DefaultTexture");
    }
    Inputs = {
        opt Texture2D Tex = PreviewTex;      // 预览图,不是字面量
        opt float     Mix = 0.5;             // 字面量 → PreviewValue
    }
    Outputs = { vec4 OutColor; }
    Graph   = { OutColor = Tex(Coordinates = UE.TexCoord(Index = 0)) * Mix; }
}

把引脚标记为可选的只有 opt 一个。非 opt 输入上的默认值仍然会构建预览图,但引脚仍然是必需的。

材质函数 Outputs / Results 条目上的 = <expression> 会被解析然后忽略。函数输出只可能由 Graph 驱动。这一条不适用于 ShaderOutputs,那里的初始化式是有意义的 since 1.3.4

PropertiesInputs 的区别

两者都往生成的函数里放东西,但它们是产出不同节点的不同语法。

PropertiesInputs
产出函数图内部的参数、常量或 UE.* 节点函数接口FunctionInput 引脚
对调用方可见性作为材质参数出现在任何使用该函数的材质上作为可连线的输入引脚
名字校验只要非空必须符合标识符规则
类型 / 名字分隔符任意空白只能是字面空格

属性名在块内必须唯一,且不能和输入名冲突。两项检查都忽略大小写,共用一条诊断: {Kind} '{Function}' property '{Name}' conflicts with another property or input name.

Shader 里的 Outputs

ShaderOutputs 用一段函数体填两个列表,逐条语句独立分类。

语句形态归类为
没有顶层 =裸的输出变量声明
有顶层 =,左半边是合法的类型化声明带初始化式的输出声明 since 1.3.4
有顶层 =,左半边不是合法的类型化声明输出绑定
Outputs = {
    vec3  Color;
    float Alpha;

    Base.EmissiveColor = Color;
    Base.Opacity       = Alpha;
}
  • 声明和绑定可以自由交错;绑定可以引用同一 section 中后面才声明的变量。
  • 名字 return 是保留的。它不能被声明,作为绑定源时只能连到 Base.* 目标 —— 而且在有 Graph 块的 Shader 里根本不能用。
  • ShaderOutputs 语句上不接受 [ … ] 元数据块。那里根本不会调用元数据解析器,所以方括号块 会留在语句文本里,产出 Invalid typed declarationInvalid output binding 错误。

完整的 Base.* 目录和 Expression( … ).Pin[i] 形式见 输出绑定

SettingsOptions

两者共用一套语句语法:<Key> = <Value> ;,在 ()[]"…" 之外的第一个 = 处切分。键被去空白并 转成小写;值会被剥掉一对外围 "…";重复的键静默覆盖先前的。

Settings 的含义
Shader材质设置 —— 特殊键加上反射写入的 UMaterial 属性。见 材质 Settings
ShaderFunctionShaderLayerShaderLayerBlend只有四个键有效:DescriptionUserExposedCaptionExposeToLibraryLibraryCategories。其他键会被解析、存储,然后静默忽略。
VirtualFunctionOptions 的别名,唯一被消费的键是 Asset

那四个材质函数键在缺省时会被重置,所以从源码里删掉一个键就等于从资产上删掉它。LibraryCategories 按逗号切分,每项去空白,空项丢弃。

Graph

节点图的函数体。声明解析器不看它内部 —— 它把 Graph = { 和配对 } 之间的文本原样存下来,在生成阶段 交给另一套表达式语法。它里面的注释不会被剥掉,而 #Region 指令只在这里被识别。

Graph = {
    vec2 UV  = UE.TexCoord(Index = 0);
    vec4 Tex = BaseTex(Coordinates = UV);
    Color = Tex.rgb * Tint;
}

语句和表达式语言见 Graph 求值模型

Layout

固定生成节点的位置并声明注释框。见 Layout 与 #Region

诊断

消息触发原因处理
Invalid property declaration '{Statement}'.Properties 的类型 token 和名字之间没有顶层空白。
Missing property name in declaration '{Statement}'.切分后名字一侧为空。
Missing property type after const in declaration '{Statement}'.const 后面什么都没有。
Unsupported property type '{Type}'.这个 token 既不是紧凑 token,也不是参数节点 token,也没有 UE. 前缀。详解
Metadata must follow a declaration.整条语句只有一个 [ … ] 块。
Unexpected '{' in Properties near '{Statement}'. Only Group("Name") { ... } may open a brace here.Properties 里出现了不是 Group 作用域头的 {。
Group(...) requires a non-empty name.写了 Group(""),或参数不以引号开头。
Unterminated Group("{Name}") { ... } block.作用域的 { 从未闭合。
Invalid typed declaration '{Statement}'.Inputs / Outputs / Results 里类型和名字之间没有字面空格、某一侧为空,或名字不是标识符。用空格,不要用制表符。 详解
Invalid setting declaration '{Statement}'.Settings 或 Options 语句没有顶层 =。
Invalid empty setting key in '{Statement}'.Settings 或 Options 语句的键一侧为空。
Metadata entry '{Entry}' must use Key=Value syntax.元数据条目没有顶层 =,且不是 Slider(…)。详解
Metadata key '{Key}' is declared more than once.规范化之后出现重复的元数据键。
Metadata SortPriority value '{Value}' is not an integer.排序优先级不是整数。
{Kind} '{Function}' property '{Name}' conflicts with another property or input name.属性名重复,或属性遮蔽了输入名。两项都忽略大小写比较。
{File}: Property '{Name}' is declared more than once. Property names must be unique.两个 Shader 属性名在忽略大小写后相同。

继续阅读

本页目录