DreamShaderLang
HLSL / GLSL Background

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.

HLSL is the shader language Unreal's material Custom nodes are written in. DreamShaderLang is not an HLSL compiler, but HLSL is not decorative here either: the body of a Function or GraphFunction is HLSL, emitted more or less verbatim into a generated include and compiled by Unreal.

This page is for readers arriving from HLSL. It says which HLSL knowledge transfers, which does not, and where the boundary sits.

Where HLSL runs, and where it does not

ConstructWhat the text inside it is
Function Name(…) { … }HLSL, emitted verbatim into a generated .ush as DreamShaderFn_<Name>; each call site becomes a UMaterialExpressionCustom node
GraphFunction Name(…) { … }HLSL, with every UE.* call hoisted out into a real material node and wired back as an extra Custom-node input pin
Graph = { … }not HLSL — a small statement language that builds UMaterialExpression nodes
Properties, Inputs, Outputs, Settingsdeclarations, not code

So: reach for HLSL when you want arithmetic the node graph makes tedious, and stay in Graph when you want engine inputs, parameters and bindings. The two meet at the function call.

// plain HLSL
float3 ApplyTint(float3 color, float3 tint)
{
    return color * tint;
}
// the DreamShaderLang declaration of the same helper
Function ApplyTint(in vec3 color, in vec3 tint, out vec3 result) {
    result = color * tint;
}

Both forms exist. A declared return type is legal and lowers to a single output, so Function float3 ApplyTint(in vec3 color, in vec3 tint) { return color * tint; } is the same helper written the HLSL way.

Types

DreamShaderLang has a closed set of type tokens. The HLSL spellings it accepts are these:

HLSL spellingAcceptedNotes
float, float1yes1 component
half, half1yes1 component; a distinct token that behaves like float
int, uint, boolyes1 component; all collapse to the same float width
float2 / float3 / float4yes2 / 3 / 4 components
half2 / half3 / half4yes2 / 3 / 4 components
int2..4, uint2..4, bool2..4yes2 / 3 / 4 components
Texture2D, TextureCube, Texture2DArray, Texture3Dyestexture objects; valid as a Function in parameter, never as a result
VolumeTextureyesrewritten to Texture3D in the generated HLSL — the only such rewrite
SamplerStateyesresolves to a Texture2D object, not to a distinct sampler value
float2x2, float3x3, float4x4nothere are no matrix types; see Matrices
double, double2..4nonot a type token
structnonot a declaration form

Two tokens exist that HLSL does not know at all: MaterialAttributes and StaticBool. They resolve for DreamShaderLang, but the token is emitted into the generated signature verbatim, so a Function using either produces a helper Unreal cannot compile. Use a ShaderFunction or a GraphFunction for that plumbing. The full validity matrix is on Types and Values.

Matrices

There are no matrix types. float2x2 / float3x3 / float4x4 — and therefore mat2 / mat3 / mat4, which normalize to them — are rejected by every declaration position. mat3 is nevertheless accepted lexically in a Function signature, because the signature normalizer rewrites it before validation runs; the declaration parses and the call fails:

Function float3 Rotate(in mat3 basis, in vec3 v) { return mul(basis, v); }
// parses; a call fails with:
//   DreamShader Function 'Rotate' input 'basis' uses unsupported type 'float3x3'.

Matrix-shaped work is reachable through the transform builtins — UE.TransformVector and UE.TransformPosition, on UE.* Nodes.

Signatures

An HLSL function signature and a Function signature look alike, but the rules differ in several places.

RuleHLSLDreamShaderLang Function
Parameter qualifiersin, out, inoutin and out only — inout does not exist and is rejected
Default qualifierinin, when the parameter has only two whitespace-separated tokens
Return typeany, plus voidoptional; declaring one implies exactly one output and forbids every out parameter
Multiple resultsout parameters alongside a returnout parameters and no return type
Overloadsyesno overload resolution; names collide case-insensitively
Default argument valuesnono — opt and defaults belong to ShaderFunction Inputs, not here

A function must produce something: at least one out parameter, or a return type. Neither gives Function '{Name}' must declare at least one out parameter.

Function float Luma(in vec3 color) {
    return dot(color, float3(0.299, 0.587, 0.114));      // one result, HLSL style
}

Function SplitChannels(in vec4 src, out vec3 rgb, out float alpha) {
    rgb   = src.rgb;                                      // two results, out style
    alpha = src.a;
}

Under a declared return type, every return at brace depth 0 is rewritten to an assignment to a synthetic __return variable. A bare return; at depth 0 is a hard error. A return nested inside an if { … } stays a real HLSL return — legal, but it bypasses __return.

Textures and samplers

A texture-typed in parameter gains a companion SamplerState <ParamName>Sampler immediately after it in the generated signature, and every call site passes the matching argument. Use that name to sample:

Function SampleTinted(in Texture2D tex, in vec2 uv, in vec3 tint, out vec3 rgb, out float alpha)
{
    float4 texel = Texture2DSample(tex, texSampler, uv);
    rgb   = texel.rgb * tint;
    alpha = texel.a;
}

