DreamShaderLang
Builtins

Math Builtins

The 29 unprefixed HLSL-spelled call names a Graph block lowers to arithmetic material nodes.

Twenty-nine unprefixed, HLSL-spelled call names are lowered directly to arithmetic UMaterialExpression nodes. They cover 26 operations — three of the names are aliases. Each call produces one node, with two exceptions: reflect and refract, which Unreal has no expression for, expand into a small subgraph.

float s     = sin(X);
float sa    = saturate(X);
vec3  mixed = lerp(A, B, sa);
vec3  unit  = normalize(mixed);
float d     = dot(unit, A);

These are called bare — saturate(x), not UE.saturate(x) — and they are the one builtin surface whose arguments are positional. They may be written inside a Graph body, in an Outputs binding expression, or in an Outputs declaration initializer.

Every name is matched case-insensitively: SATURATE(x), Lerp(a, b, t) and Sin(x) all resolve.

These 29 names are reserved and shadow your own code silently. A Function, GraphFunction, ShaderFunction, VirtualFunction or property named lerp, clamp, dot, min, max, pow, abs — or any other spelling in the catalogue — is unreachable from a Graph block. The builtin wins during name resolution and no diagnostic is emitted.

The declaration still compiles and still generates its asset; only the Graph call site is redirected. Rename the user symbol, or call it from a Function body instead of a Graph block.

1.6.0 widened this namespace from 19 names to 29 since 1.6.0. Existing sources that declare a length, step, cross or atan of their own need renaming.

Constructor names (float3, vec4, int2, …) are reserved the same way, and are tested even earlier.

Synopsis

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> …
{ abs | acos | asin | atan | ceil | cos | floor | frac | fract | length
| normalize | saturate | sin | sqrt } ( <x> )
{ atan2 | cross | dot | fmod | max | min | mod | pow | reflect | step } ( <x> , <y> )
{ clamp | lerp | mix | refract | smoothstep } ( <x> , <y> , <z> )

Catalogue

One row per accepted spelling. Return width is the component count the generator assigns to the result; Authoritative is whether that width counts for the widening rules in Expressions and conversions.

SpellingArityLowers toInput pins wired, in orderReturn widthAuthoritative
abs1UMaterialExpressionAbsInputwidth of the argumentinherited from the argument
acos since 1.6.01UMaterialExpressionArccosineInputwidth of the argumentinherited from the argument
asin since 1.6.01UMaterialExpressionArcsineInputwidth of the argumentinherited from the argument
atan since 1.6.01UMaterialExpressionArctangentInputwidth of the argumentinherited from the argument
atan2 since 1.6.02UMaterialExpressionArctangent2Y ← argument 1, X ← argument 2max of both argumentsset when either argument had it
ceil1UMaterialExpressionCeilInputwidth of the argumentinherited from the argument
clamp3UMaterialExpressionClampInput, Min, Maxwidth of argument 1from argument 1
cos1UMaterialExpressionCosineInputwidth of the argumentinherited from the argument
cross since 1.6.02UMaterialExpressionCrossProductA, Balways 3always set
dot2UMaterialExpressionDotProductA, Balways 1always set
floor1UMaterialExpressionFloorInputwidth of the argumentinherited from the argument
fmod since 1.5.02UMaterialExpressionFmodA ← dividend, B ← divisorwidth of argument 1from argument 1
frac1UMaterialExpressionFracInputwidth of the argumentinherited from the argument
fract since 1.5.01UMaterialExpressionFracInputwidth of the argumentinherited from the argument
length since 1.6.01UMaterialExpressionLengthInputalways 1always set
lerp3UMaterialExpressionLinearInterpolateA, B, Alphamax of arguments 1 and 2set when either of arguments 1, 2 had it
max2UMaterialExpressionMaxA, Bmax of both argumentsset when either argument had it
min2UMaterialExpressionMinA, Bmax of both argumentsset when either argument had it
mix3UMaterialExpressionLinearInterpolateA, B, Alphamax of arguments 1 and 2set when either of arguments 1, 2 had it
mod since 1.5.02UMaterialExpressionFmodA ← dividend, B ← divisorwidth of argument 1from argument 1
normalize1UMaterialExpressionNormalizeVectorInputwidth of the argumentinherited from the argument
pow2UMaterialExpressionPowerBase, Exponentwidth of argument 1from argument 1
reflect since 1.6.02a 4-node subgraph — see the note belowmax of both argumentsset when either argument had it
refract since 1.6.03a 14-node subgraph — see the note belowmax of arguments 1 and 2set when either of arguments 1, 2 had it
saturate1UMaterialExpressionSaturateInputwidth of the argumentinherited from the argument
sin1UMaterialExpressionSineInputwidth of the argumentinherited from the argument
smoothstep since 1.6.03UMaterialExpressionSmoothStepMin, Max, Valuemax of all three argumentsset when any argument had it
sqrt1UMaterialExpressionSquareRootInputwidth of the argumentinherited from the argument
step since 1.6.02UMaterialExpressionStepY ← argument 1 (edge), X ← argument 2 (value)max of both argumentsset when either argument had it

