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
| Notation | Meaning | Example |
|---|---|---|
<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.
| Spelling | Arity | Lowers to | Input pins wired, in order | Return width | Authoritative |
|---|---|---|---|---|---|
abs | 1 | UMaterialExpressionAbs | Input | width of the argument | inherited from the argument |
acos since 1.6.0 | 1 | UMaterialExpressionArccosine | Input | width of the argument | inherited from the argument |
asin since 1.6.0 | 1 | UMaterialExpressionArcsine | Input | width of the argument | inherited from the argument |
atan since 1.6.0 | 1 | UMaterialExpressionArctangent | Input | width of the argument | inherited from the argument |
atan2 since 1.6.0 | 2 | UMaterialExpressionArctangent2 | Y ← argument 1, X ← argument 2 | max of both arguments | set when either argument had it |
ceil | 1 | UMaterialExpressionCeil | Input | width of the argument | inherited from the argument |
clamp | 3 | UMaterialExpressionClamp | Input, Min, Max | width of argument 1 | from argument 1 |
cos | 1 | UMaterialExpressionCosine | Input | width of the argument | inherited from the argument |
cross since 1.6.0 | 2 | UMaterialExpressionCrossProduct | A, B | always 3 | always set |
dot | 2 | UMaterialExpressionDotProduct | A, B | always 1 | always set |
floor | 1 | UMaterialExpressionFloor | Input | width of the argument | inherited from the argument |
fmod since 1.5.0 | 2 | UMaterialExpressionFmod | A ← dividend, B ← divisor | width of argument 1 | from argument 1 |
frac | 1 | UMaterialExpressionFrac | Input | width of the argument | inherited from the argument |
fract since 1.5.0 | 1 | UMaterialExpressionFrac | Input | width of the argument | inherited from the argument |
length since 1.6.0 | 1 | UMaterialExpressionLength | Input | always 1 | always set |
lerp | 3 | UMaterialExpressionLinearInterpolate | A, B, Alpha | max of arguments 1 and 2 | set when either of arguments 1, 2 had it |
max | 2 | UMaterialExpressionMax | A, B | max of both arguments | set when either argument had it |
min | 2 | UMaterialExpressionMin | A, B | max of both arguments | set when either argument had it |
mix | 3 | UMaterialExpressionLinearInterpolate | A, B, Alpha | max of arguments 1 and 2 | set when either of arguments 1, 2 had it |
mod since 1.5.0 | 2 | UMaterialExpressionFmod | A ← dividend, B ← divisor | width of argument 1 | from argument 1 |
normalize | 1 | UMaterialExpressionNormalize | VectorInput | width of the argument | inherited from the argument |
pow | 2 | UMaterialExpressionPower | Base, Exponent | width of argument 1 | from argument 1 |
reflect since 1.6.0 | 2 | a 4-node subgraph — see the note below | — | max of both arguments | set when either argument had it |
refract since 1.6.0 | 3 | a 14-node subgraph — see the note below | — | max of arguments 1 and 2 | set when either of arguments 1, 2 had it |
saturate | 1 | UMaterialExpressionSaturate | Input | width of the argument | inherited from the argument |
sin | 1 | UMaterialExpressionSine | Input | width of the argument | inherited from the argument |
smoothstep since 1.6.0 | 3 | UMaterialExpressionSmoothStep | Min, Max, Value | max of all three arguments | set when any argument had it |
sqrt | 1 | UMaterialExpressionSquareRoot | Input | width of the argument | inherited from the argument |
step since 1.6.0 | 2 | UMaterialExpressionStep | Y ← argument 1 (edge), X ← argument 2 (value) | max of both arguments | set when either argument had it |
Aliases
Three alias pairs. The two spellings in each pair are interchangeable and produce identical nodes — neither is deprecated.
| Pair | Node | Note |
|---|---|---|
lerp / mix | LinearInterpolate | mix is the GLSL spelling |
frac / fract | Frac | fract is the GLSL spelling since 1.5.0 |
fmod / mod | Fmod | mod 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.
| # | Rule | Consequence when violated |
|---|---|---|
| 1 | Arity is exact — no defaults, no optional arguments, no varargs | Math function '{Name}' expects exactly {N} argument(s). |
| 2 | Every argument is positional; a named argument is not accepted | reported as an arity error, see below |
| 3 | Each argument is evaluated as a full Graph expression, nested builtin calls included | the inner error is wrapped as Math function '{Name}' argument {Index}: {Error} |
| 4 | Texture-object values are rejected | Math function '{Name}' only accepts numeric scalar/vector arguments. |
| 5 | MaterialAttributes values are rejected | same message |
| 6 | Substrate values are rejected | same message |
| 7 | Argument component counts are not checked, widened or broadcast | nothing 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:
| # | Candidate | Reference |
|---|---|---|
| 1 | vector/scalar constructor names (float3, vec4, int2, …) | Expressions |
| 2 | UE.SceneTexture | UE.* nodes |
| 3 | any UE.-prefixed callee | UE.* nodes |
| 4 | any Substrate.-prefixed callee | Substrate |
| 5 | math builtins — this page | — |
| 6 | SampleTexture2D | UE.Expression |
| 7 | declared properties, in the parameter pin-call form | Property types |
| 8 | Function, GraphFunction, ShaderFunction, VirtualFunction | Calls |
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.0step(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.0smoothstep(min, max, x), argument order as in HLSL, wired straight to the node's Min, Max and
Value pins.
length, cross
since 1.6.0The 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.0The 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.0The 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)) * nThe 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.
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: LinearInterpolate → lerp, Clamp (when ClampMode == CMODE_Clamp) → clamp,
Power → pow, DotProduct → dot, Normalize → normalize, Min/Max → min/max,
Abs → abs, Saturate → saturate, Floor/Ceil/Frac/SquareRoot →
floor/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.
| Message | Cause | Fix |
|---|---|---|
| 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