SamplerState is not in the set that triggers this expansion. A parameter declared SamplerState S emits SamplerState S into the signature and receives a texture object at the call site.

Control flow

Inside a Function body, HLSL's own control flow applies in full — if, else, for, while, do, switch, break, continue, discard. The body is passed through, so what compiles in a Custom node compiles here.

Inside a Graph block the vocabulary is much smaller: if / else only, with a parenthesised condition and braced bodies. There are no loops.

&& and || do not exist in a Graph block and are silently droppedif (a > 0 && b > 0) compiles as if (a > 0) with no diagnostic. The same truncation applies to %, ?:, &, |, ^, << and v[i]. Inside a Function body all of these are ordinary HLSL and work normally. See What Graph Is Not.

Intrinsics

ContextWhat is available
Function / GraphFunction bodythe full HLSL intrinsic set, plus everything Unreal's Common.ush puts in scope
Graph blockexactly 19 math builtin spellings, plus UE.Expression(…) for anything else

The Graph surface is abs, ceil, clamp, cos, dot, floor, fmod, frac, fract, lerp, max, min, mix, mod, normalize, pow, saturate, sin, sqrt. Notably absent: step, smoothstep, length, cross, reflect, refract, exp, log, and every inverse trigonometric function. Reach them through UE.Expression(Class = …), or write them in a Function body.

Naming your own helper after a math builtin makes it unreachable from Graph, with no diagnostic. A Function called lerp, dot or pow still compiles and still generates — the Graph call site simply resolves to the builtin instead. Constructor names (float3, vec4, …) are reserved the same way.

What happens to your body text

Every Function and GraphFunction body — and nothing else in the language — is passed through an identifier-level rewrite before it is stored. The scan is comment- and string-aware.

WrittenBecomes
vec2 / vec3 / vec4float2 / float3 / float4
ivec2..4, uvec2..4, bvec2..4int2..4, uint2..4, bool2..4
mat2 / mat3 / mat4float2x2 / float3x3 / float4x4
mixlerp
fractfrac
modfmod
A::BA_B

The match is on the whole identifier, ignoring case. A local variable, helper or struct member named Mix, Mod, Fract, Vec3 or Mat4 inside a body is silently renamed. There is no diagnostic; the failure surfaces as an HLSL compile error, or as silently different math. Rename the identifier — MixColor, ModValue.

The full alias list, in both directions, is on GLSL Overview.

DreamShaderBuiltins.ush

The plugin ships one HLSL header of its own. It defines DS_* macros and functions that mirror the HLSL the material translator emits for the corresponding UE.* nodes.

On disk<Plugin>/Shaders/DreamShaderBuiltins.ush
Virtual shader path/Plugin/DreamShader/DreamShaderBuiltins.ush
Include guardDREAMSHADER_BUILTINS_USH
Defines25 DS_* symbols — 22 macros and 3 functions
Emitted by the pluginno
#include "/Plugin/DreamShader/DreamShaderBuiltins.ush"

At module startup DreamShader registers a shader source directory mapping from the virtual directory /Plugin/DreamShader to the plugin's Shaders folder, unless a mapping for that virtual directory already exists. The mapping is unconditional — it does not depend on any project setting, backend choice or engine version — so the path above always resolves for the engine's shader preprocessor. It is a separate mapping from the one used for generated per-source helper includes.

Nothing in the plugin currently emits an include for this header, and nothing in the plugin references any DS_* symbol. Neither backend includes it; the generated per-source .ush contains only the DreamShaderFn_* definitions produced from your Function blocks. The header is reachable only from a hand-written #include in a Function HLSL body.

The header's own comment describes it as being included by generated DSI_*.ush files. That was the arrangement used by the retired Instance backend; the resolved backend set is now Graph and ThinCustom, no DSI_*.ush is produced any more, and the comment describes a path that no longer runs. See Backend.

Symbols

