Examples
Examples
Examples
DreamShader » Examples
Fifteen complete, self-contained DreamShaderLang 1.x sources, ordered from the smallest possible
material to the constructs that need a .dsh header, a .dsf function file, or a specific engine
version — each followed by the same thing written as a .dss.
| Applies to | DreamShaderLang 1.x (.dsm, .dsf, .dsh), built by the 2.0 compiler through its legacy front end (since 2.0.0); the .dss forms are DreamShaderLang 2.0 |
| Engines | UE 5.3 – 5.8; anything version-gated is marked inline |
| Assumed source root | <Project>/DShader — the SourceDirectory project setting |
| Generated output | a Graph-backend material and every material function, layer and blend: saved on each successful build. A ThinCustom material — the default backend — stays Ephemeral, in memory, until it is materialized; see Ephemeral materials |
Every snippet below is a whole file. Paths in the leading comment are the on-disk location the
example assumes; import and #include paths resolve against that layout. Assets referenced with
Path(Engine, …) or /Engine/… ship with the engine, so those examples load as written; the two
that reference a project asset no example builds (11,
15) are marked.
Diagnostics in the notes are cited by code. A compile reports each one as
<file>(<line>,<col>): DSHnnnn: <message>, at the line and column of the construct it is about;
the code's page has the message, its cause and the fix. The code is the contract, the wording is not.
A message of the 1.x generator found in an old log is mapped on
Diagnostics.
The same in a .dss, under each example, is that file written by hand in DreamShaderLang 2.0:
#pragma material(…) for Settings, a uniform with /// tags for each parameter, and one
export function whose name is the asset's. A .dss builds its export in the file's own folder, so
each one here builds the same asset path as the 1.x file it stands beside — keep one or the other,
not both. dsc migrate writes the .dss of a 1.x file and proves it builds
the same graph; its output is longer than these, because it keeps every 1.x detail (/// @sort 32
on each parameter, the zero initialisers 1.x gave each output).
Contents
| # | Example | Exercises |
|---|---|---|
| 1 | Minimal unlit material | Shader, output bindings |
| 2 | Parameters, metadata, group scopes | Properties, metadata |
| 3 | A shared .dsh header | Namespace, import |
| 4 | Calling functions | Graph calls |
| 5 | UE.* and UE.Expression | UE.*, UE.Expression |
| 6 | if / else | if |
| 7 | MaterialAttributes | MaterialAttributes |
| 8 | Substrate material (UE 5.4+) | Substrate.* |
| 9 | ShaderFunction in a .dsf | ShaderFunction, VirtualFunction |
| 10 | ShaderLayer / ShaderLayerBlend | Layers |
| 11 | VirtualFunction | VirtualFunction |
| 12 | GraphFunction hoisting | GraphFunction |
| 13 | Settings and Backend | Shader settings, Backend |
| 14 | Layout and #Region | Layout |
| 15 | Runtime Virtual Texture | UE.Expression, output bindings |
Custom Pass pipelines (.dsp, UE 5.8) have a page of their own: Custom Pass examples.
1. Minimal unlit material
The smallest file that produces an asset: one parameter, one binding, one assignment.
// DShader/Materials/M_Minimal.dsm
Shader(Name="Materials/M_Minimal", Root="Game")
{
Properties = {
vec3 Tint = vec3(1.0, 0.2, 0.2);
}
Settings = {
Domain = "UI";
ShadingModel = "Unlit";
}
Outputs = {
vec3 Color;
Base.EmissiveColor = Color;
}
Graph = {
Color = Tint;
}
}Generated asset:
package /Game/Materials/M_Minimal
object path /Game/Materials/M_Minimal.M_Minimal
class UDreamShaderMaterialInstance over a hidden base UMaterial (the default ThinCustom backend)Shaderis matched case-sensitively;Properties,Settings,Outputs,Graphare not. See Lexical elements.- The
=after a section name is optional sugar (since 1.5.0):Properties { … }parses identically. The final;in a section body is optional too. Rootdefaults to/Game, soRoot="Game"above is redundant. Full rules: Asset paths.- A
Shaderwith noGraphsection and nothing in itsOutputsisDSH2255. Without aGraph, theOutputscompute the values themselves — an output declaration with an initializer, or (since 2.0.0) a binding whose right side is an expression. AShaderwith noOutputssection builds with nothing wired to it, and says so with the warningDSH2256(since 2.0.0).
The same in a .dss
// DShader/Materials/M_Minimal.dss
#pragma material(Domain = UI, ShadingModel = Unlit)
uniform float3 Tint = float3(1.0, 0.2, 0.2);
export void M_Minimal(inout material m)
{
m.EmissiveColor = Tint;
}The signature export void <Name>(inout material m) is what makes the file a material; a 1.x
Outputs binding is a write to a member of m.
See also: Shader · Output bindings ·
Material enums
2. Parameters: scalar, vector, static switch, texture
Every parameter family, with a Group("…") scope, [ … ] metadata and the Slider(min, max)
shorthand.
// DShader/Materials/M_Params.dsm
Shader(Name="Materials/M_Params")
{
Properties = {
Group("Surface") {
ScalarParameter Roughness = 0.55 [Slider(0, 1)];
VectorParameter Albedo = float4(0.8, 0.8, 0.8, 1.0) [Description="Base albedo"];
}
Group("Detail") {
TextureSampleParameter2D DetailMap = Path(Engine, "EngineResources/WhiteSquareTexture") [
SamplerType = "LinearColor";
SamplerSource = "FromTextureAsset";
SortPriority = 99;
];
StaticSwitchParameter UseDetail = true;
}
Texture2D NoiseTex = Path(Engine, "EngineResources/WhiteSquareTexture");
const float DebugScale = 1.0;
}
Settings = {
Domain = "Surface";
ShadingModel = "DefaultLit";
BlendMode = "Opaque";
}
Outputs = {
float3 Color;
float Rough;
Base.BaseColor = Color;
Base.Roughness = Rough;
}
Graph = {
vec2 UV = UE.TexCoord(Index = 0);
vec4 Detail = DetailMap(Coordinates = UV);
vec4 Noise = SampleTexture2D(NoiseTex, UV);
Color = UseDetail(True = Detail.rgb * Albedo.rgb, False = Albedo.rgb);
Rough = Roughness * DebugScale * Noise.r;
}
}float/vec3/Texture2Dare the compact spellings;ScalarParameter/VectorParameter/TextureSampleParameter2Dname the Unreal node explicitly. Both are documented in Compact types and Parameter nodes.- A
Group("…") { … }scope stamps its name onto the parameters inside it and numbers them0, 10, 20, …from one counter perPropertiessection; an explicitSortPrioritywins and takes no number. HereRoughnessgets 0 andAlbedo10. Nested groups compose with|(Outer|Inner). - (since 2.0.0) The stamp reaches the scalar, vector, texture and static-bool parameters. A
pin-bearing or switch parameter —
DetailMap,UseDetail— is written out as a node at each use, and that node carries only the metadata in its own[ … ]:DetailMapgetsSortPriority = 99and no group,UseDetailneither. constdeclares a value nobody can override — a compile-time constant, not a parameter. It takes a scalar, vector or texture type; any other isDSH3253.
[!NOTE] The call form
Name(Pin = …)only works for the ten parameter tokens whose node owns input pins —ChannelMaskParameter,StaticComponentMaskParameterand the eight*SampleParameter*tokens — and every argument must be named (DSH5259). A texture object parameter (Texture2D NoiseTex,TextureObjectParameter) has no pins and is no function:NoiseTex(Coordinates = UV)isDSH4208. Sample it with the reservedSampleTexture2D(textureObject, uv)form instead, as above.SampleTexture2Dis matched case-sensitively and takes exactly two positional arguments (DSH5256otherwise).
[!WARNING] A bare read of a
StaticSwitchParameteris not a value:Color = UseDetail;isDSH4200. Call it withTrue=andFalse=(orA=/B=, or positionally); a call without both branches isDSH5258, and any other argument is dropped with the warningDSH5254.
The same in a .dss
// DShader/Materials/M_Params.dss
#pragma material(Domain = Surface, ShadingModel = DefaultLit, BlendMode = Opaque)
/// @group Surface @sort 0 @slider 0 1
uniform float Roughness = 0.55;
/// @group Surface @sort 10 @desc Base albedo
uniform float4 Albedo = float4(0.8, 0.8, 0.8, 1.0);
/// @group Detail @sort 99 @sampler LinearColor
/// @default /Engine/EngineResources/WhiteSquareTexture
uniform Texture2D DetailMap;
/// @static
/// @group Detail @sort 20
uniform bool UseDetail = true;
/// @default /Engine/EngineResources/WhiteSquareTexture
uniform Texture2D NoiseTex;
static const float DebugScale = 1.0;
export void M_Params(inout material m)
{
float2 UV = UE.TexCoord(Index = 0);
float4 Detail = DetailMap.Sample(UV);
float4 Noise = NoiseTex.Sample(UV);
float3 Color = Albedo.rgb;
if (UseDetail)
{
Color = Detail.rgb * Albedo.rgb;
}
m.BaseColor = Color;
m.Roughness = Roughness * DebugScale * Noise.r;
}- A texture
uniformtakes no initializer: its asset is/// @default, its sampler type/// @sampler. Sampled with.Sample(UV)it builds aTextureObjectParameterand aTextureSample, where the 1.x token built oneTextureSampleParameter2D; the sampler source is the node's default, From Texture Asset. - A
/// @staticuniform boolread by anifis the static switch./// @groupand/// @sortwrite each parameter's group and priority;static constis the 1.xconst.
See also: Properties · Metadata ·
SamplerType · Path(...) ·
Reading parameters in Graph
3. A shared .dsh header: Namespace + Function
A .dsh header holds Function, GraphFunction, Namespace and VirtualFunction blocks,
import lines, and 2.0 declarations — no asset block (DSH2249).
It generates no asset. It is parsed on its own, and its declarations become names of every file that
imports it (since 2.0.0; 1.x pasted its text into the importing file).
// DShader/Shared/Common.dsh
Namespace(Name="Common")
{
Function ApplyTint(in vec3 color, in vec3 tint, out vec3 result) {
result = color * tint;
}
Function float Luma(in vec3 color) {
return dot(color, float3(0.299, 0.587, 0.114));
}
}
Function SelfContained Remap01(in float value, out float result) {
result = saturate(value * 0.5 + 0.5);
}
Function SplitChannels(in vec4 src, out vec3 rgb, out float alpha) {
rgb = src.rgb;
alpha = src.a;
}Importing it:
// DShader/Materials/M_Tinted.dsm
import "Shared/Common.dsh";
Shader(Name="Materials/M_Tinted")
{
Properties = {
vec3 Albedo = vec3(0.6, 0.8, 1.0);
vec3 Tint = vec3(1.0, 0.4, 0.1);
}
Settings = { Domain = "Surface"; ShadingModel = "Unlit"; }
Outputs = {
vec3 Color;
Base.EmissiveColor = Color;
}
Graph = {
Common::ApplyTint(Albedo, Tint, Tinted);
Color = Tinted;
}
}import "Shared/Common"is equivalent: when the specifier carries no extension at all,.dshis appended. Only a.dshcan be imported (since 2.0.0): a.dsfor.dsmpath isDSH2252.- A relative specifier is tried against the importing file's own directory, then its source root's
directory (
DShader), then that root'sPackages— never another root's. A candidate that resolves outside its root is skipped. Seeimport. - (since 2.0.0)
importis read as a token at file scope: the;is optional,'single quotes'work in a 1.x file, and animportinside a/* … */comment is commented out (1.x processed it). Keep eachimporton a line of its own all the same: the editor's dependency scanner, which recompiles the importers when a header is saved, still reads them line by line. Functionbodies are HLSL, notGraphstatements:dot,saturateandfloat3(…)here are HLSL intrinsics. GLSL spellings are rewritten inside those bodies, case-insensitively and without a diagnostic (vec3→float3,mix→lerp,fract→frac,mod→fmod).- (since 2.0.0) Every
Functionnode embeds theFunctions its body calls, which 1.x did only for aSelfContainedone, soSelfContainedchanges nothing.Inlineis its old spelling, with the warningDSH6306; on aGraphFunctioneither one isDSH6307. - (since 2.0.0) A namespace-qualified call inside another
FunctionorGraphFunctionbody works: body normalisation flattensCommon::ApplyTinttoCommon_ApplyTint, which is the function's own name, and the callee is embedded in the caller's node. 1.x left an undefined symbol for the shader compiler. Write the::without spaces around it.
The same in a .dss
// DShader/Materials/M_Tinted.dss
#include "Shared/Common.dsh"
#pragma material(Domain = Surface, ShadingModel = Unlit)
uniform float3 Albedo = float3(0.6, 0.8, 1.0);
uniform float3 Tint = float3(1.0, 0.4, 0.1);
export void M_Tinted(inout material m)
{
float3 Tinted;
Common_ApplyTint(Albedo, Tint, Tinted);
m.EmissiveColor = Tinted;
}A .dsh holds both dialects, so the .dss includes the header as it is. A namespaced function is
called by the name it is declared under, Common_ApplyTint, and an out argument is a variable
declared first. The header itself in 2.0 spelling — a .dsm imports this one too:
// DShader/Shared/Common.dsh
/// @custom
/// @name Common::ApplyTint
void Common_ApplyTint(float3 color, float3 tint, out float3 result)
{
result = color * tint;
}
/// @custom
/// @name Common::Luma
float Common_Luma(float3 color)
{
return dot(color, float3(0.299, 0.587, 0.114));
}
/// @custom
void Remap01(float value, out float result)
{
result = saturate(value * 0.5 + 0.5);
}
/// @custom
void SplitChannels(float4 src, out float3 rgb, out float alpha)
{
rgb = src.rgb;
alpha = src.a;
}/// @custom keeps the body verbatim and makes each call one Custom node, as a 1.x Function does;
/// @name keeps the node's title Common::ApplyTint.
See also: import · Namespace ·
Function · Source files ·
Packages
4. Calling functions: value form and statement form
A function's value is its return value, or its first out. The statement form passes the inputs,
then one target per output; its trailing arguments name the variables that receive the results.
// DShader/Materials/M_Calls.dsm
import "Shared/Common.dsh";
Shader(Name="Materials/M_Calls")
{
Properties = {
vec4 Source = vec4(0.4, 0.6, 0.9, 0.75);
vec3 Tint = vec3(1.0, 0.4, 0.1);
}
Settings = { Domain = "Surface"; ShadingModel = "Unlit"; BlendMode = "Translucent"; }
Outputs = {
vec3 Color;
float Alpha;
Base.EmissiveColor = Color;
Base.Opacity = Alpha;
}
Graph = {
// statement form: two out results, two target names
SplitChannels(Source, Rgb, A);
// statement form: one out result
Common::ApplyTint(Rgb, Tint, Tinted);
// value form: single-output functions
float L = Common::Luma(Tinted);
float Soft = Remap01(L);
Color = Tinted * Soft;
Alpha = A;
}
}| Callee kind | Value form | Statement form | Named arguments |
|---|---|---|---|
Function | yes — the return value, or the first out (since 2.0.0; 1.x: one result only) | yes | yes (since 2.0.0) |
GraphFunction | as Function | yes | yes (since 2.0.0) |
ShaderFunction | yes — the first output, unless a selector picks another; callable only from its own file | yes | yes |
ShaderLayer / ShaderLayerBlend | not callable: DSH6208 (since 2.0.0) | — | — |
VirtualFunction | yes | yes | yes |
- An out target nobody declared is declared by the call, as a local of the output's type (info
DSH5283). A target has to be assignable — a variable, or a member or swizzle of one (DSH4239) — of the output's type (DSH4218). - In an all-positional statement call the last arguments receive the outputs, one each, in
declaration order; the ones before them are inputs. Too many arguments are
DSH4224, and an input left out that is notoptisDSH4217. - Function names are case-sensitive (since 2.0.0): a call that matches a function only in case
still resolves, with the warning
DSH5275. There is no overload resolution — one name declares one thing, and a second declaration of it isDSH4210where it is written. - The math builtins (
lerp,dot,pow,min,max,clamp,saturate,sin,frac,fmod, …) are resolved before user functions; a function named like one isDSH6206at its declaration.mix,fractandmodare their GLSL spellings, accepted in a 1.xGraphwith the warningDSH5277.
The same in a .dss
// DShader/Materials/M_Calls.dss
#include "Shared/Common.dsh"
#pragma material(Domain = Surface, ShadingModel = Unlit, BlendMode = Translucent)
uniform float4 Source = float4(0.4, 0.6, 0.9, 0.75);
uniform float3 Tint = float3(1.0, 0.4, 0.1);
export void M_Calls(inout material m)
{
float3 Rgb;
float A;
SplitChannels(Source, Rgb, A);
float3 Tinted;
Common_ApplyTint(Rgb, Tint, Tinted);
float L = Common_Luma(Tinted);
float Soft;
Remap01(L, Soft);
m.EmissiveColor = Tinted * Soft;
m.Opacity = A;
}A .dss passes every out of a call to a variable it declared first. The 1.x value call of a
function without a return type, Remap01(L), is a legacy rule (L3b); dsc migrate writes it as the
statement above — see what the rewrite does.
See also: Graph calls · Name resolution · Math builtins · Statements
5. UE.* nodes and a generic UE.Expression(...)
UE.<Name>(…) creates one UMaterialExpression. <Name> is a class of the builtin catalog — any
engine expression class by its short name (Sine, CameraVectorWS), or one of the 1.x aliases the
catalog carries (TexCoord, TransformVector, …). UE.Expression(Class = …) names the class in an
argument instead; the two are one path (since 2.0.0).
// DShader/Materials/M_UEBuiltins.dsm
Shader(Name="Materials/M_UEBuiltins")
{
Settings = {
Domain = "UI";
ShadingModel = "Unlit";
}
Outputs = {
vec3 Color;
Base.EmissiveColor = Color;
}
Graph = {
float2 uv = UE.TexCoord(Index = 0);
float t = UE.Time();
float3 view = UE.CameraVectorWS();
// Generic form: any MaterialExpression class, by name.
float pulse = UE.Expression(Class="Sine", OutputType="float1", Input=t);
// Class= may be omitted when the function name IS the class name.
float pulse2 = UE.Sine(OutputType="float1", Input=t * 0.5);
// Reflected literal properties are written by name.
float3 local = UE.TransformVector(Input = view, Source = "World", Destination = "Local");
Color = float3(uv.x, uv.y, pulse + pulse2 + local.z);
}
}Class="Sine","MaterialExpressionSine","UMaterialExpressionSine"(since 2.0.0) and"/Script/Engine.MaterialExpressionSine"all resolve to the same class. Quotes are optional:Class=SineandOutputType=float1work identically.- An argument names an input pin of the class first, then one of its properties; the pin wins on a
tie. A pin takes an expression; a property takes a literal
(
DSH4373for a value computed at run time). What happens to a name that is neither is onUE.Expression. UE.Expression(…)withoutClass=isDSH5218.OutputType(aliasResultType) is no longer required (since 2.0.0): the legacy front end takes it off the call, and the catalog types the node. It still sets the output type of aCustomnode; elsewhere a numeric token only gives the width of a one-output node's value, and anything else is dropped. SeeOutputType.
[!WARNING] On the 27 1.x names of the
UE.*catalogue —TexCoord,Time,CameraVectorWS,TransformVector, … — the legacy front end keeps the arguments 1.x read and drops every other one, an unknown name or a positional argument, with the warningDSH5254(since 2.0.0; 1.x dropped it without a word).UE.Time(Bogus = 1)andUE.TexCoord(0)still build, and say why. The only positional argument kept is the first ofUE.TransformVector/UE.TransformPosition(→Input). On any other node a positional argument binds only where the catalog gives the class an argument order, and isDSH5220elsewhere.
The same in a .dss
// DShader/Materials/M_UEBuiltins.dss
#pragma material(Domain = UI, ShadingModel = Unlit)
export void M_UEBuiltins(inout material m)
{
float2 uv = UE.TexCoord(Index = 0);
float t = UE.Time();
float3 view = UE.CameraVectorWS();
float pulse = UE.Expression(Class = "Sine", Input = t);
float pulse2 = UE.Sine(Input = t * 0.5);
float3 local = UE.TransformVector(Input = view, Source = "World", Destination = "Local");
m.EmissiveColor = float3(uv.x, uv.y, pulse + pulse2 + local.z);
}A .dss gets no legacy filter: an argument the node does not have is
DSH5213, not a warning.
See also: UE.* catalogue · UE.Expression ·
OutputType · Transform bases
6. if / else in Graph
The condition must be parenthesised and each body must be braced. Both branches are built into the
graph; one conditional node per changed variable — a UMaterialExpressionIf, or a StaticSwitch
on a static bool — selects between them at run time.
// 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;
}
}
}| Condition | Meaning |
|---|---|
a > b, a < b, a >= b, a <= b, a == b, a != b | the six comparison operators |
if (x) | truthy — identical to x != 0 |
- The condition compares two numbers: a comparison of two vectors is
DSH4260, an operand that is not a numberDSH4226. - A variable declared before the
ifand changed in one branch only is merged with the value it had before theif. A variable that had no value before it and is assigned in one branch only isDSH4372where it is read after theif. - A variable declared inside a branch is local to that branch (since 2.0.0): it is not merged, and
its name is unknown after the
if. - A value is converted to its variable's declared type at every assignment, so the two branch values of one variable always agree.
- Reading a parameter inside a branch is fine — parameters are never branch outputs.
ifis matched case-sensitively:If (x) { … }is a call to a function namedIf, and the{after it isDSH2154.
[!WARNING]
&&and||are refused in a 1.x condition (since 2.0.0):DSH2210. 1.x dropped everything after the first comparison without a word, soif (a > 0 && b > 0)testeda > 0. Nest twoifstatements instead. The other constructs 1.x truncated —%,?:,&,|,^,<<,v[i]— are errors anywhere in an expression (DSH2200–DSH2202); see Unsupported constructs.
The same in a .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;
}The variable the branches assign is declared before the if, as a 1.x Outputs declaration is.
See also: if / else · Expressions ·
Conversions
7. MaterialAttributes output binding
Binding Base.MaterialAttributes turns on Use Material Attributes on the generated material.
MaterialAttributes Attrs; is an empty attribute set — one MakeMaterialAttributes node — and its
members are written by name; where the value is used, the writes become one
SetMaterialAttributes node (since 2.0.0; 1.x chained one per write).
// 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;
}
}[!NOTE] The
Outputsbindings are applied after theGraph, so the binding takes theAttrsdeclared in theGraph, as above. (since 2.0.0) AnOutputsdeclarationMaterialAttributes Attrs;is that variable too, starting empty — see MaterialAttributes.MaterialAttributesis a type ofOutputs,Inputs,Graphvariables and function results; as aPropertiestype it isDSH3252, and as an input of aFunctionDSH6210.
- Member names are the material property names (
BaseColor,Metallic,Specular,Roughness,EmissiveColor,Opacity,Normal, …) of the running engine; one it does not have isDSH5200. - An arithmetic operator on a
MaterialAttributesvalue isDSH4226. Base.MaterialAttributesandBase.FrontMaterialin oneShaderare no longer refused (since 2.0.0): both are wired, and Use Material Attributes makes the material read the attribute set.
The same in a .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;
}The entry writes the members of m, which go straight to the material's pins: no
MakeMaterialAttributes node is made. A member is read back only after it is written
(DSH4370 otherwise).
See also: MaterialAttributes · Output bindings · Types
8. Substrate material (UE 5.4+)
Substrate.* builds Substrate nodes; Base.FrontMaterial is the binding that consumes them.
// 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 needs UE 5.4 or newer: the catalog lists the Substrate classes of the running engine,
and an older engine has none. There a
Substrate.*call isDSH5210andBase.FrontMaterialDSH5200;Substrate.Selectneeds UE 5.6 (DSH5294). The project must also have Substrate on for the engine to compile the graph — see One source, two kinds of project. - DreamShader sets no shading model for
Base.FrontMaterial(since 2.0.0; 1.x forcedSubstrateand refused any otherShadingModel): aShadingModelsetting is applied as written. - A BSDF node takes its arguments by name; the five composition nodes (
Add,Weight,Mix,Layer,Select) take them by position too (since 2.0.0). A positional argument elsewhere isDSH5220, andClass=isDSH5216: the name already is the class. - A
Substrate.*call in aGraphFunctionbody is not lifted into a node — no Custom node input carries a Substrate value — and says so with the warningDSH6316(since 2.0.0). - (since 2.0.0) Substrate values combine:
A + B,A * wandlerp(A, B, t)areSubstrate.Add,Substrate.WeightandSubstrate.HorizontalMix, and anifbetween two of them is aStaticSwitchunder a static condition and aSubstrate.Selectotherwise. Any other operator isDSH5293, and a member read on a Substrate valueDSH4207. See Substrate sugar.
The same in a .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);
}See also: Substrate.* ·
Output bindings · Types
9. ShaderFunction in a .dsf, called from a material
A .dsf declares function assets — ShaderFunction, ShaderLayer, ShaderLayerBlend — beside
helper blocks (Function, GraphFunction, Namespace, VirtualFunction). Only a .dsh can be
imported, so a material in another file reaches the generated function as it reaches any existing
asset: through a VirtualFunction (since 2.0.0). A material in the same .dsm as the function
calls it by name.
// DShader/Functions/F_Tint.dsf
ShaderFunction(Name="Functions/F_Tint")
{
Inputs = {
vec3 InColor;
opt float Strength = 1.0 [
Description = "Preview strength";
SortPriority = 10;
];
}
Outputs = {
vec3 OutColor [Description="Tinted colour"];
}
Settings = {
Description = "Tint helper";
ExposeToLibrary = true;
LibraryCategories = "Examples";
}
Graph = {
OutColor = InColor * Strength;
}
}Calling it from a material:
// DShader/Materials/M_UsesTint.dsm
VirtualFunction(Name="F_Tint")
{
Options = { Asset = Path(Game, "Functions/F_Tint"); }
Inputs = {
vec3 InColor;
opt float Strength = 1.0;
}
Outputs = {
vec3 OutColor;
}
}
Shader(Name="Materials/M_UsesTint")
{
Properties = {
vec3 Albedo = vec3(0.6, 0.8, 1.0);
}
Settings = { Domain = "Surface"; ShadingModel = "Unlit"; }
Outputs = {
vec3 Color;
Base.EmissiveColor = Color;
}
Graph = {
// named form; the opt input may be omitted or passed `default`
Color = F_Tint(InColor = Albedo, Strength = 0.5);
}
}- (since 2.0.0) Through 1.9.x the material imported the function file —
import "Functions/F_Tint.dsf";— and one compile built both assets. That import is nowDSH2252; theVirtualFunctiondeclares the asset thatF_Tint.dsfbuilds, and each file builds its own. - Build the function first. The call loads the asset when the material is built, and a missing one
is
DSH8219. A material function is saved on every successful build, and the commandlet's-Allcompiles every.dsfbefore anything else. - Inside its own
.dsf, aShaderFunctionis called by the last segment of itsName(F_Tint); anywhere else, by theVirtualFunction'sName. Names are case-sensitive (since 2.0.0); a call that matches only in case resolves with the warningDSH5275. - Arguments are positional, named, or positional then named (since 2.0.0; 1.x refused a mix); a
positional argument after a named one is
DSH2158. An input left out of a call must beoptor have a default, or the call isDSH4217. opt— or (since 2.0.0) a default value alone — is what makes an input optional on the generatedUMaterialFunction; its default drives the input's preview value.- A material function honours
Description,ExposeToLibraryandLibraryCategories. (since 2.0.0)ExposeToLibrarytakes effect only with a non-emptyLibraryCategories,UserExposedCaptionis not applied (warningDSH3264), and any other key is ignored with the warningDSH3263. In aShadera key that names no material property fails the build — see example 13.
The same in a .dss
// DShader/Functions/F_Tint.dss
/// @desc Tint helper
/// @library Examples
/// @param Strength Preview strength
export void F_Tint(float3 InColor, float Strength = 1.0, out float3 OutColor)
{
OutColor = InColor * Strength;
}// DShader/Materials/M_UsesTint.dss
/// @asset /Game/Functions/F_Tint
extern void F_Tint(float3 InColor, float Strength = 1.0, out float3 OutColor);
#pragma material(Domain = Surface, ShadingModel = Unlit)
uniform float3 Albedo = float3(0.6, 0.8, 1.0);
export void M_UsesTint(inout material m)
{
float3 Tinted;
F_Tint(Albedo, Strength = 0.5, OutColor = Tinted);
m.EmissiveColor = Tinted;
}An export function that is not a material entry is a material function; a default argument makes
an input optional, an out parameter is an extra output, and /// @library is the library category.
The material calls the asset through an extern prototype bound by /// @asset — the 2.0
VirtualFunction.
See also: ShaderFunction ·
VirtualFunction ·
Inputs / Outputs ·
Function settings · Graph calls
10. ShaderLayer / ShaderLayerBlend
These generate native UMaterialFunctionMaterialLayer and UMaterialFunctionMaterialLayerBlend
assets (since 1.3.0), and their interfaces are fixed by signature rules.
// DShader/Layers/L_SimpleSurface.dsf
ShaderLayer(Name="Layers/L_SimpleSurface")
{
Properties = {
VectorParameter LayerColor = float4(0.8, 0.2, 0.1, 1.0) [Group="Layer"];
ScalarParameter LayerRough = 0.5 [Group="Layer"; Slider(0, 1)];
}
Outputs = {
MaterialAttributes Attrs;
}
Graph = {
Attrs.BaseColor = LayerColor.rgb;
Attrs.Roughness = LayerRough;
}
}
ShaderLayerBlend(Name="Layers/LB_Overlay")
{
Properties = {
ScalarParameter Alpha = 0.5 [Group="Blend"; Slider(0, 1)];
}
Inputs = {
MaterialAttributes Bottom;
MaterialAttributes Top;
}
Outputs = {
MaterialAttributes Attrs;
}
Graph = {
Attrs.BaseColor = lerp(Bottom.BaseColor, Top.BaseColor, Alpha);
Attrs.Roughness = lerp(Bottom.Roughness, Top.Roughness, Alpha);
}
}| Block | Inputs | Output |
|---|---|---|
ShaderLayer | none, or one MaterialAttributes. (since 2.0.0) The layer always gets exactly one MaterialAttributes input, named after its output — the material it is applied to | exactly one MaterialAttributes |
ShaderLayerBlend | MaterialAttributes only, at least one (since 2.0.0; 1.x: exactly two); the engine's layer stack blends two | exactly one MaterialAttributes |
- A layer with any other input or output is
DSH6204; a blend without aMaterialAttributesinput, or with another output,DSH6205; either one without aMaterialAttributesoutputDSH3278. Layer and blend controls belong inProperties, where they become parameters of the generated function. - A layer's output starts as the material that came in, so
L_SimpleSurfacechanges two attributes of the material it is applied to; a blend's output starts empty. - On UE 5.7+ each blend input gets a
BlendInputRelevancefrom its name:Top/TopLayerisTop,Bottom/BottomLayer/Base/BaseLayerisBottom, and any other name isBottomon the first input andTopon a later one. On earlier engines the property does not exist. MaterialLayer(...)/MaterialLayerBlend(...)still parse as the old spellings, with the warningDSH2251(deprecated in 1.3.0).- Neither block can be called from a
Graph(since 2.0.0):DSH6208. Their consumer is the layer stack of a material or material instance.
The same in a .dss
// DShader/Layers/L_SimpleSurface.dss
/// @group Layer
uniform float4 LayerColor = float4(0.8, 0.2, 0.1, 1.0);
/// @group Layer @slider 0 1
uniform float LayerRough = 0.5;
/// @group Blend @slider 0 1
uniform float Alpha = 0.5;
/// @layer
export void L_SimpleSurface(inout material m)
{
m.BaseColor = LayerColor.rgb;
m.Roughness = LayerRough;
}
/// @layerblend
export void LB_Overlay(material Bottom, material Top, inout material Attrs)
{
Attrs.BaseColor = lerp(Bottom.BaseColor, Top.BaseColor, Alpha);
Attrs.Roughness = lerp(Bottom.Roughness, Top.Roughness, Alpha);
}One file, two exports, two assets: /// @layer and /// @layerblend say which kind each one is.
See also: ShaderLayer / ShaderLayerBlend ·
MaterialAttributes · Source files
11. VirtualFunction: wrapping an existing asset
A VirtualFunction generates nothing. It declares the interface of a UMaterialFunction that
already exists so that Graph blocks can call it with type checking.
// DShader/VirtualFunctions/BufferWriter.dsh
VirtualFunction(Name="BufferWriter")
{
Options = {
Asset = Path(Game, "MaterialFunctions/F_BufferWriter");
Description = "Existing material function declared for Graph calls.";
}
Inputs = {
float3 Color;
opt float Alpha = 1.0;
}
Outputs = {
float3 Result;
}
}// DShader/Materials/M_Buffered.dsm
import "VirtualFunctions/BufferWriter.dsh";
Shader(Name="Materials/M_Buffered")
{
Properties = { vec3 Tint = vec3(1.0, 0.4, 0.1); }
Settings = { Domain = "Surface"; ShadingModel = "Unlit"; }
Outputs = {
vec3 Color;
Base.EmissiveColor = Color;
}
Graph = {
Color = BufferWriter(Color = Tint, Alpha = 0.5);
}
}[!NOTE] This example references
/Game/MaterialFunctions/F_BufferWriter, a project asset. Substitute a path that exists in your project, or let the editor write the declaration for you: the CreateVirtualFunction entry of aUMaterialFunction's context menu writes a matching.dshunderDShader/VirtualFunctions, and Create extern Prototype (2.0) writes theexternform a.dssincludes. See VirtualFunction tools.
- The asset may come from
Options = { Asset = … }or from the header attributeVirtualFunction(Name="…", Asset="…"). Without one the block isDSH6312. Settingsis an accepted alias forOptions, andPropertiesis an accepted alias forInputsinside this block only. AGraphorCodesection isDSH2247.- At least one output is required (
DSH6313), andNamemust be an identifier (DSH6311). - Nothing is checked against the asset until a
Graphcalls it. A declared input or output is then matched to the asset's pins by name — exactly, then ignoring case — and failing both by its position; one that matches nothing isDSH8220/DSH8221, and an asset that does not loadDSH8219. Pathroots:Game,Engine,Plugin.<Name>/Plugins.<Name>, a full object path, or a bare quoted"/Game/…". SeeVirtualFunction.
The same in a .dss
// DShader/VirtualFunctions/BufferWriter.dsh
/// @asset /Game/MaterialFunctions/F_BufferWriter
/// @desc Existing material function declared for Graph calls.
extern float3 BufferWriter(float3 Color, float Alpha = 1.0);// DShader/Materials/M_Buffered.dss
#include "VirtualFunctions/BufferWriter.dsh"
#pragma material(Domain = Surface, ShadingModel = Unlit)
uniform float3 Tint = float3(1.0, 0.4, 0.1);
export void M_Buffered(inout material m)
{
m.EmissiveColor = BufferWriter(Tint, Alpha = 0.5);
}The header is the same declaration in 2.0 spelling — an extern prototype; a first output named
Result is its return value, and a default argument an input the call may leave out. A .dsm
imports this header as it imports the 1.x one, and the .dss includes either.
See also: VirtualFunction ·
Options · Path(...) ·
VirtualFunction tools
12. GraphFunction: hoisting a UE.* call into a Custom node
A GraphFunction body is HLSL like a Function, but every UE.* call inside it is evaluated as a
real material node and wired into the generated Custom node as an extra input pin.
// DShader/Shared/Wind.dsh
GraphFunction WindPulse(in float2 uv, out float pulse) {
float t = UE.Time();
pulse = sin(uv.x * 8.0 + t);
}// DShader/Materials/M_Wind.dsm
import "Shared/Wind.dsh";
Shader(Name="Materials/M_Wind")
{
Settings = { Domain = "Surface"; ShadingModel = "Unlit"; }
Outputs = {
vec3 Color;
Base.EmissiveColor = Color;
}
Graph = {
vec2 UV = UE.TexCoord(Index = 0);
float Pulse = WindPulse(UV); // value form: one out result
Color = vec3(Pulse, Pulse, Pulse);
}
}Generated Custom-node code, in outline:
float pulse = (float)0;
// Begin DreamShader source: <file>
// DreamShader custom: WindPulse line <N>
float t = _ds_WindPulse_UE0;
pulse = sin(uv.x * 8.0 + t);
// End DreamShader source: <file>
return pulse;- The generated pin is named
_ds_<Function>_UE<N>: the base name__ds_<Function>_UE<N>is identifier-sanitized, which collapses its leading__to one_, and a name that collides with a declaredinparameter gets_1,_2, … SeeGraphFunction. - (since 2.0.0) A lifted call is bound in the function's own scope: its parameters, replaced at each
call by that call's arguments, and the file's declarations. A name only the caller has is
DSH6325— 1.x read the caller'sGraphscope. - Only the
UE.prefix is lifted. ASubstrate.*call stays HLSL text, with the warningDSH6316; a call to anotherFunctionis embedded in the node, as in aFunctionbody. - A lifted value may be a number, a bool or (since 2.0.0) a texture object; a
MaterialAttributesor Substrate value isDSH6329. GraphFunctionaccepts noSelfContained/Inlinemodifier (DSH6307) and no recursion (DSH6260, andDSH6330through a lifted call). It takes named arguments (since 2.0.0). An empty body is accepted, and its outputs come out as 0.- The statement form works too:
WindPulse(UV, Pulse);declaresPulse(infoDSH5283) when nothing else does.
The same in a .dss
// DShader/Materials/M_Wind.dss
#pragma material(Domain = Surface, ShadingModel = Unlit)
/// @custom
void WindPulse(float2 uv, out float pulse)
{
float t = UE.Time();
pulse = sin(uv.x * 8.0 + t);
}
export void M_Wind(inout material m)
{
float2 UV = UE.TexCoord(Index = 0);
float Pulse;
WindPulse(UV, Pulse);
m.EmissiveColor = float3(Pulse, Pulse, Pulse);
}A /// @custom function is a GraphFunction and a Function at once: its body is verbatim HLSL,
and a UE. call in it is lifted into an input of the Custom node the same way.
See also: GraphFunction · Function ·
Generated HLSL · UE.* catalogue
13. Settings: domain, shading model, blend mode, 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;
}
}| Key | Aliases | Values |
|---|---|---|
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, plus Substrate / Strata on UE 5.4+ |
BlendMode | RenderType | Opaque, Masked / Cutout, Translucent / Transparent, Additive, Modulate, AlphaComposite / PremultipliedAlpha / Premultiplied, AlphaHoldout, TranslucentColoredTransmittance |
Backend | — | Graph, ThinCustom; Instance is the old spelling of ThinCustom, with the warning DSH7204 |
- Enum values are matched with spaces,
_and-stripped, case-insensitively:"Default Lit","DefaultLit","default_lit"and"DEFAULT-LIT"are one alias. Quotes are optional on every setting value. MaterialDomain,ShadingModelandBlendModeare resolved through the tables above;Backendand (since 2.0.0)Substrateare DreamShader's own keys. Every other key is reflected straight ontoUMaterial—TwoSidedandWireframeabove are realUMaterialproperties. A key that names no property fails the build:DSH8215, carryingDSH7118.- A key written twice is the warning
DSH3262(since 2.0.0; silent before), and the later value wins. RepeatedSettingssections merge. - Omitting
Backendfalls back to the project's Default Compiler Backend (ThinCustomas shipped). WithBackend = "Graph"the material is an ordinaryUMaterial, saved on every successful build.
[!WARNING]
Backend = "";is the errorDSH7201(since 2.0.0; 1.x read it asGraph), and any value that is no backend isDSH7202. Only omitting the key falls back to the project setting.
- A modified engine adds its own shading models automatically — the reflected table is read from
EMaterialShadingModelat runtime, and the project's mapping tables can add or shadow spellings.
The same in a .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 takes the same keys and values as Settings, and may be split over several
lines; a key set twice is an error there, DSH7200.
See also: Settings · Shader settings ·
Material enums · Backend ·
Project settings
14. Layout placement and #Region
Layout pins generated nodes to fixed positions and draws comment boxes. #Region / #EndRegion
group statements inside a Graph block; each region becomes a comment box in the generated graph.
// DShader/Materials/M_Laid.dsm
Shader(Name="Materials/M_Laid")
{
Properties = {
VectorParameter BaseColor = float4(0.8, 0.8, 0.8, 1.0) [Group="Surface"; SortPriority=10];
ScalarParameter Roughness = 0.55 [Group="Surface"; SortPriority=20];
Texture2D NoiseTex = Path(Engine, "EngineResources/WhiteSquareTexture");
}
Settings = {
Domain = "Surface";
ShadingModel = "DefaultLit";
BlendMode = "Opaque";
}
Outputs = {
float3 Color;
float Rough;
Base.BaseColor = Color;
Base.Roughness = Rough;
}
Graph = {
#Region "Sampling"
vec2 UV = UE.TexCoord(Index = 0);
vec4 Noise = SampleTexture2D(NoiseTex, UV);
#EndRegion
#Region "Surface"
Color = BaseColor.rgb * Noise.rgb;
Rough = Roughness;
#EndRegion
}
Layout = {
Comment(Name="Sampling", X=-1200, Y=-200, W=900, H=400, Color=float4(0.10, 0.16, 0.22, 0.35));
Comment(Name="Surface", X=-1200, Y=260, W=900, H=400);
Node(Var="UV", X=-1100, Y=-120);
Node(Var="Noise", X=-760, Y=-120);
}
}| Call | Required arguments | Optional |
|---|---|---|
Node | Var (text), X, Y (integers) | — |
Comment | Name (text), X, Y, W, H (integers) | Color (a float4 literal) |
Varnames aGraphvariable. Anything butNode(…)andComment(…)in aLayoutsection isDSH3274; a missing or non-integer argumentDSH3275, a malformedColorDSH3276.- A second
Layoutsection replaces the first rather than appending, with the warningDSH2258— unlikeProperties,InputsandOutputs, which append. #Regionnames may be quoted or bare, the directives are matched case-insensitively, and regions nest. A#Regionwithout a name isDSH2216, an#EndRegionwith none openDSH2217, a region never closedDSH2218. A directive is a token, so every diagnostic keeps its real line and column.- Region directives are read only in a
Graphbody; aFunctionorGraphFunctionbody is HLSL, passed on as written. #Regionnames andLayoutCommentnames are independent: theSamplingregion and theSamplingcomment above are two boxes.- The decompiler writes node positions and free comment boxes back when the Export Decompiled
Layout project setting is on (the default) — as
#pragma layoutlines, or as aLayoutsection with the legacy format.
[!WARNING] Regeneration clears the target graph. Node positions not pinned by
Layout, hand-added nodes, node property tweaks and comment boxes whose text begins withDreamShader:are destroyed. Only comment boxes without that prefix survive. An asset edited by hand since it was generated is not rebuilt at all (DSH8207); see Divergence.
The same in a .dss
// DShader/Materials/M_Laid.dss
#pragma material(Domain = Surface, ShadingModel = DefaultLit, BlendMode = Opaque)
/// @group Surface @sort 10
uniform float4 BaseColor = float4(0.8, 0.8, 0.8, 1.0);
/// @group Surface @sort 20
uniform float Roughness = 0.55;
/// @default /Engine/EngineResources/WhiteSquareTexture
uniform Texture2D NoiseTex;
#pragma layout(Comment, Name = "Sampling", X = -1200, Y = -200, W = 900, H = 400, Color = "0.10 0.16 0.22 0.35")
#pragma layout(Comment, Name = "Surface", X = -1200, Y = 260, W = 900, H = 400)
#pragma layout(Node, Var = "UV", X = -1100, Y = -120)
#pragma layout(Node, Var = "Noise", X = -760, Y = -120)
export void M_Laid(inout material m)
{
#pragma region Sampling
float2 UV = UE.TexCoord(Index = 0);
float4 Noise = NoiseTex.Sample(UV);
#pragma endregion
#pragma region Surface
m.BaseColor = BaseColor.rgb * Noise.rgb;
m.Roughness = Roughness;
#pragma endregion
}#pragma layout(…) is the 2.0 Layout statement, and #pragma region / #pragma endregion the
2.0 #Region / #EndRegion.
See also: Layout · Graph layout ·
Regeneration · Decompiler
15. Runtime Virtual Texture: sampling and writing
The RVT nodes have no dedicated builtin family and need none. The sample, replace and feature-switch
nodes are ordinary expressions reached through UE.<ClassName>;
RuntimeVirtualTextureOutput is a custom output with no output pin at all, so it is never the
right-hand side of a Graph assignment — it is bound in Outputs through
Expression( … ).Pin[i].
// 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;
}
}[!NOTE] This example references
/Game/RVT/RVT_Terrain, a projectURuntimeVirtualTextureasset. Substitute one that exists in your project.
| Node | Class specifier | Written as |
|---|---|---|
| Runtime Virtual Texture Sample | RuntimeVirtualTextureSample | UE.… in Graph |
| Runtime Virtual Texture Sample Parameter | RuntimeVirtualTextureSampleParameter | a parameter declaration in Properties |
| Runtime Virtual Texture Replace | RuntimeVirtualTextureReplace | UE.… in Graph |
| Virtual Texture Feature Switch | VirtualTextureFeatureSwitch | UE.… in Graph |
| Runtime Virtual Texture Output | RuntimeVirtualTextureOutput | Expression( … ).Pin[i] in Outputs |
RuntimeVirtualTextureSamplecarries eight outputs:0BaseColor,1Specular,2Roughness,3Normal,4WorldHeight,5Mask,6Displacement,7Mask4. Select one by name withOutput="BaseColor"or by position withOutputIndex=; without a selector the call is read as a node with several outputs — see Output widths.OutputTypeis taken off each call (since 2.0.0); the catalog types the nodes.RuntimeVirtualTextureOutputinput pins, inPin[i]order:0BaseColor,1Specular,2Roughness,3Normal,4WorldHeight,5Opacity,6Mask,7Displacement,8Mask4.VirtualTextureis a reflected asset property, so it takes aPath(...)reference —Path(Game, …),Path(Plugin.<Name>, …)— or a bare quoted object path.MaterialTypeand the node's other enum and bool properties are written by the same reflection pass, with the usual enum spelling rules.- A material that writes to RVT keeps
Domain = "Surface"and adds the output node, exactly as above. The separateDomain = "RuntimeVirtualTexture"spelling maps toMD_RuntimeVirtualTexture, which the engine marks deprecated and hidden; DreamShader still accepts it so existing assets keep decompiling, but new materials should not use it. Backend = "Graph"is pinned here so the result is a plainUMaterial, saved on every build, whose nodes you can open and compare against the source. The nodes themselves do not depend on the backend.
The same in a .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);
}An output is selected by member access (.BaseColor), and an asset property takes a quoted object
path. A custom-output node is a statement of the entry; Pin[i] = … connects input i by its
engine index.
See also: UE.Expression ·
Output bindings · Path(...) ·
Material enums
Running the examples
- Save the files under
<Project>/DShader(or whereverSourceDirectorypoints). - With Auto Compile On Save enabled — the default — the editor compiles a source 0.25 s after it is
saved (Save Debounce Seconds). A
Graph-backend material and every material function, layer and blend are saved to disk by each successful build. AThinCustommaterial — the default backend — is built Ephemeral, in memory, until a cook, an explicit Materialize, a child material instance or the commandlet gives it a package. - Headless compile of one file:
& "<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 -logSubstitute -All for -Source= to compile every source of the project: every .dsf first, then the
other sources in path order; a .dsh is compiled through the files that include it. The
commandlet materializes every ThinCustom product, so it writes a package for each material.
See also: Getting started · Ephemeral materials · Commandlet · Editor integration
See also
- Getting started — installation and the first compile
- DreamShaderLang — the 1.x declaration grammar, block by block
- DreamShaderLang 2.0 — the
.dsslanguage of the second form of each example dsc migrate— rewriting a 1.x source as.dss, proved before it is written- Graph language — the statement and expression language inside
Graph - Builtins —
UE.*,Substrate.*, math, and the HLSL library - Custom Pass examples —
.dsppipelines with their materials and shaders - Parameters — every parameter token, metadata key and
Pathroot - Settings — per-file and project-wide configuration
- Generation — how a source file becomes an asset
- Tools — editor integration, decompiler, commandlet, bridge
- Diagnostics — where diagnostics appear, how to read a code, the 1.x messages
- Testing — the
Tests/Corpusfixtures the.dssforms are modelled on