Skip to content
The Technical ArtistArt. Code. Pipelines. Games.
Home/Shaders/HLSL from Zero
shaders - starter
hlsl
zero code assumed

HLSL From Zero, For Artists

Startertutorial~4 min read

If you’ve built materials with nodes, many HLSL operations will already look familiar. Multiply and Lerp have direct code equivalents. This page works through the syntax one step at a time, using small effects you can compare with a node graph.

On this page The Mental Model Floats & Vectors Swizzling The Five Functions UV Maths Is Motion Build: Dissolve Effect Where To Write It

The Mental Model: One Tiny Program Per Pixel

A pixel shader is a short program that runs once for every pixel your object covers - hundreds of thousands of times per frame, all in parallel. It receives a few inputs (this pixel's UV, its normal, some textures) and must output one thing: a colour. For these examples, focus on how the inputs produce the output colour at each pixel.

The consequence that shapes everything: the program can't see neighbouring pixels, can't remember last frame, can't loop over the whole image. It only knows here, now. When shader code looks weird, it's usually this constraint doing the shaping.

Floats And Vectors (You Know These As Pins)

HLSL
float roughness = 0.5; // one number (a gray pin)
float2 uv = float2(0.25, 0.75); // two numbers (a UV pin)
float3 color = float3(1.0, 0.4, 0.0); // three (a color pin - orange)
float4 rgba = float4(color, 1.0); // four (color + alpha)

Maths works on all of them at once - color * 0.5 darkens all three channels, exactly like piping a colour through a Multiply node with 0.5. When you combine different sizes, HLSL is strict: float3 * float is fine (the float applies to each channel); float3 + float2 is a compile error, and the fix is deciding what you actually meant - which the node graph was silently deciding for you.

Swizzling: Select And Reorder Components

HLSL
float3 c = tex.rgb; // grab three channels (a Mask node)
float r = tex.r; // grab one (ComponentMask R)
float3 g = tex.ggg; // one channel, three times (instant grayscale spread)
float2 flipped = uv.yx; // swap! (rotate a gradient 90 deg)
c.rb = c.br; // swap red and blue in place

Any combination of xyzw/rgba, any order, repeats allowed. What takes three Mask-and-Append nodes in a graph is four characters here. This is the first place code stops being "the hard way" and starts being the fast way.

Five Common Material Functions

FunctionWhat it doesWhere you've used it as a node
lerp(a, b, t)Blend a->b by tEvery blend, layer, and mask combine you've ever made
saturate(x)Clamp to 0-1Clamp node - the "stop lighting from breaking" function
dot(a, b)How aligned two directions areFresnel, fake lighting, facing masks (maths sheet)
frac(x)Keep only the fraction - makes anything repeatTiling, stripes, scrolling that wraps
smoothstep(e0, e1, x)A soft threshold: 0 below e0, 1 above e1, silk betweenEvery soft mask edge; useful when you need a gradual transition

Two worked one-liners to make them real. Fresnel - surfaces facing away from you glow at the rim:

HLSL - fresnel rim
float rim = pow(saturate(1.0 - dot(Normal, ViewDir)), 3.0);
return lerp(baseColor, rimColor, rim);

Read it aloud: "how much does this pixel face the camera -> invert it -> sharpen with a power -> blend towards the rim colour." Stripes - frac makes anything periodic:

HLSL - stripes / progress bars / scan lines
float stripes = step(0.5, frac(uv.x * 10.0)); // 10 hard bands across U
float soft = smoothstep(0.4, 0.6, frac(uv.x * 10.0)); // same, soft edges

UV Maths Is Motion

UVs are just a float2 per pixel saying where to look in a texture. Add to them and the texture slides; multiply and it tiles; feed them through functions and it warps:

HLSL - the uv toolkit
float2 scrolled = uv + float2(Time * 0.1, 0.0); // panner
float2 tiled = uv * 4.0; // 4x4 tiling
float2 distorted = uv + (noiseTex.rg - 0.5) * 0.05; // heat-haze wobble

That third line is the whole "distortion" family - water refraction, heat haze, flag ripple - in one move: look somewhere slightly wrong, on purpose. Feed it a texture from the noise generator and you've built it for real.

Build It: The Dissolve Effect

The classic portfolio piece, in eight lines you now fully understand:

HLSL - dissolve with glowing edge
float n = noiseTex.r; // 0-1 noise, tileable
float cut = step(n, Progress); // 1 where dissolved away
float edge = smoothstep(Progress - 0.06, Progress, n)
 * (1.0 - cut); // thin band at the frontier
float3 color = baseColor + edge * edgeColor * 8.0; // overdrive it - bloom food
float opacity = 1.0 - cut;
// output: color -> Emissive/Base, opacity -> Opacity Mask

Slide Progress 0->1 and the noise eats the object, glowing at the boundary. Every part is one of the five functions. Try changing one input at a time and compare the result with the code.

Where To Actually Write It

Fastest path if you live in Unreal: the Custom node - paste the dissolve above, wire the inputs, iterate live. That workflow (plus material functions, plus keeping track of shader permutations) is the next page. For a pure sandbox with zero engine setup, Shadertoy in a browser tab is useful for practising these functions - just know it's GLSL there (vec3 for float3, mix for lerp; the thinking is identical).