完整示例
可直接编译的完整 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;
}
}| 键 | 别名 | 取值 |
|---|---|---|
MaterialDomain | Domain | Surface, 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 |
BlendMode | RenderType | Opaque, 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 起)(之前不报),后一个值生效。重复的Settingssection 会合并。 - 省略
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 Sample | RuntimeVirtualTextureSample | UE.…,位于 Graph |
| Runtime Virtual Texture Sample Parameter | RuntimeVirtualTextureSampleParameter | Properties 中的参数声明 |
| Runtime Virtual Texture Replace | RuntimeVirtualTextureReplace | UE.…,位于 Graph |
| Virtual Texture Feature Switch | VirtualTextureFeatureSwitch | UE.…,位于 Graph |
| Runtime Virtual Texture Output | RuntimeVirtualTextureOutput | Expression( … ).Pin[i],位于 Outputs |
RuntimeVirtualTextureSample带有八个输出:0BaseColor、1Specular、2Roughness、3Normal、4WorldHeight、5Mask、6Displacement、7Mask4。用Output="BaseColor"按名字选择,或用OutputIndex=按位置选择。OutputType从每个调用上获取(自 2.0.0 起);节点目录会为节点设置类型。RuntimeVirtualTextureOutput输入引脚,按Pin[i]顺序:0BaseColor、1Specular、2Roughness、3Normal、4WorldHeight、5Opacity、6Mask、7Displacement、8Mask4。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。
运行这些文件
- 把它们保存在
<Project>/DShader下,或者SourceDirectory指向的位置。 - 启用 Auto Compile On Save(默认启用)时,编辑器会在源文件保存 0.25 秒后编译它(Save
Debounce Seconds)。每次成功构建后,
Graph后端的材质以及每个材质函数、图层和混合都会保存到磁盘。ThinCustom材质 —— 默认后端 —— 以 Ephemeral 形式构建在内存中,直到 cook、显式 Materialize、子材质实例或命令行给它一个 package。 - 无头编译单个文件:
& "<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.* 目标及其接受的值。
- 命令行 — 无头编译及其开关。