HLSL for Unreal Engine materials
Unreal materials are built from nodes, but the Custom node lets you write HLSL directly. That is the most powerful tool in the Material editor, and the one most tutorials skip. Practise the HLSL here, and ShaderBattle wraps your functions in a struct so that a whole shader fits inside one Custom node.
Things to know about HLSL
- The Custom node holds a function body, not a whole file, so helper functions live inside a
struct. - Inputs such as
UVandTimeare added in the node's inputs list. - Unreal's texture coordinates start at the top-left; the export flips
yso lessons match. - Set the Output Type to
CMOT Float 4and connect the result to Emissive Color.
Cheat sheet
| Idea | Write |
|---|---|
| Vectors | float2 float3 float4 |
| Declare a value | float d = length(p); |
| A function | float circle(float2 p, float r) { ... } |
| Matrices | float2x2, multiply with mul(M, v) |
| Blend | lerp(a, b, t) |
| Fractional part | frac(x) |
| Clamp to 0..1 | saturate(x) |
| Remainder | fmod(a, b) (keeps the sign of a) |
| Choose | c ? a : b |
| Angle of a vector | atan2(y, x) |
| Time in seconds | time, or View.GameTime |
| Pixel position | uv (0..1, bottom-left) |
| Width / height | aspect |
| Entry point | float4 frag(float2 uv) { return …; } |
Handy built-ins: sin cos abs sign floor ceil frac step smoothstep clamp min max pow exp sqrt length distance dot cross normalize reflect
Gotchas
- Every pixel runs on its own, with no memory of other pixels.
- Declare a function above the place you use it.
- Prefer 2.0 over 2 in maths. This site fixes most cases for you; real engines may not.
- Loops need a small fixed count: for (int i = 0; i < 5; i++).
- In Unreal the export wraps your functions in a struct so a single Custom node can hold them.
Example: a glowing ring
The same shader as it is written for Unreal:
float4 frag(float2 uv)
{
float2 p = (uv - 0.5) * float2(aspect, 1.0);
float d = length(p);
float m = 1.0 - smoothstep(0.0, 0.02, abs(d - 0.3));
float3 bg = float3(0.08, 0.08, 0.12);
float3 fg = float3(0.4, 0.9, 1.0);
return float4(lerp(bg, fg, m), 1.0);
} Exporting to Unreal
In a Material, add a Custom node. Output Type: CMOT Float 4. Add two inputs named UV (from a TexCoord node) and Time (from a Time node). Paste this as the Code and connect the output to Emissive Color (Unlit) or Base Color. The struct lets your helper functions live inside one Custom node.
// ShaderBattle: Glowing ring
struct ShaderBattle
{
float time;
float aspect;
float4 frag(float2 uv)
{
float2 p = (uv - 0.5) * float2(aspect, 1.0);
float d = length(p);
float m = 1.0 - smoothstep(0.0, 0.02, abs(d - 0.3));
float3 bg = float3(0.08, 0.08, 0.12);
float3 fg = float3(0.4, 0.9, 1.0);
return float4(lerp(bg, fg, m), 1.0);
}
};
ShaderBattle sb;
sb.time = Time;
sb.aspect = 1.0; // a square plane; use your mesh's width / height otherwise
return sb.frag(float2(UV.x, 1.0 - UV.y)); Questions
Can I use multiple functions in a Custom node?
Not directly, which is why the export puts them in a struct and calls the main one from the node's code.
Other languages
Unity (Built-in) · Unity (URP) · Shadertoy / WebGL · OpenGL (LWJGL, raylib) · three.js · GameMaker · LÖVE (Lua) · Godot · Apple (iOS, macOS) · WebGPU / Bevy