Step & smoothstep
Hard and soft thresholds, where every edge and mask starts.
How it works
A shape in a fragment shader is a decision every pixel makes on its own: am I inside or outside? step and
smoothstep turn a continuous value, like a coordinate or a distance, into that decision.
step: a hard edge
UV coordinates A 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.step(edge, x) returns 0.0 when x is below edge and 1.0 otherwise. Fed a UVA 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.uv = gl_FragCoord.xy / resolution
float right = step(0.5, uv.x); // 0 on the left half, 1 on the right
Mind the argument order: the threshold comes first. Swapping the arguments flips the result, and so does
1.0 - step(...). Multiplying two steps keeps only the pixels where both are 1, which is how a band or a box starts:
float band = step(0.4, uv.y) * (1.0 - step(0.6, uv.y)); // 1 between 0.4 and 0.6
A step changes all at once, inside a single pixel. On an edge that isn’t perfectly horizontal or vertical that shows
as jagged stairs (aliasing), and a moving edge flickers.
smoothstep: a soft edge
Linear interpolation (mix) Blends from a to b by a factor t, along a straight line. GLSL calls it mix; HLSL and most node editors call it lerp.smoothstep(e0, e1, x) is 0 below e0, 1 above e1, and rises smoothly in between. Inside, it first remaps x to
0–1 with a clamped inverse lerpBlends from a to b by a factor t, along a straight line. GLSL calls it mix; HLSL and most node editors call it lerp.mix(a, b, t) = a (1 − t) + b t
float t = clamp((x - e0) / (e1 - e0), 0.0, 1.0);
float s = t * t * (3.0 - 2.0 * t); // what smoothstep(e0, e1, x) returns
The curve is flat at both ends, so the transition blends into the regions around it with no visible kink. A narrow
transition gives an anti-aliased edge; a wide one gives a glow or a fade. With p the centered, aspect-corrected
coordinate from UV & aspect ratio:
float d = length(p);
float disc = 1.0 - smoothstep(0.49, 0.51, d); // a crisp disc of radius 0.5
float glow = 1.0 - smoothstep(0.0, 1.0, d); // a soft falloff from the center
The GLSL specification leaves the result undefined when e0 is not smaller than e1, so a reversed edge that
looks right on one GPU may break on another. For a falling edge, write 1.0 - smoothstep(e0, e1, x) rather than swapping the edges.
How narrow is narrow? A fixed width such as 0.02 is about one pixel at one screen size, and blurry or jagged at
another. The robust answer measures one pixel in the units of d with screen-space derivatives (fwidth), which get
their own fundamental later.