Aliases

Three alias pairs. The two spellings in each pair are interchangeable and produce identical nodes — neither is deprecated.

PairNodeNote
lerp / mixLinearInterpolatemix is the GLSL spelling
frac / fractFracfract is the GLSL spelling since 1.5.0
fmod / modFmodmod is the GLSL spelling since 1.5.0

Inside a Function HLSL body the identifier mod is rewritten to fmod by the GLSL-alias pass. In a Graph block both spellings are accepted directly, with no rewrite.

Argument rules

These apply identically to every builtin above.

#RuleConsequence when violated
1Arity is exact — no defaults, no optional arguments, no varargsMath function '{Name}' expects exactly {N} argument(s).
2Every argument is positional; a named argument is not acceptedreported as an arity error, see below
3Each argument is evaluated as a full Graph expression, nested builtin calls includedthe inner error is wrapped as Math function '{Name}' argument {Index}: {Error}
4Texture-object values are rejectedMath function '{Name}' only accepts numeric scalar/vector arguments.
5MaterialAttributes values are rejectedsame message
6Substrate values are rejectedsame message
7Argument component counts are not checked, widened or broadcastnothing here; the mismatch surfaces later as an Unreal material-translation error

Rule 7 is the one to watch. dot(vec3Value, floatValue) is accepted by DreamShader without a diagnostic and then fails during Unreal's own shader compile. Unlike the arithmetic operators, this path has no scalar/vector compatibility test — compare Expressions and conversions.

Named arguments

Every arity guard is evaluated as "argument count is wrong or an argument is named", and both outcomes emit the arity message.

Passing a named argument to a math builtin reports an arity error, not a namedness error. saturate(Input = X) — one argument, correctly named after the node's pin — fails with Math function 'saturate' expects exactly 1 argument.

The fix is to drop the name: saturate(X). Named arguments are a UE.* / Substrate.* feature, not a math-builtin feature.

Name resolution

Math-builtin names are resolved before any user-declared name. The Graph call dispatcher tests, in order:

#CandidateReference
1vector/scalar constructor names (float3, vec4, int2, …)Expressions
2UE.SceneTextureUE.* nodes
3any UE.-prefixed calleeUE.* nodes
4any Substrate.-prefixed calleeSubstrate
5math builtins — this page
6SampleTexture2DUE.Expression
7declared properties, in the parameter pin-call formProperty types
8Function, GraphFunction, ShaderFunction, VirtualFunctionCalls

A misspelled builtin is not reported as a math error. saturte(x) falls through all eight steps and is reported by the call path as Unknown Graph function 'saturte'.

Per-builtin notes

clamp

clamp(Input, Min, Max) wires all three arguments and leaves the node's ClampMode at its default, CMODE_Clamp. For CMODE_ClampMin or CMODE_ClampMax, go through the generic form:

UE.Expression(Class = "Clamp", OutputType = "float1", Input = x, Min = a, ClampMode = "CMODE_ClampMin")

dot

