DreamShaderLang
示例

完整示例

可直接编译的完整 DreamShaderLang 材质 —— 动画着色、UV 平移、分支、静态开关变体、半透明 UI、PBR、MaterialAttributes、Substrate、参数集合、多输出 helper、Settings 一览与 Runtime Virtual Texture,每个都有 .dsm 和 .dss 两种写法。

下面每个配方都是一个能独立编译的完整 .dsm,后面跟着同一个材质的 .dss 写法。 它们按"参数、设置、输出、图"的顺序写,方便从上往下读。凡是有坑的地方, 都直接写在代码旁边,而不是留给你自己踩。

适用于DreamShaderLang 1.x(.dsm),由 2.0 编译器经 legacy 前端构建(自 2.0.0 起);.dss 写法是 DreamShaderLang 2.0
引擎UE 5.3 – 5.8;Substrate 那个配方需要(自 UE 5.4 起)
假定源码根目录<Project>/DShader
生成产物Graph backend 的材质每次构建都存盘;ThinCustom(默认)材质只在内存里 —— 见内存材质

一个 .dss 和同名的 .dsm 构建出同一个资产路径,两者留一个。诊断写码, 读法见错误速查。想看这些材质是由哪些语言构件拼出来的,见 常用写法。

动画着色

UE.Time() 给出一个标量时钟;sin 是数学内置之一,所以裸调用、 按位置传参。

// DShader/Materials/M_Pulse.dsm
Shader(Name="Materials/M_Pulse")
{
    Properties = {
        vec3            Tint  = vec3(1.0, 0.4, 0.1);
        ScalarParameter Speed = 2.0 [Slider(0, 10)];
    }

    Settings = {
        Domain       = "UI";
        ShadingModel = "Unlit";
    }

    Outputs = {
        vec3 Color;
        Base.EmissiveColor = Color;
    }

    Graph = {
        float t     = UE.Time();
        float pulse = sin(t * Speed) * 0.5 + 0.5;
        Color = Tint * pulse;
    }
}
  • sin 建出一个 Sine 节点,其 Period 保持默认值 1,所以它按周期计、不按弧度:Speed = 2.0 就是每秒两次 脉冲。常量折叠也按周期 (自 2.1.2 起)。要别的周期,直接调用节点: UE.Sine(Input = x, Period = 2.0)。
  • 两个相同的调用就是一个节点,不论是数学内置还是 UE.* (自 2.0.0 起):写两次 UE.Time() 只会有 一个 Time 节点。

写成 .dss

// DShader/Materials/M_Pulse.dss
#pragma material(Domain = UI, ShadingModel = Unlit)

uniform float3 Tint = float3(1.0, 0.4, 0.1);
/// @slider 0 10
uniform float Speed = 2.0;

export void M_Pulse(inout material m)
{
    float t = UE.Time();
    float pulse = sin(t * Speed) * 0.5 + 0.5;
    m.EmissiveColor = Tint * pulse;
}

UV 平移

// DShader/Materials/M_Panned.dsm
Shader(Name="Materials/M_Panned")
{
    Properties = {
        TextureSampleParameter2D BaseTex  = Path(Engine, "EngineResources/WhiteSquareTexture");
        ScalarParameter          PanSpeed = 0.1 [Slider(-1, 1)];
    }

    Settings = {
        Domain       = "UI";
        ShadingModel = "Unlit";
    }

    Outputs = {
        vec3 Color;
        Base.EmissiveColor = Color;
    }

    Graph = {
        vec2 uv    = UE.TexCoord(Index = 0);
        vec2 moved = UE.Panner(Coordinate = uv, Time = UE.Time(), Speed = vec2(PanSpeed, 0.0));
        vec4 texel = BaseTex(Coordinates = moved);

        Color = texel.rgb;
    }
}

提示:

UE.Panner 的参数分两类。Coordinate、Time 和 Speed 是输入 pin,接受表达式 —— 所以上面能用 PanSpeed 驱动平移。而 SpeedX、SpeedY 和 FractionalPart 是节点上的字面量属性, 只接常量:SpeedX = PanSpeed 是 DSH4373。拼错的参数名在 1.x 源文件里被丢掉并给出 warning DSH5254,在 .dss 里是错误 DSH5213。见 UE.Panner。

