step, lerp and smoothstep: the three functions behind most shader effects

Updated 2026-10-12

Open a few shaders at random and you will find the same three functions again and again: step, lerp (called mix in GLSL, Metal and WGSL) and smoothstep. Between them they cover hard edges, blends and soft edges, which is most of what a 2D effect needs. Learn these three well and a lot of shader code stops looking like magic.

step: a hard edge

step(edge, x) returns 0 when x is below edge and 1 when it is at or above it. That is all it does, and it is surprisingly useful. It turns any smooth number into an on/off mask:

float4 frag(float2 uv)
{
    float left = step(uv.x, 0.5);        // 1 on the left half, 0 on the right
    return float4(left, left, left, 1.0);
}

Watch the argument order. step(0.5, uv.x) is 0 on the left and 1 on the right; step(uv.x, 0.5) is the other way round. When the result looks mirrored, swap the two arguments before you do anything else.

lerp: a blend between two things

lerp(a, b, t) blends from a to b. At t = 0 you get a, at t = 1 you get b, and halfway you get the average. The formula is a + (b - a) * t. It works on numbers, on vectors, and on colors:

float3 sky     = float3(0.1, 0.3, 0.8);
float3 horizon = float3(1.0, 0.7, 0.4);
float3 color   = lerp(horizon, sky, uv.y);     // orange at the bottom, blue at the top

The third argument is the "how much" control. Feed it a gradient and you get a smooth blend. Feed it a hard 0-or-1 mask and you get a choice between the two. That is how a shader does "if" without branching:

float3 color = lerp(float3(0.1, 0.1, 0.1), float3(1.0, 0.8, 0.0), step(uv.x, 0.5));

Notice what is happening: step produces the mask and lerp uses it to pick a color. You will write that pair constantly.

smoothstep: a soft edge

smoothstep(e0, e1, x) is 0 below e0, 1 above e1, and eases smoothly between them. Inside the range it computes t = saturate((x - e0) / (e1 - e0)) and returns t * t * (3 - 2 * t), an S-shaped curve that starts and ends flat. That flat start and end is what makes the transition look soft rather than like a straight ramp.

float4 frag(float2 uv)
{
    float d = length(uv - 0.5);
    float edge = smoothstep(0.30, 0.25, d);   // see the note below about reversed edges
    return float4(edge, edge, edge, 1.0);
}

Use it wherever step would leave jagged, aliased edges. A circle drawn with step has staircase edges; the same circle drawn with a narrow smoothstep across its rim looks clean.

A mistake to avoid: reversed edges

Many shading languages leave smoothstep undefined when e0 is larger than e1. Some compilers accept it and give the reversed curve, others do not, and the result can differ between your laptop and a phone. The safe way to flip a smoothstep is to subtract it from one:

float inside = 1.0 - smoothstep(0.25, 0.30, d);

This works everywhere, so make it your habit.

Putting them together

Here are the three working as a team to draw a soft-edged disc over a gradient background:

float4 frag(float2 uv)
{
    float2 p = (uv - 0.5) * float2(aspect, 1.0);
    float d = length(p);

    float3 background = lerp(float3(0.05, 0.05, 0.15), float3(0.15, 0.1, 0.3), uv.y);
    float3 disc       = float3(1.0, 0.75, 0.2);

    float mask = 1.0 - smoothstep(0.24, 0.26, d);        // soft edge, about 0.02 wide
    float3 color = lerp(background, disc, mask);
    return float4(color, 1.0);
}

The disc mask is a number between 0 and 1 at every pixel. lerp uses that number to blend the disc color over the background. Change the numbers in smoothstep to move the edge or make it softer: (0.10, 0.40, d) gives a big glowing falloff, and (0.25, 0.2501, d) is almost a hard edge.

Stripes and bands from two steps

Subtract one step from another and you get a band:

float band = step(0.3, uv.x) - step(0.6, uv.x);    // 1 only where 0.3 <= uv.x < 0.6

Combine that with frac (which repeats a value) and you have repeating stripes, a checkerboard, a grid: Repeating: floor and frac shows how.

Quick reference

  • step(edge, x): 0 or 1. A hard mask.
  • lerp(a, b, t): a blend. t of 0 gives a, 1 gives b. Called mix elsewhere.
  • smoothstep(e0, e1, x): an eased 0-to-1 ramp, for soft edges. Keep e0 < e1.
  • Mask with step or smoothstep, then blend with lerp.

Practise them

The lessons step() and lerp() and smoothstep(): soft edges each ask you to match a target picture with just these functions, and the challenge list has dozens that use them as building blocks.

Keep reading

Start the interactive lessons