The only builtin with a fixed return width. dot always produces a 1-component, authoritative result regardless of the argument widths, so float d = dot(A, B); needs no swizzle.

fmod, mod

Argument 1 is the dividend and argument 2 the divisor; they are wired to the node's A and B pins respectively. The result takes the dividend's width.

The decompiler has no case for UMaterialExpressionFmod. An existing Fmod node exports as a generic UE.Expression(Class="Fmod", …) call rather than as fmod(…). The exported source is equivalent; it simply does not round-trip to the builtin spelling.

lerp, mix

The result width is max of arguments 1 and 2 — the Alpha argument does not participate. A scalar Alpha blending two vec3 values yields a vec3.

min, max

The two names share one implementation and differ only in the node class selected. Both take the max of the two argument widths.

normalize

The only builtin whose input pin is not named Input. Inputs on this path are bound by reflected property name, and UMaterialExpressionNormalize names its pin VectorInput. The difference is invisible at the call site — normalize(N) — but it does appear in the two could not bind input / failed to access input diagnostics.

sin, cos

Both leave the node's Period property at its default. For a non-default period:

UE.Expression(Class = "Sine", OutputType = "float1", Input = x, Period = 2.0)

step

since 1.6.0

step(edge, x) returns x >= edge ? 1 : 0, as in HLSL. UMaterialExpressionStep names its pins the other way round — Y is the edge and X is the value — so argument 1 wires to Y and argument 2 to X. Writing the node form by hand, the equivalent call is:

UE.Expression(Class = "Step", OutputType = "float1", Y = edge, X = x)

smoothstep

since 1.6.0

smoothstep(min, max, x), argument order as in HLSL, wired straight to the node's Min, Max and Value pins.

length, cross

since 1.6.0

The two builtins besides dot with a fixed return width: length is always 1 component and cross always 3, whatever the arguments were. Both widths are authoritative, and they match what the same classes already report when reached through UE.Expression.

asin, acos, atan, atan2

since 1.6.0

The four inverse-trigonometric nodes. atan2(y, x) takes its arguments in HLSL order and the node's pins are already named Y and X, so the mapping is direct.

The engine also ships ArcsineFast, ArccosineFast, ArctangentFast and Arctangent2Fast — cheaper approximations valid over a limited input range. They have no builtin spelling; reach them with UE.Expression(Class = "ArcsineFast", OutputType = "float1", Input = x).

reflect, refract

since 1.6.0

The only two builtins with no node behind them. Unreal has no Reflect or Refract UMaterialExpression, so both are lowered to the arithmetic HLSL defines them as, and the value returned to the caller is the final node of that subgraph.

reflect(i, n) becomes i - 2 * dot(i, n) * n — four nodes (DotProduct, two Multiply, Subtract). The literal 2 rides Multiply's ConstB rather than costing a Constant node.

refract(i, n, eta) becomes the full HLSL definition — fourteen nodes:

k = 1 - eta*eta * (1 - dot(n, i)^2)
k < 0 ? 0 : eta*i - (eta*dot(n, i) + sqrt(k)) * n

The total-internal-reflection test is an If node, so both sides are translated and one is selected; sqrt of a negative k lands only on the discarded side, exactly as in HLSL. k == 0 still satisfies the formula (sqrt(0) == 0) and takes the refracted side. The zero branch is built as i * 0 rather than a constant so that its type always equals i's — If requires its two branches to agree, and the tracked component count cannot always guarantee a hand-picked constant would.

Both are node-count-expensive by construction, and neither is common enough in a Graph block to be worth a graph that large. Where the surrounding code is already HLSL, a Function body still does it in one node.

Node reuse

Results are common-subexpression cached. Two textually identical calls over identical operand values — sin(X) written twice — produce one Sine node, not two. The cache key covers the builtin name, the node class and every argument value.

Math builtins share this behaviour with the generic UE.Expression path and the Substrate.* wrappers. The registered UE.* builtins are the exception: they create a fresh node per call.

Every math node is created at editor X coordinate 360, with Y taken from the generator's running layout counter.