Parameters is the material parameter struct in scope at the include site; Tex is a texture parameter identifier, from which the sampler name is derived by token pasting (Tex##Sampler).

SymbolKindExpansionNode equivalent
DS_TIMEmacro(View.GameTime)UE.Time()
DS_REAL_TIMEmacro(View.RealTime)
DS_DELTA_TIMEmacro(View.DeltaTime)
DS_PERIODIC_TIME(Period)macro(fmod(View.GameTime, (Period)))UE.Time(Period=…)
DS_TexCoord(Parameters, CoordinateIndex)float2 functionParameters.TexCoords[CoordinateIndex].xy, or float2(0, 0) when NUM_TEX_COORD_INTERPOLATORS is 0UE.TexCoord
DS_VertexColor(Parameters)float4 functionParameters.VertexColorUE.VertexColor
DS_CameraVector(Parameters)macro((Parameters).CameraVector)UE.CameraVector
DS_ReflectionVector(Parameters)macro((Parameters).ReflectionVector)UE.ReflectionVector
DS_PixelNormalWS(Parameters)macro((Parameters).WorldNormal)UE.PixelNormalWS
DS_VertexNormalWS(Parameters)macro((Parameters).TangentToWorld[2])UE.VertexNormalWS
DS_TwoSidedSign(Parameters)macro((Parameters).TwoSidedSign)UE.TwoSidedSign
DS_ViewportUV(Parameters)macro(GetViewportUV(Parameters))UE.ViewportUV
DS_PixelDepth(Parameters)macro(GetPixelDepth(Parameters))UE.PixelDepth
DS_WorldPosition(Parameters)macro(WSDemote(GetWorldPosition(Parameters)))UE.WorldPosition
DS_TranslatedWorldPosition(Parameters)macro(GetTranslatedWorldPosition(Parameters))UE.TranslatedWorldPosition
DS_ObjectPosition(Parameters)macro(WSDemote(GetObjectWorldPosition(Parameters)))UE.ObjectPosition
DS_ObjectRadius(Parameters)macro(GetPrimitiveData(Parameters).ObjectRadius)UE.ObjectRadius
DS_ObjectBounds(Parameters)macro(float3(GetPrimitiveData(Parameters).ObjectBoundsX, …ObjectBoundsY, …ObjectBoundsZ))UE.ObjectBounds
DS_CameraPosition(Parameters)macro(WSDemote(GetWorldCameraOrigin(Parameters)))UE.CameraPosition
DS_PerInstanceRandom(Parameters)macro(GetPerInstanceRandom(Parameters))UE.PerInstanceRandom
DS_PerInstanceFadeAmount(Parameters)macro(GetPerInstanceFadeAmount(Parameters))UE.PerInstanceFadeAmount
DS_Panner(UV, Time, Speed)float2 functionUV + float2(frac(Time * Speed.x), frac(Time * Speed.y))UE.Panner
DS_SampleTexture2D(Tex, UV)macroTexture2DSample(Tex, Tex##Sampler, UV)SampleTexture2D(tex, uv) in a Graph block
DS_SampleTexture2DLod(Tex, UV, Lod)macroTexture2DSampleLevel(Tex, Tex##Sampler, UV, Lod)
DS_SampleTexture2DBias(Tex, UV, Bias)macroTexture2DSampleBias(Tex, Tex##Sampler, UV, Bias)

The WSDemote calls lower the engine's large-world-coordinate vectors to float3. The translated (camera-relative) position form is already float3 and keeps precision near the camera.

Using it safely

An #include in a Function body lands inside a function. A Function block's HLSL is emitted verbatim between the braces of the generated DreamShaderFn_* definition, so the #include — and therefore the header's contents — is inserted at that point. The 22 macros are unaffected; a #define is legal anywhere. The three function definitions (DS_TexCoord, DS_VertexColor, DS_Panner) are not legal inside another function body. Restrict a hand-written include to bodies that use only the macro half, or copy the wanted function's body inline.

DS_TexCoord and DS_VertexColor depend on translator side effects that nothing arranges any more. DS_TexCoord reads Parameters.TexCoords[…], which exists only when at least one interpolator slot was allocated during material translation; with none allocated the function compiles and returns float2(0, 0). DS_VertexColor reads Parameters.VertexColor, which is only fed when the material was translated with vertex-colour usage set. The retired Instance backend arranged both; the Graph and ThinCustom backends do not. Use UE.TexCoord and UE.VertexColor, or a wired Function input, instead of these two.

  • The remaining reads are side-effect free. Every field and Get*(Parameters) helper they touch is populated unconditionally at pixel entry, so they compile in any opaque Surface pixel evaluation with no usage flag or interpolator request.
  • DS_PerInstanceRandom and DS_PerInstanceFadeAmount are meaningful only for instanced or GPU-culled draws. On a plain mesh they read back safe constants (0 and 1) with no compile error.
  • The include guard makes repeated inclusion in one translation unit harmless.
  • The GLSL-alias rewrite skips string literals, so the path inside #include "…" is never touched.
Function vec2 Wobble(in vec2 uv, in float speed)
{
    #include "/Plugin/DreamShader/DreamShaderBuiltins.ush"
    float t = DS_TIME * speed;
    return uv + float2(sin(t), cos(t)) * 0.02;
}

The node-based equivalent needs no include and no HLSL helper at all:

GraphFunction vec2 Wobble(in vec2 uv, in float speed)
{
    float t = UE.Time() * speed;
    return uv + vec2(sin(t), cos(t)) * 0.02;
}

Common HLSL keywords

For reference — this is HLSL's own vocabulary, valid inside a Function body. The Also in DreamShaderLang column marks what the declaration grammar recognises outside a body.

CategoryKeywordsAlso in DreamShaderLang
Typesvoid, bool, int, uint, half, float, doubleall but void and double, as type tokens
Control flowif, else, for, while, do, switch, case, defaultif / else in Graph; default as a call argument
Jumpsreturn, break, continue, discardreturn in a return-typed Function body
Parametersin, out, inoutin and out only
Modifiersconst, static, uniform, groupsharedconst in a Properties declaration, with a different meaning
TexturesTexture2D, TextureCube, SamplerState, SamplerComparisonStatethe first three, as type tokens
Aggregatesstruct

Where to next

On this page