Recipes
Complete DreamShaderLang materials — animated tint, panning UVs, branching, static-switch variants, translucent UI, PBR, MaterialAttributes, Substrate, parameter collections and a Settings tour.
Each recipe below is a finished .dsm that compiles on its own. They are written to be read top to
bottom: parameters, settings, outputs, graph. Where a construct has a sharp edge, it is called out
next to the code rather than left for you to find.
| Applies to | DreamShaderLang 1.5.0 |
| Engines | UE 5.3 – 5.8; the Substrate recipe needs since UE 5.4 |
| Assumed source root | <Project>/DShader |
| Generated output | in memory by default — see In-memory Materials |
For the language constructs these are assembled from, see Patterns.
Animated tint
UE.Time() gives a scalar clock; sin is one of the math builtins, so it
is called bare and positionally.
// 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;
}
}Writing sin(X) twice produces one Sine node — math builtins are common-subexpression cached.
The registered UE.* builtins are not: two UE.Time() calls create two Time nodes.
Panning UVs
// 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 has both kinds of argument. Coordinate, Time and Speed are input pins and take
expressions — which is why the PanSpeed parameter can drive the pan above. SpeedX, SpeedY and
FractionalPart are literal properties on the node: SpeedX = PanSpeed would quietly write
nothing and the node would pan at its default rate. Registered UE.* builtins do not validate their
argument list, so a misspelling is silently dropped too.
Branching on a threshold
Both branches are built unconditionally into the graph; a UMaterialExpressionIf selects between
them at runtime. The condition must be parenthesised and each body must be braced.
// 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 — wired as x != 0, so a negative value takes the then-branch |
- Both sides of a comparison must evaluate to a scalar.
- A variable written in one branch must also be written in the other, or the merge fails with
Graph if statement could not resolve both branch values for '…'.This includes variables declared only inside one branch. - Branch values must agree in shape, otherwise
Graph if branches assign variable '…' with inconsistent types. - Reading a parameter inside a branch is fine — parameters are never branch outputs.
ifis matched case-sensitively;If (x) { … }is not a conditional.
&& and || do not exist and are silently dropped. if (a > 0 && b > 0) compiles as
if (a > 0) with no diagnostic. Nest two if statements instead. The same truncation applies to
%, ?:, &, |, ^, << and v[i] in ordinary expressions — see
What Graph Is Not.
A static-switch variant
A StaticSwitchParameter is not a runtime branch — it produces a different permutation. Unlike if,
it is written as a call.
// 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= and the positional form all work. What does not work is
if (UseDetail) { … } or a bare Color = UseDetail; — both fail with
Unknown Graph identifier 'UseDetail'. The two branch values must have the same component count.
A translucent UI material
// 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" and ShadingModel = "Unlit" are the pair a UMG material wants; BlendMode is what
makes Base.Opacity meaningful. Every accepted spelling is on
Enum Values.
A Surface PBR skeleton
// 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;
}
}Output variables and binding targets live in different namespaces, but keeping them visibly
distinct (Color → Base.BaseColor) keeps the file readable. The full target catalogue is on
Output Bindings.
Writing MaterialAttributes
Binding Base.MaterialAttributes auto-enables Use Material Attributes on the generated material. A
MaterialAttributes variable is a MakeMaterialAttributes node, and its members are written by
name.
// 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;
// Members can be read back through a BreakMaterialAttributes node.
float Echo = Attrs.Roughness;
Attrs.Specular = Echo;
}
}Attrs must be a graph value before the binding is evaluated. Declare it inside Graph, as
above, or give the Outputs declaration an initializer. MaterialAttributes is a valid type token
in Outputs, Inputs and function signatures, but not in Properties.
- Member names are the material property names (
BaseColor,Metallic,Specular,Roughness,EmissiveColor,Opacity,Normal, …) — the same catalogue as theBase.<X>binding targets. - Arithmetic operators reject
MaterialAttributesoperands:Arithmetic operators cannot be applied to MaterialAttributes values. Base.MaterialAttributesandBase.FrontMaterialcannot both be used by oneShader.
A Substrate surface
since UE 5.4Substrate.* builds Substrate BSDF 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);
}
}- The
Substratetype token, theSubstrate.*namespace andBase.FrontMaterialall require UE 5.4 or newer — not 5.7. Below that, the compile fails withSubstrate builtin call '…' requires Unreal Engine 5.4 or newer. Base.FrontMaterialforce-sets the shading model to Substrate, so an explicitShadingModelsetting must be absent or"Substrate". Anything else fails withBase.FrontMaterial requires ShadingModel="Substrate" or no explicit ShadingModel setting.- Every
Substrate.*argument must be named — the registeredUE.*sugar does not apply here — andClass=is rejected, because each name maps to a fixed expression class. Substrate.*calls are not hoisted out of aGraphFunctionbody; onlyUE.*is.- Substrate values cannot be swizzled, cannot take arithmetic operators, and cannot be selected by an
ifstatement.
Driving a material from a parameter collection
// 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(aliasAsset) andParameter(aliasParameterName) are both required.UE.CollectionParameteris an accepted second spelling of the builtin.- The collection asset is loaded at generation time and the parameter looked up by name in it. A
vector parameter yields
float4, a scalar yieldsfloat1, and anything else is an error. GroupandSortPriorityarguments exist but are silently dropped below since UE 5.7.
This builtin's output width is not authoritative, so it cannot act as the widening partner in a
mixed-width binary operator. Tint * wind works because wind is a scalar; multiplying a vec3 by
a collection vector parameter of unknown width does not get the same rescue. See
Expressions and Conversions.
A multi-output helper
Two or more out parameters mean the statement call form, whose trailing arguments are the names
that receive the results.
// 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;
}
}- The function body is HLSL, so
maxhere is the HLSL intrinsic, not theGraphmath builtin. - A declared return type implies exactly one output and forbids every
outparameter.Function void Name(…, out float r)is therefore an error — drop the return type when you want several results. - The out targets are created by the call; they do not need a prior declaration.
Settings tour: glass with an explicit 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 (deprecated alias for ThinCustom) |
- 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. - Those four keys plus
RenderTypeandDomainare the only hand-handled ones. Every other key is reflected straight ontoUMaterial—TwoSidedandWireframeabove are realUMaterialproperties. An unrecognised key in aShaderSettingsblock is a hard error. - Duplicate keys silently overwrite, last one wins. Repeated
Settingssections merge. - Omitting
Backendfalls back to the project's Default Compiler Backend, which isThinCustom.
Backend = ""; resolves to Graph, not to the project default. Only omitting the key falls
back to the project setting. See Backend.
Running these files
- Save them under
<Project>/DShader, or whereverSourceDirectorypoints. - With Auto Compile On Save enabled — the default — the editor recompiles on save after a 0.25 s
debounce and generates the material in memory. Nothing is written to a
.uassetuntil a cook, an explicit Materialize action, or the commandlet. - To compile one file headlessly:
& "<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 .dsf and .dsm in the project. Unlike the
editor, the commandlet writes persistent assets.
Where to next
Patterns
The file-shaped building blocks of DreamShaderLang — minimal material, parameters, texture sampling, shared headers, functions, layers, GraphFunction and Layout — each one a complete source file.
HLSL Overview
HLSL for DreamShaderLang authors — where HLSL actually runs, how types and signatures map, what a Function body may contain, and the shipped DreamShaderBuiltins.ush helper header.