写成 .dss

// DShader/Materials/M_Panned.dss
#pragma material(Domain = UI, ShadingModel = Unlit)

/// @default /Engine/EngineResources/WhiteSquareTexture
uniform Texture2D BaseTex;
/// @slider -1 1
uniform float PanSpeed = 0.1;

export void M_Panned(inout material m)
{
    float2 uv = UE.TexCoord(Index = 0);
    float2 moved = UE.Panner(Coordinate = uv, Time = UE.Time(), Speed = float2(PanSpeed, 0.0));
    m.EmissiveColor = BaseTex.Sample(moved).rgb;
}

.Sample 建出一个 TextureObjectParameter 和一个 TextureSample,而 1.x 的 token 建的是一个 TextureSampleParameter2D;材质看到的结果相同。

按阈值分支

两个分支都会被建进图里,每个被改动的变量一个条件节点 —— UMaterialExpressionIf,或者条件是 static bool 时的 StaticSwitch —— 在运行时二选一。条件必须带括号,每个分支体必须带花括号。

// DShader/Materials/M_Branch.dsm
Shader(Name="Materials/M_Branch")
{
    Properties = {
        ScalarParameter Threshold = 0.5  [Group="Surface"];
        VectorParameter Lit       = float4(1.0, 0.85, 0.2, 1.0) [Group="Surface"];
        VectorParameter Dark      = float4(0.05, 0.05, 0.1, 1.0) [Group="Surface"];
    }

    Settings = {
        Domain       = "Surface";
        ShadingModel = "Unlit";
        BlendMode    = "Opaque";
    }

    Outputs = {
        vec3 Color;
        Base.EmissiveColor = Color;
    }

    Graph = {
        float2 uv   = UE.TexCoord(Index = 0);
        float  mask = uv.x;

        if (mask > Threshold) {
            Color = Lit.rgb;
        } else if (mask > Threshold * 0.5) {
            Color = Lit.rgb * 0.5;
        } else {
            Color = Dark.rgb;
        }
    }
}
条件含义
a > b、a < b、a >= b、a <= b、a == b、a != b六个比较运算符
if (x)真值判断 —— 等同于 x != 0,所以负值走 then 分支
  • 条件比较的是两个数:比较两个向量是 DSH4260,不是数字的操作数是 DSH4226。
  • 在 if 之前声明、只在一个分支里改过的变量,和它在 if 之前的值合并。 if 之前没有值、又只在一个分支里赋值的变量,在 if 之后被读取的地方报 DSH4372。
  • 在分支里声明的变量只属于那个分支 (自 2.0.0 起):它不参与合并,if 之后它的名字是 未知的。
  • 每次赋值都会把值转换成变量声明的类型,所以同一个变量的两个分支值 总是一致的。
  • 在分支里读参数没问题 —— 参数永远不是分支输出。
  • if 区分大小写:If (x) { … } 是对一个叫 If 的函数的调用,后面的 { 报 DSH2154。

警告:

1.x 条件里的 && 和 || 被拒绝 (自 2.0.0 起):DSH2210。1.x 会一声不吭地丢掉第一个 比较之后的一切,所以 if (a > 0 && b > 0) 测的是 a > 0。请改成两层嵌套的 if。1.x 会截断的其他写法 —— %、?:、&、|、^、<<、v[i] —— 在表达式的任何位置都是错误 (DSH2200–DSH2202);见不支持的写法。.dss 里这些运算符都有。

写成 .dss

// DShader/Materials/M_Branch.dss
#pragma material(Domain = Surface, ShadingModel = Unlit, BlendMode = Opaque)

/// @group Surface
uniform float Threshold = 0.5;
/// @group Surface
uniform float4 Lit = float4(1.0, 0.85, 0.2, 1.0);
/// @group Surface
uniform float4 Dark = float4(0.05, 0.05, 0.1, 1.0);

export void M_Branch(inout material m)
{
    float2 uv = UE.TexCoord(Index = 0);
    float mask = uv.x;

    float3 Color = float3(0, 0, 0);
    if (mask > Threshold)
    {
        Color = Lit.rgb;
    }
    else if (mask > Threshold * 0.5)
    {
        Color = Lit.rgb * 0.5;
    }
    else
    {
        Color = Dark.rgb;
    }
    m.EmissiveColor = Color;
}

分支赋值的变量在 if 之前声明,正如 1.x 的 Outputs 声明那样。

静态开关变体

StaticSwitchParameter 不是运行时分支 —— 它产出的是不同的 permutation。在 1.x 里,和 if 不同,它写成调用。

// DShader/Materials/M_Variant.dsm
Shader(Name="Materials/M_Variant")
{
    Properties = {
        Group("Surface") {
            VectorParameter BaseColor   = float4(0.1, 0.2, 0.3, 1.0);
            VectorParameter DetailColor = float4(1.0, 0.8, 0.3, 1.0);
        }

        Group("Switches") {
            StaticSwitchParameter UseDetail = true [Description="Use the detail colour"];
        }
    }

    Settings = {
        Domain       = "Surface";
        ShadingModel = "DefaultLit";
        BlendMode    = "Opaque";
    }

    Outputs = {
        float3 Color;
        Base.BaseColor = Color;
    }

    Graph = {
        Color = UseDetail(True = DetailColor.rgb, False = BaseColor.rgb);
    }
}

True= / False=、A= / B= 和位置形式都可以;缺一边的调用是 DSH5258。不行的是裸写 Color = UseDetail; 或 if (UseDetail) { … } —— 两者都报 DSH4200,因为 1.x 的开关参数不是值。见 StaticSwitchParameter。

写成 .dss

// DShader/Materials/M_Variant.dss
#pragma material(Domain = Surface, ShadingModel = DefaultLit, BlendMode = Opaque)

/// @group Surface
uniform float4 BaseColor = float4(0.1, 0.2, 0.3, 1.0);
/// @group Surface
uniform float4 DetailColor = float4(1.0, 0.8, 0.3, 1.0);

/// @static
/// @group Switches   @desc Use the detail colour
uniform bool UseDetail = true;

export void M_Variant(inout material m)
{
    float3 Color = BaseColor.rgb;
    if (UseDetail)
    {
        Color = DetailColor.rgb;
    }
    m.BaseColor = Color;
}

在 .dss 里,/// @static 的 uniform bool 就是一个值:被 if 读取,它的合并处是一个 StaticSwitch,而该参数是真正的静态开关参数。

半透明 UI 材质

// DShader/Materials/M_UI_Glass.dsm
Shader(Name="Materials/M_UI_Glass")
{
    Properties = {
        vec3            Tint    = vec3(1.0, 1.0, 1.0);
        ScalarParameter Opacity = 0.75 [Slider(0, 1)];
    }

    Settings = {
        Domain       = "UI";
        ShadingModel = "Unlit";
        BlendMode    = "Translucent";
    }

    Outputs = {
        vec3  Color;
        float Alpha;

        Base.EmissiveColor = Color;
        Base.Opacity       = Alpha;
    }

    Graph = {
        Color = Tint;
        Alpha = Opacity;
    }
}

Domain = "UI" 加 ShadingModel = "Unlit" 是 UMG 想要的组合;BlendMode 是让 Base.Opacity 真正起作用的关键。所有可接受写法见枚举取值。

写成 .dss

// DShader/Materials/M_UI_Glass.dss
#pragma material(Domain = UI, ShadingModel = Unlit, BlendMode = Translucent)

uniform float3 Tint = float3(1.0, 1.0, 1.0);
/// @slider 0 1
uniform float Opacity = 0.75;

export void M_UI_Glass(inout material m)
{
    m.EmissiveColor = Tint;
    m.Opacity = Opacity;
}

Surface PBR 骨架

// DShader/Materials/M_Surface.dsm
Shader(Name="Materials/M_Surface")
{
    Properties = {
        Group("Surface") {
            VectorParameter Albedo    = float4(0.8, 0.6, 0.4, 1.0);
            ScalarParameter Roughness = 0.5 [Slider(0, 1)];
            ScalarParameter Metallic  = 0.0 [Slider(0, 1)];
        }
    }

    Settings = {
        Domain       = "Surface";
        ShadingModel = "DefaultLit";
        BlendMode    = "Opaque";
    }

    Outputs = {
        vec3  Color;
        float Rough;
        float Metal;

        Base.BaseColor = Color;
        Base.Roughness = Rough;
        Base.Metallic  = Metal;
    }

    Graph = {
        Color = Albedo.rgb;
        Rough = Roughness;
        Metal = Metallic;
    }
}

输出变量和绑定目标处在不同的命名空间里,但把它们写得明显不同(Color → Base.BaseColor)能让文件更好读。完整的目标清单见输出绑定;目标名区分大小写,拼错是 DSH5200。

写成 .dss

// DShader/Materials/M_Surface.dss
#pragma material(Domain = Surface, ShadingModel = DefaultLit, BlendMode = Opaque)

/// @group Surface
uniform float4 Albedo = float4(0.8, 0.6, 0.4, 1.0);
/// @group Surface   @slider 0 1
uniform float Roughness = 0.5;
/// @group Surface   @slider 0 1
uniform float Metallic = 0.0;

export void M_Surface(inout material m)
{
    m.BaseColor = Albedo.rgb;
    m.Roughness = Roughness;
    m.Metallic  = Metallic;
}

写 MaterialAttributes

绑定 Base.MaterialAttributes 会为生成的材质打开 Use Material Attributes。MaterialAttributes Attrs; 是一个空的属性集 —— 一个 MakeMaterialAttributes 节点 —— 它的成员按名字写;在值被使用的地方,这些写入合成一个 SetMaterialAttributes 节点(自 2.0.0 起)(1.x 每次写入串一个)。

// DShader/Materials/M_Attrs.dsm
Shader(Name="Materials/M_Attrs")
{
    Properties = {
        vec3  BaseTint = vec3(0.6, 0.8, 1.0);
        float R        = 0.35;
    }

    Settings = {
        Domain       = "Surface";
        ShadingModel = "DefaultLit";
        BlendMode    = "Opaque";
    }

    Outputs = {
        Base.MaterialAttributes = Attrs;
    }

    Graph = {
        MaterialAttributes Attrs;

        Attrs.BaseColor = BaseTint;
        Attrs.Roughness = R;
        Attrs.Metallic  = 0.0;

        // A member already written reads back as the value written; no node is made.
        float Echo = Attrs.Roughness;
        Attrs.Specular = Echo;
    }
}

注意:

Outputs 的绑定在 Graph 之后应用,所以绑定拿到的是上面在 Graph 里声明的 Attrs。(自 2.0.0 起) Outputs 里的声明 MaterialAttributes Attrs; 也是那个变量,从空开始。MaterialAttributes 是 Outputs、Inputs、Graph 变量和函数结果的类型;作为 Properties 类型它是 DSH3252,作为 Function 的输入是 DSH6210。

  • 成员名就是运行中引擎的材质属性名(BaseColor、Metallic、Specular、Roughness、EmissiveColor、Opacity、Normal……);引擎没有的是 DSH5200。
  • 对 MaterialAttributes 值用算术运算符是 DSH4226。
  • 一个 Shader 里同时用 Base.MaterialAttributes 和 Base.FrontMaterial 不再被拒绝(自 2.0.0 起):两者都会接上,Use Material Attributes 让材质读取属性集。

写成 .dss

// DShader/Materials/M_Attrs.dss
#pragma material(Domain = Surface, ShadingModel = DefaultLit, BlendMode = Opaque)

uniform float3 BaseTint = float3(0.6, 0.8, 1.0);
uniform float  R        = 0.35;

export void M_Attrs(inout material m)
{
    m.BaseColor = BaseTint;
    m.Roughness = R;
    m.Metallic  = 0.0;
    m.Specular  = m.Roughness;
}

该入口写入 m 的成员,它们直接连接到材质的引脚:不会生成 MakeMaterialAttributes 节点。成员只有先写入才能读回(否则 DSH4370)。

Substrate 表面

(自 UE 5.4 起)

Substrate.* 构建 Substrate 节点;Base.FrontMaterial 是消费它们的绑定。

// DShader/Materials/M_Substrate.dsm
Shader(Name="Materials/M_Substrate")
{
    Properties = {
        vec3 Color = vec3(0.1, 0.6, 1.0);
    }

    Outputs = {
        Substrate Surface;
        Base.FrontMaterial = Surface;
    }

    Graph = {
        Surface = Substrate.Unlit(EmissiveColor = Color);
    }
}
  • Substrate 需要 UE 5.4 或更新版本:节点目录列出运行中引擎的 Substrate 类,更旧的引擎一个都没有。在那种引擎上,Substrate.* 调用是 DSH5210,Base.FrontMaterial 是 DSH5200;Substrate.Select 需要 UE 5.6(DSH5294)。项目还必须启用 Substrate,引擎才能编译该图 —— 见 Substrate 语法糖。
  • DreamShader 不为 Base.FrontMaterial 设置着色模型(自 2.0.0 起)(1.x 强制设为 Substrate 并拒绝任何其他 ShadingModel):ShadingModel 设置会按所写的内容应用。
  • BSDF 节点按名称接收参数;五个组合节点(Add、Weight、Mix、Layer、Select)也接受按位置传参(自 2.0.0 起)。其他位置参数是 DSH5220,Class= 是 DSH5216:名称本身就是类。
  • GraphFunction 函数体中的 Substrate.* 调用不会被提升为节点 —— Custom 节点的输入无法携带 Substrate 值 —— 并会给出警告 DSH6316(自 2.0.0 起)。
  • (自 2.0.0 起)Substrate 值可以组合:A + B、A * w 和 lerp(A, B, t) 分别是 Substrate.Add、Substrate.Weight 和 Substrate.HorizontalMix,而两个 Substrate 值之间的 if 在静态条件下是 StaticSwitch,否则是 Substrate.Select。任何其他运算符是 DSH5293,对 Substrate 值读取成员是 DSH4207。

写成 .dss

// DShader/Materials/M_Substrate.dss
uniform float3 Color = float3(0.1, 0.6, 1.0);

export void M_Substrate(inout material m)
{
    m.FrontMaterial = Substrate.Unlit(EmissiveColor = Color);
}

用参数集合驱动材质

// DShader/Materials/M_Wind.dsm
Shader(Name="Materials/M_Wind")
{
    Properties = {
        vec3 Tint = vec3(0.4, 0.7, 0.3);
    }

    Settings = {
        Domain       = "Surface";
        ShadingModel = "Unlit";
    }

    Outputs = {
        vec3 Color;
        Base.EmissiveColor = Color;
    }

    Graph = {
        float wind = UE.CollectionParam(
            Collection = Path(Game, "Collections/MPC_Wind"),
            Parameter  = "WindStrength");

        Color = Tint * wind;
    }
}
  • Collection(别名 Asset)和 Parameter(别名 ParameterName)都是必填。UE.CollectionParameter 是引擎的名字,和 UE.CollectionParam 是同一个类(自 2.1.1 起)(2.0.0 – 2.1.0 在 Graph 中拒绝 UE.CollectionParam,并给出 DSH5210)。
  • 集合资产在建节点时加载,参数按名字在里面查找:集合解析不了或加载不了是 DSH8213,名字既不是标量参数也不是向量参数是 DSH8254。
  • 引擎没给这个节点的输出定类型,所以值和它被读入的位置一样宽 (自 2.0.0 起):这里读进一个 float。
  • Group 和 SortPriority 只在运行中引擎的类有它们时才接受((自 UE 5.7 起));类 没有时 Group 是 DSH5213,SortPriority 建节点时以 DSH8212 失败。见 UE.CollectionParam。

写成 .dss

// DShader/Materials/M_Wind.dss
#pragma material(Domain = Surface, ShadingModel = Unlit)

uniform float3 Tint = float3(0.4, 0.7, 0.3);

export void M_Wind(inout material m)
{
    float wind = UE.CollectionParameter(Collection = "/Game/Collections/MPC_Wind",
                                        ParameterName = "WindStrength");
    m.EmissiveColor = Tint * wind;
}

资产属性接一个带引号的对象路径;ParameterName 是引擎上那个属性的名字。

多输出 helper

两个及以上 out 参数时,用语句调用形式拿到全部结果,尾部的实参就是接收结果的名字。

// DShader/Materials/M_Brightness.dsm
Function SplitBrightness(in vec3 color, out vec3 normalized, out float brightness) {
    brightness = max(max(color.r, color.g), color.b);
    normalized = color / max(brightness, 0.0001);
}

Shader(Name="Materials/M_Brightness")
{
    Properties = {
        vec3 Tint = vec3(1.0, 0.5, 0.25);
    }

    Settings = {
        Domain       = "UI";
        ShadingModel = "Unlit";
    }

    Outputs = {
        vec3 Color;
        Base.EmissiveColor = Color;
    }

    Graph = {
        SplitBrightness(Tint, Normalized, Brightness);
        Color = Normalized * Brightness;
    }
}
  • 函数体是 HLSL,所以这里的 max 是 HLSL 内建函数,不是 Graph 的数学内置。
  • 1.x 的 Function 要么有返回类型、要么有 out 参数,两者都有是 DSH6304,两者都没有是 DSH6305。
  • 没人声明过的 out 目标由调用声明(info DSH5283)。按值调用 —— vec3 n = SplitBrightness(Tint); —— 拿到的是第一个 out(自 2.0.0 起)。

写成 .dss

// DShader/Materials/M_Brightness.dss
#pragma material(Domain = UI, ShadingModel = Unlit)

uniform float3 Tint = float3(1.0, 0.5, 0.25);

/// @custom
void SplitBrightness(float3 color, out float3 normalized, out float brightness)
{
    brightness = max(max(color.r, color.g), color.b);
    normalized = color / max(brightness, 0.0001);
}

export void M_Brightness(inout material m)
{
    float3 Normalized;
    float  Brightness;
    SplitBrightness(Tint, Normalized, Brightness);
    m.EmissiveColor = Normalized * Brightness;
}

/// @custom 让函数体原样成为一个 Custom 节点,和 1.x 的 Function 一样;out 实参是先声明好的变量。

Settings 一览:带显式 backend 的玻璃

// DShader/Materials/M_Glass.dsm
Shader(Name="Materials/M_Glass")
{
    Properties = {
        vec3  Tint    = vec3(0.7, 0.9, 1.0);
        float Opacity = 0.35;
    }

    Settings = {
        Domain       = "Surface";
        ShadingModel = "DefaultLit";
        BlendMode    = "Translucent";
        TwoSided     = true;
        Wireframe    = false;
        Backend      = "Graph";
    }

    Outputs = {
        vec3  Color;
        float Alpha;

        Base.BaseColor = Color;
        Base.Opacity   = Alpha;
    }

    Graph = {
        Color = Tint;
        Alpha = Opacity;
    }
}
键别名取值
MaterialDomainDomainSurface, DeferredDecal / Decal, LightFunction, Volume, PostProcess, UI / UserInterface, RuntimeVirtualTexture / VirtualTexture
ShadingModel—Unlit, DefaultLit / Lit, Subsurface, PreintegratedSkin, ClearCoat, SubsurfaceProfile, TwoSidedFoliage, Hair, Cloth, Eye, SingleLayerWater, ThinTranslucent,以及 UE 5.4+ 上的 Substrate / Strata
BlendModeRenderTypeOpaque, Masked / Cutout, Translucent / Transparent, Additive, Modulate, AlphaComposite / PremultipliedAlpha / Premultiplied, AlphaHoldout, TranslucentColoredTransmittance
Backend—Graph, ThinCustom;Instance 是 ThinCustom 的旧拼写,带 warning DSH7204
  • 枚举值在比较前会去掉空格、_ 和 -,并且不区分大小写:"Default Lit"、"DefaultLit"、 "default_lit" 和 "DEFAULT-LIT" 是同一个别名。所有设置值的引号都可省。
  • MaterialDomain、ShadingModel 和 BlendMode 按上表解析;Backend 和 (自 2.0.0 起)Substrate 是 DreamShader 自己的键。其余每个键都被直接反射到 UMaterial 上 —— 上面的 TwoSided 和 Wireframe 就是真实的 UMaterial 属性。不是任何属性的键会让 构建失败:DSH8215,附带 DSH7118。
  • 一个键写两遍是 warning DSH3262 (自 2.0.0 起)(之前不报),后一个值生效。重复的 Settings section 会合并。
  • 省略 Backend 会回退到项目的 Default Compiler Backend(出厂是 ThinCustom)。 Backend = "Graph" 时材质是一个普通的 UMaterial,每次构建成功都存盘。

警告:

Backend = ""; 是错误 DSH7201 (自 2.0.0 起)(1.x 把它读成 Graph),不是 backend 的值是 DSH7202。只有省略这个键才会回退到项目设置。见 Backend。

写成 .dss

// DShader/Materials/M_Glass.dss
#pragma material(Domain = Surface, ShadingModel = DefaultLit, BlendMode = Translucent)
#pragma material(TwoSided = true, Wireframe = false, Backend = Graph)

uniform float3 Tint = float3(0.7, 0.9, 1.0);
uniform float Opacity = 0.35;

export void M_Glass(inout material m)
{
    m.BaseColor = Tint;
    m.Opacity = Opacity;
}

#pragma material 接受和 Settings 一样的键和值,可以分成多行写;同一个键设两次 在这里是错误 DSH7200。

Runtime Virtual Texture:采样与写入

RVT 节点没有专门的内置族,也不需要。采样、替换和特性开关节点都是通过 UE.<ClassName> 触达的普通表达式;RuntimeVirtualTextureOutput 是一个 custom output,根本没有输出引脚,因此它永远不会出现在 Graph 赋值的右侧——它通过 Expression( … ).Pin[i] 在 Outputs 中绑定。

// DShader/Materials/M_RVTSurface.dsm
Shader(Name="Materials/M_RVTSurface", Root="Game")
{
    Properties = {
        vec3 Fallback = vec3(0.5, 0.5, 0.5);
    }

    Settings = {
        Backend      = "Graph";
        Domain       = "Surface";
        ShadingModel = "DefaultLit";
        BlendMode    = "Opaque";
    }

    Outputs = {
        float3 SurfaceColor;
        float3 RVTBaseColor;
        float  RVTRoughness;

        Base.BaseColor = SurfaceColor;

        // Both statements reuse one RuntimeVirtualTextureOutput node.
        Expression(Class="RuntimeVirtualTextureOutput").Pin[0] = RVTBaseColor;
        Expression(Class="RuntimeVirtualTextureOutput").Pin[2] = RVTRoughness;
    }

    Graph = {
        float3 Sampled = UE.RuntimeVirtualTextureSample(
            OutputType     = "float3",
            Output         = "BaseColor",
            MaterialType   = "BaseColor_Normal_Roughness",
            VirtualTexture = Path(Game, "RVT/RVT_Terrain"));

        // Where the virtual texture has no page, fall back to the parameter ...
        float3 Blended = UE.RuntimeVirtualTextureReplace(
            OutputType = "float3", Default = Fallback, VirtualTextureOutput = Sampled);

        // ... and where virtual texturing is off entirely, fall back to it again.
        SurfaceColor = UE.VirtualTextureFeatureSwitch(
            OutputType = "float3", Yes = Blended, No = Fallback);

        RVTBaseColor = SurfaceColor;
        RVTRoughness = 0.5;
    }
}

注意:

此示例引用了 /Game/RVT/RVT_Terrain,一个项目 URuntimeVirtualTexture 资产。请替换为你项目中实际存在的资产。

节点Class 说明符写法
Runtime Virtual Texture SampleRuntimeVirtualTextureSampleUE.…,位于 Graph
Runtime Virtual Texture Sample ParameterRuntimeVirtualTextureSampleParameterProperties 中的参数声明
Runtime Virtual Texture ReplaceRuntimeVirtualTextureReplaceUE.…,位于 Graph
Virtual Texture Feature SwitchVirtualTextureFeatureSwitchUE.…,位于 Graph
Runtime Virtual Texture OutputRuntimeVirtualTextureOutputExpression( … ).Pin[i],位于 Outputs
  • RuntimeVirtualTextureSample 带有八个输出:0 BaseColor、1 Specular、2 Roughness、3 Normal、 4 WorldHeight、5 Mask、6 Displacement、7 Mask4。用 Output="BaseColor" 按名字选择,或用 OutputIndex= 按位置选择。
  • OutputType 从每个调用上获取(自 2.0.0 起);节点目录会为节点设置类型。
  • RuntimeVirtualTextureOutput 输入引脚,按 Pin[i] 顺序:0 BaseColor、1 Specular、2 Roughness、3 Normal、 4 WorldHeight、5 Opacity、6 Mask、7 Displacement、8 Mask4。
  • VirtualTexture 是一个反射的资产属性,因此它接受一个 Path(...) 引用或一个 裸的带引号对象路径。MaterialType 以及该节点的其他枚举和布尔属性由同一个反射步骤写入,遵循通常的枚举写法规则。
  • 写入 RVT 的材质保留 Domain = "Surface" 并添加输出节点,完全如上所述。单独的 Domain = "RuntimeVirtualTexture" 写法映射到 MD_RuntimeVirtualTexture,引擎将其标记为已弃用 并隐藏;DreamShader 仍然接受它,以便现有资产可以继续反编译,但新材质不应使用它。
  • Backend = "Graph" 在此处固定,因此结果是一个普通的 UMaterial,每次构建都会保存,你可以打开其节点 并与源码进行对比。节点本身不依赖于后端。

写成 .dss

// DShader/Materials/M_RVTSurface.dss
#pragma material(Backend = Graph, Domain = Surface, ShadingModel = DefaultLit, BlendMode = Opaque)

uniform float3 Fallback = float3(0.5, 0.5, 0.5);

export void M_RVTSurface(inout material m)
{
    float3 Sampled = UE.RuntimeVirtualTextureSample(
        MaterialType = "BaseColor_Normal_Roughness",
        VirtualTexture = "/Game/RVT/RVT_Terrain").BaseColor;
    float3 Blended = UE.RuntimeVirtualTextureReplace(Default = Fallback, VirtualTextureOutput = Sampled);
    float3 SurfaceColor = UE.VirtualTextureFeatureSwitch(Yes = Blended, No = Fallback);

    m.BaseColor = SurfaceColor;
    UE.Expression(Class = "RuntimeVirtualTextureOutput", Pin[0] = SurfaceColor, Pin[2] = 0.5);
}

输出通过成员访问(.BaseColor)选择,资产属性接收带引号的对象路径。自定义输出节点是入口的一条语句;Pin[i] = … 按引擎索引连接输入 i。

运行这些文件

  1. 把它们保存在 <Project>/DShader 下,或者 SourceDirectory 指向的位置。
  2. 启用 Auto Compile On Save(默认启用)时,编辑器会在源文件保存 0.25 秒后编译它(Save Debounce Seconds)。每次成功构建后,Graph 后端的材质以及每个材质函数、图层和混合都会保存到磁盘。ThinCustom 材质 —— 默认后端 —— 以 Ephemeral 形式构建在内存中,直到 cook、显式 Materialize、子材质实例或命令行给它一个 package。
  3. 无头编译单个文件:
& "<Engine>/Engine/Binaries/Win64/UnrealEditor-Cmd.exe" `
  "<Project>/MyProject.uproject" `
  -run=DreamShader compile -Source="<Project>/DShader/Materials/M_Minimal.dsm" -Force `
  -unattended -nopause -nosplash -stdout -log

把 -Source= 换成 -All 就能编译项目的每个源文件:先编译所有 .dsf,再按路径顺序编译其余源文件;.dsh 经由 include 它的文件编译。命令行会 Materialize 每个 ThinCustom 产物,所以每个材质都会写出一个 package。见命令行。

接下来

  • 常用写法 — 这些材质是由哪些构件拼起来的。
  • Custom Pass 示例 — .dsp 管线:描边、毛玻璃、透视、风场。
  • if / else — 条件语句、真值语义与分支合并规则。
  • 材质 Settings — 每一个 Settings 键,以及反射是怎么解析它的。
  • 输出绑定 — 每一个 Base.* 目标及其接受的值。
  • 命令行 — 无头编译及其开关。

本页目录