DreamShaderLang
Language

Top-level Blocks

The seven top-level blocks — Shader, ShaderFunction, ShaderLayer / ShaderLayerBlend, VirtualFunction, Function, GraphFunction and Namespace — and what each one generates.

A source file is a flat list of top-level blocks. They may appear in any order, there is no separator between them, and — apart from Shader — any number of each may appear in one translation unit. One compile of a file generates every asset every one of its blocks describes.

The block keywords are the only case-sensitive tokens in the language. shader(…) and SHADER(…) match nothing and fail with Unexpected token near index {Index}. See Lexical Elements.

Synopsis

<top-level-block> := { Shader | ShaderFunction | ShaderLayer | ShaderLayerBlend
                     | MaterialLayer | MaterialLayerBlend | VirtualFunction | Namespace }
                     ( <attribute> = <value> [, …] ) { <section>… }
                   | { Function [ SelfContained | Inline ] | GraphFunction }
                     [<return-type>] <name> ( [<parameter>, …] ) { <HLSL> }

<section>          := <section-name> [=] { <statement>… } [;]
NotationMeaningExample
<x>Placeholder — substitute a real value; the angle brackets are not typed.Name = <string>
[ x ]Optional — the whole group may be left out.[, Root = <string>]
{ a | b }Choice — take exactly one of the alternatives separated by |.{ Node( … ) | Comment( … ) }
Repetition — the preceding item may appear any number of times.<property-declaration> …

Two header shapes, then. The five asset-shaped blocks take a ( Key = Value ) attribute list and a body of sections; the two function-shaped blocks take a C-style signature and a body of raw HLSL.

What each block generates

BlockHeaderGeneratesSince
ShaderShader(Name = "…"[, Root = "…"])UMaterial, or a UDreamShaderMaterialInstance over a hidden base under the ThinCustom backend
ShaderFunctionShaderFunction(Name = "…"[, Root = "…"])UMaterialFunction (usage Default)
ShaderLayerShaderLayer(Name = "…"[, Root = "…"])UMaterialFunctionMaterialLayersince 1.3.0
ShaderLayerBlendShaderLayerBlend(Name = "…"[, Root = "…"])UMaterialFunctionMaterialLayerBlendsince 1.3.0
VirtualFunctionVirtualFunction(Name = "…"[, Asset = "…"])nothing — declares an existing assetsince 1.2.0
FunctionFunction [SelfContained | Inline] [<ret>] <Name>( … )one HLSL helper in the generated .ush include; a Custom node per call
GraphFunctionGraphFunction [<ret>] <Name>( … )nothing by itself; a Custom node plus hoisted material nodes per callsince 1.3.1
NamespaceNamespace(Name = "…")nothing — prefixes its members' names with Ns::

Name is required on every attribute-taking block. Root defaults to /Game; the full path grammar is on Asset Paths.

Shader

Declares one material: its parameters, its render state, its output bindings and the graph feeding them.