What is not here

Matrices are not here, and are not addable. The Unreal material graph has no matrix value type at all, so mul(M, v) has no Graph spelling regardless of what the DSL does. Space conversions go through UE.Expression(Class = "Transform") or "TransformPosition"; everything else goes in a Function HLSL body.

Still absent but reachable through UE.Expression, each having a node: exponential, logarithmic, tan, sign, round, trunc and distance. For example:

UE.Expression(Class = "Distance", OutputType = "float1", A = u, B = v)

Inside a Function HLSL body these names are not handled by this dispatcher at all — the body is emitted verbatim and HLSL's own intrinsics apply.

The decompiler emits these spellings when exporting an existing material: LinearInterpolatelerp, Clamp (when ClampMode == CMODE_Clamp) → clamp, Powerpow, DotProductdot, Normalizenormalize, Min/Maxmin/max, Absabs, Saturatesaturate, Floor/Ceil/Frac/SquareRootfloor/ceil/frac/sqrt, and Sine/Cosine (when Period is 1.0) → sin/cos.

The ten spellings added in 1.6.0 were not added to the export side: Step, SmoothStep, Length, CrossProduct and the four inverse-trigonometric nodes export as generic UE.Expression(Class="…", …) calls — the same asymmetry Fmod already has. reflect and refract cannot round-trip at all, since what they leave in the graph is ordinary arithmetic nodes with nothing marking their origin.

Diagnostics

{Name} is the spelling as the author wrote it, so its casing is preserved. {Index} is 1-based.

MessageCauseFix
Math function '{Name}' expects exactly 1 argument.Wrong argument count for a 1-argument builtin, or any argument was named.Drop the argument names — this surface is positional only.
Math function '{Name}' expects exactly 2 arguments.The same, for dot, pow, min, max, fmod, mod, step, cross, atan2 and reflect.
Math function '{Name}' expects exactly 3 arguments.The same, for lerp, mix, clamp, smoothstep and refract.
Math function '{Name}' is missing argument {Index}.An argument slot the builtin asked for does not exist.
Math function '{Name}' argument {Index}: {Error}Evaluating the argument expression failed; {Error} is the inner diagnostic.
Math function '{Name}' only accepts numeric scalar/vector arguments.An argument is a texture object, a MaterialAttributes value or a Substrate value.Sample the texture, or break the attributes, before doing arithmetic.
Failed to create math function '{Name}'.The material node could not be created.
Math function '{Name}' could not bind input '{Input}'.The node class does not expose the expected input property. Unary builtins only.
Math function '{Name}' failed to access input '{Input}'.The input property exists but its storage could not be reached. Unary builtins only.
Unknown Graph function '{Name}'.The name is not a builtin, constructor, property or user function — emitted by the call path, not by this one. A misspelled builtin lands here.Details

The complete list lives in the diagnostics index.

Example

Shader(Name="Docs/M_MathBuiltins")
{
    Properties = {
        float X = 0.5;
        vec3  A = vec3(1.0, 0.0, 0.0);
        vec3  B = vec3(0.0, 1.0, 0.0);
    }
    Settings = { Domain = "UI"; ShadingModel = "Unlit"; }
    Outputs  = { vec3 Color; Base.EmissiveColor = Color; }
    Graph = {
        float s     = sin(X);
        float c     = cos(X);
        float cl    = clamp(X, 0.0, 1.0);
        float sa    = saturate(X);
        vec3  mixed = lerp(A, B, sa);
        vec3  unit  = normalize(mixed);
        float d     = dot(unit, A);
        Color = mixed * (s + c + cl) + unit * d;
    }
}

Generated nodes:

Sine(X)                       -> s
Cosine(X)                     -> c
Clamp(X, 0.0, 1.0)            -> cl
Saturate(X)                   -> sa
LinearInterpolate(A, B, sa)   -> mixed     (3 components: max(3, 3))
Normalize(mixed)              -> unit      (3 components)
DotProduct(unit, A)           -> d         (1 component, always)
Add / Multiply chain          -> Color

Where to next

On this page