Here's the secret the node graph has been keeping from you: you already write HLSL. Every Multiply node is a *, every Lerp node is lerp(). This page just removes the boxes. One idea at a time, each tied to an effect you've already built with nodes.
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 color. That's it. That's the whole job. Every gorgeous material you've ever seen is "compute one color, given where you're" repeated very fast.
The consequence that shapes everything: the program can't see neighboring 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.
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)
Math works on all of them at once - color * 0.5 darkens all three channels, exactly like piping a color 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.
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.
| Function | What it does | Where you've used it as a node |
|---|---|---|
lerp(a, b, t) | Blend a->b by t | Every blend, layer, and mask combine you've ever made |
saturate(x) | Clamp to 0-1 | Clamp node - the "stop lighting from breaking" function |
dot(a, b) | How aligned two directions are | Fresnel, fake lighting, facing masks (math sheet) |
frac(x) | Keep only the fraction - makes anything repeat | Tiling, stripes, scrolling that wraps |
smoothstep(e0, e1, x) | A soft threshold: 0 below e0, 1 above e1, silk between | Every soft mask edge; the civilized version of an if |
Two worked one-liners to make them real. Fresnel - surfaces facing away from you glow at the 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 toward the rim color." Stripes - frac makes anything periodic:
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
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:
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.
The classic portfolio piece, in eight lines you now fully understand:
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. This is the moment to notice: you read shader code now.
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 not melting your permutation budget) is the next page. For a pure sandbox with zero engine setup, Shadertoy in a browser tab is unbeatable for practicing the five functions - just know it's GLSL there (vec3 for float3, mix for lerp; the thinking is identical).