Shader(Name = "<asset-path>" [, Root = "<root>"])
{
    [Properties [=] { <property-declaration> ; … }]
    [Settings   [=] { <key> = <value> ; … }]
    [Outputs    [=] { { <output-declaration> | <output-binding> } ; … }]
    [Graph      [=] { <graph-statement> … }]
    [Layout     [=] { { Node( … ) | Comment( … ) } ; … }]
}
Shader(Name="Materials/M_Emissive", Root="Game")
{
    Properties = {
        Group("Look") {
            vec3  Tint      = vec3(1.0, 0.4, 0.1) [Description="Emissive tint"];
            float Intensity = 2.0                 [Slider(0, 10)];
        }
        TextureSampleParameter2D BaseTex = Path(Game, "Textures/T_Noise");
    }

    Settings = {
        ShadingModel = "Unlit";
        BlendMode    = "Translucent";
        TwoSided     = true;
    }

    Outputs = {
        vec3  Color;
        float Alpha;

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

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

Rules that only apply here:

  • .dsm only. A .dsh or .dsf whose text contains Shader( is rejected before parsing.
  • At most one per translation unit, import closure included.
  • A Graph block is required unless at least one output declaration carries an initializer since 1.3.4. Otherwise the parse fails with Shader must provide a Graph block.
  • Bindings are what make a material. A Shader with no Base.* or Expression(…) binding parses with a warning and then fails generation with {File}: Outputs block is required.
  • Binding Base.MaterialAttributes auto-enables Use Material Attributes. Binding Base.FrontMaterial force-sets the shading model to Substrate and needs since UE 5.4. The two cannot be used by the same Shader.
  • Inputs, Results and Options are not sections a Shader knows; Code is a hard error.

Which UClass lands at the target path depends on the resolved backend. ThinCustom — the default — writes a UDreamShaderMaterialInstance whose parent is a hidden UMaterial subobject named MB_DreamThinBase_<leaf>; Graph writes a plain UMaterial. Instance is a deprecated alias of ThinCustom. See Backend.

ShaderFunction

Declares one reusable UMaterialFunction: typed input and output pins, function-local parameter nodes, and the graph connecting them.

ShaderFunction(Name = "<asset-path>" [, Root = "<root>"])
{
    [Properties            [=] { <property-declaration> ; … }]
    [Inputs                [=] { <parameter-declaration> ; … }]
    { Outputs | Results }  [=] { <parameter-declaration> ; … }
    Graph                  [=] { <graph-statement> … }
    [Settings              [=] { <key> = <value> ; … }]
    [Layout                [=] { { Node( … ) | Comment( … ) } ; … }]
}
ShaderFunction(Name="Functions/F_Tint", Root="Game")
{
    Properties = {
        Group("Tint") {
            float Boost = 1.0 [Slider(0, 4); Description="Extra gain applied after tinting"];
        }
        const float Epsilon = 0.001;
    }

    Inputs = {
        vec3  InColor;
        vec3  InTint             [Description="Multiplied with InColor"];
        opt float Strength = 1.0 [Description="Blend amount"; SortPriority=10];
    }

    Outputs = {
        vec3  OutColor;
        float OutLuma;
    }

    Graph = {
        vec3 Tinted = InColor * InTint * Boost;
        OutColor    = lerp(InColor, Tinted, Strength);
        OutLuma     = dot(OutColor, vec3(0.2126, 0.7152, 0.0722)) + Epsilon;
    }
}

Graph and at least one output are required. Everything else is optional. Properties here declares nodes inside the function; Inputs declares pins on it — see Sections for the difference, which is the most common confusion in this block.

Across a regeneration the Id GUID of every input and output pin is cached by name and restored since 1.3.2, so existing hand-authored MaterialFunctionCall nodes keep their wiring — as long as the pin name is unchanged. Renaming an input is equivalent to deleting it: call sites lose that connection.

ShaderLayer and ShaderLayerBlend

since 1.3.0

The material-layer variants of ShaderFunction. They share its body parser and its section table; what distinguishes them are five arity rules checked at generation.

ShaderLayer(Name = "<asset-path>" [, Root = "<root>"])
{
    [Inputs                [=] { MaterialAttributes <name> ; }]
    { Outputs | Results }  [=] { MaterialAttributes <name> ; }
    Graph                  [=] { <graph-statement> … }
}

ShaderLayerBlend(Name = "<asset-path>" [, Root = "<root>"])
{
    Inputs                 [=] { MaterialAttributes <name> ; MaterialAttributes <name> ; }
    { Outputs | Results }  [=] { MaterialAttributes <name> ; }
    Graph                  [=] { <graph-statement> … }
}
KindInputsOutputs
ShaderLayerat most one, and it must be MaterialAttributesexactly one, MaterialAttributes
ShaderLayerBlendexactly two, both MaterialAttributesexactly one, MaterialAttributes

Scalars, vectors and textures cannot be layer or blend inputs. Expose them through Properties instead: they become parameter nodes inside the generated function and appear on the layer stack's parameter panel. Both diagnostics say so explicitly — … Use Properties for layer controls.

ShaderLayer(Name="Layers/L_Rust", Root="Game")
{
    Properties = {
        Group("Rust") {
            vec3  RustColor = vec3(0.35, 0.13, 0.05);
            float RustRough = 0.85 [Slider(0, 1)];
        }
    }

    Outputs = { MaterialAttributes Attrs; }

    Graph = {
        Attrs.BaseColor = RustColor;
        Attrs.Roughness = RustRough;
        Attrs.Metallic  = 0.0;
    }
}

On since UE 5.7 each MaterialAttributes input of a blend gets a BlendInputRelevance derived from its name — Top / TopLayer map to Top, Bottom / BottomLayer / Base / BaseLayer map to Bottom. Anything else falls back to position: the first input becomes Bottom, the second Top. On UE 5.3–5.6 the property does not exist and nothing is written.

Deprecated spellings

Deprecated since 1.3.0

Use ShaderLayer instead.

MaterialLayer(...) and MaterialLayerBlend(...) still parse and still generate exactly the same assets, but each pushes a parse warning that is appended to the compile message: MaterialLayer is deprecated; use ShaderLayer instead. and MaterialLayerBlend is deprecated; use ShaderLayerBlend instead.

Two consequences of the aliasing are worth knowing. A missing Name reports the spelling you actually typed (MaterialLayer(Name="...") is required.), while every other diagnostic reports the modern kind name — a MaterialLayer with two inputs fails with ShaderLayer '{Function}' must declare at most one input, …, never MaterialLayer ….

VirtualFunction

since 1.2.0

Declares an existing UMaterialFunction — its path and its pin signature — so a Graph can call it. It generates nothing and validates nothing until a call site needs it.

VirtualFunction(Name = "<call-name>" [, Asset = "<asset-reference>"])
{
    [{ Options | Settings }   [=] { Asset = <asset-reference> ; … }]
    [{ Inputs | Properties }  [=] { <parameter-declaration> ; … }]
    { Outputs | Results }     [=] { <parameter-declaration> ; … }
}
VirtualFunction(Name="BufferWriter")
{
    Options = {
        Asset       = Path(Game, "MaterialFunctions/F_BufferWriter");
        Description = "Existing material function declared for Graph calls.";
    }

    Inputs = {
        vec3  Color;
        float Alpha;
        opt float Exposure = 1.0;
    }

    Outputs = {
        vec3  Result;
        float Coverage;
    }
}
  • Name is a call name, not an asset path. There is no Root attribute — the package root is part of the asset reference.
  • The header Asset= attribute wins over Options.Asset; the section value is consulted only when the attribute is absent or blank.
  • Graph and Code are hard errors: this block declares an asset, it does not build one.
  • A file holding only VirtualFunction blocks compiles successfully with DreamShader file '{File}' contains VirtualFunction declarations only; no assets were generated.

Inside a VirtualFunction, and only here, Properties is an alias for Inputs. It gets the typed-parameter grammar, not the parameter-node grammar. A declaration such as const float X = 1; that is legal in a Shader fails here with VirtualFunction '{Name}' input 'X' uses unsupported type 'const float'.

An unquoted attribute value ends at the first , or ). Asset=Path(Game, "F/X") in the header truncates to Path(Game and fails with Expected identifier near index {Index}. Put Path(...) forms in Options instead.

Function

An HLSL helper. The body is emitted verbatim into a generated .ush include as DreamShaderFn_<Name>, and every call site becomes a UMaterialExpressionCustom node.

Function [ { Inline | SelfContained } ] [ <return-type> ] <name> ( [ <parameter-list> ] )
{
    <hlsl>
}
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);
}

There is no attribute header, no Settings, and no sections of any kind — the { … } after the parameter list is raw HLSL. Full parameter, return-type and emission rules are on Functions.

GraphFunction

since 1.3.1

Looks exactly like a Function, but its UE.* calls are lifted out of the text, built as real material nodes, and wired into the generated Custom node as auto-named input pins.

GraphFunction [ <return-type> ] <name> ( [ <parameter-list> ] )
{
    <hlsl-with-UE-calls>
}
GraphFunction WindPulse(in float2 uv, out float pulse)
{
    float t = UE.Time();
    pulse = sin(uv.x * 8.0 + t);
}

Inline and SelfContained are not modifiers here. The modifier branch is skipped for GraphFunction, so the token is consumed as a return type, and the failure surfaces as DreamShader GraphFunction 'Foo' has unsupported result type 'SelfContained'. (or, with any out parameter, as a return-type-plus-out parse error). There is no self-contained mode for GraphFunction.

Namespace

Prefixes the names of the Function and GraphFunction declarations it contains with <Name>::.

Namespace(Name = "<identifier>")
{
    { <function-declaration> | <graph-function-declaration> } …
}
Namespace(Name="Common")
{
    Function ApplyTint(in vec3 color, in vec3 tint, out vec3 result) {
        result = color * tint;
    }
}

A Namespace is not an entity: no object is stored for it and it creates no scope. Its only effect is on the member's recorded name. It may not nest, and it may contain nothing but those two block kinds — anything else fails with Namespace '{Name}' may only contain Function or GraphFunction blocks. See Functions.

Comparison

ShaderShaderFunctionShaderLayer(Blend)VirtualFunctionFunctionGraphFunctionNamespace
Writes a .uassetyesyesyesnononono
Allowed in .dshnononoyesyesyesyes
Multiplicity per unit1anyanyanyanyanyany
BodysectionssectionssectionssectionsHLSLHLSLblocks
Propertiesparameter nodesparameter nodesparameter nodesalias for Inputs
Graph requiredunless an output is initializedyesyesrejected
Callable from Graphnoyesyesyesyesyesthrough its members
Value-callableyesyesyessingle-output onlysingle-output only

Diagnostics

MessageCauseFix
Unexpected token near index {Index}.Text at top level that is not one of the ten block keywords — including a correctly spelled keyword in the wrong case.Check capitalisation; block keywords are case-sensitive. Details
{Block}(Name="...") is required.A block header with no Name attribute. {Block} is the spelling you actually typed.
Only one top-level Shader block is currently supported.A second Shader block anywhere in the import closure.Details
Shader must provide a Graph block.No Graph section and no initialized output declaration.
Unknown shader section '{Section}'.A section a Shader does not accept — Inputs, Results and Options among them.Details
Unknown material function section '{Section}'.A section a ShaderFunction / ShaderLayer / ShaderLayerBlend does not accept.Details
Unknown VirtualFunction section '{Section}'.A section a VirtualFunction does not accept.
VirtualFunction declares an existing MaterialFunction asset and does not support Graph or Code sections.A Graph or Code section inside a VirtualFunction.
ShaderLayer '{Function}' must declare at most one input, and it must be MaterialAttributes. Use Properties for layer controls.Two or more inputs, or any non-MaterialAttributes input, on a layer.Move the control into Properties.
ShaderLayerBlend '{Function}' must declare exactly two inputs, both MaterialAttributes. Use Properties for blend controls.An input count other than two, or any non-MaterialAttributes input, on a blend.
{Kind} '{Function}' must declare exactly one MaterialAttributes output.More than one output, or an output that is not MaterialAttributes, on a layer or blend.
Namespace '{Name}' may only contain Function or GraphFunction blocks.Any other token in a Namespace body — a nested Namespace included.
Namespace name '{Name}' is not a valid identifier.An illegal character in the namespace name, including ::, ., - and space. There is no multi-segment declaration form.
Shader graph sections now use Graph = { ... }. Function Code = { ... } is still supported.A Code section was used. Despite the message, no reachable grammar accepts Code.Use Graph.

Where to next

On this page