Shader concept
smoothstep: what it does and how to use it
smoothstep(a, b, x) returns 0 when x is below a, 1 when it is above b, and a smooth S-shaped curve in between. It is the standard way to make a soft edge in a shader.
Running live in your browser. The code below is exactly what draws it.
What it does
Give smoothstep two edges and a value. Below the lower edge you get 0, above the upper edge you get 1, and in between the result rises along a curve that starts and ends flat (its slope is zero at both ends). That flat start and end is why it looks smooth instead of like a ramp with visible corners.
Inside it computes t = clamp((x - a) / (b - a), 0, 1) and then returns t * t * (3 - 2 * t). You rarely need to write that out, but knowing it explains every trick below.
What it is used for
- Soft edges.
1.0 - smoothstep(r - w, r + w, length(p))draws a circle whose edge is blurred over a width of2w. - Anti-aliasing. Make
wabout one pixel and the jagged staircase on an edge disappears. - Fades and masks. Fade an effect in over a distance, or between two times.
- Easing. Feed it a slowly increasing value to get animation that eases in and out.
Common mistakes
- Swapping the edges.
smoothstep(0.7, 0.3, x)is undefined in GLSL (it may work in HLSL, but do not rely on it). To invert, write1.0 - smoothstep(0.3, 0.7, x). - Using the same value for both edges, which divides by zero. Use
stepfor a hard cut. - Forgetting that it clamps: it never goes above 1 or below 0, so it cannot make a gradient that keeps growing.
The code, in every shader language
The same example for each language family. Open one to see it.
HLSL Unity, Unreal
float4 frag(float2 uv)
{
// top half: step() cuts hard at 0.5. bottom half: smoothstep() fades from 0.3 to 0.7
float hard = step(0.5, uv.x);
float soft = smoothstep(0.3, 0.7, uv.x);
float t = lerp(soft, hard, step(0.5, uv.y));
float3 col = lerp(float3(0.08, 0.1, 0.22), float3(0.2, 0.85, 0.8), t);
return float4(col, 1.0);
} GLSL Shadertoy, three.js, OpenGL
vec4 frag(vec2 uv)
{
// top float: step() cuts hard at 0.5. bottom float: smoothstep() fades from 0.3 to 0.7
float hard = step(0.5, uv.x);
float soft = smoothstep(0.3, 0.7, uv.x);
float t = mix(soft, hard, step(0.5, uv.y));
vec3 col = mix(vec3(0.08, 0.1, 0.22), vec3(0.2, 0.85, 0.8), t);
return vec4(col, 1.0);
} WGSL WebGPU, Bevy
fn frag(uv: vec2f) -> vec4f
{
// top half: step() cuts hard at 0.5. bottom half: smoothstep() fades from 0.3 to 0.7
let hard = step(0.5, uv.x);
let soft = smoothstep(0.3, 0.7, uv.x);
let t = mix(soft, hard, step(0.5, uv.y));
let col = mix(vec3f(0.08, 0.1, 0.22), vec3f(0.2, 0.85, 0.8), t);
return vec4f(col, 1.0);
} Godot Godot 4 shading language
shader_type canvas_item;
vec4 frag(vec2 uv)
{
// top float: step() cuts hard at 0.5. bottom float: smoothstep() fades from 0.3 to 0.7
float hard = step(0.5, uv.x);
float soft = smoothstep(0.3, 0.7, uv.x);
float t = mix(soft, hard, step(0.5, uv.y));
vec3 col = mix(vec3(0.08, 0.1, 0.22), vec3(0.2, 0.85, 0.8), t);
return vec4(col, 1.0);
}
void fragment()
{
// Godot's UV starts at the top-left, the lessons use bottom-left
COLOR = frag(vec2(UV.x, 1.0 - UV.y));
} Metal iOS, macOS
float4 frag(float2 uv)
{
// top half: step() cuts hard at 0.5. bottom half: smoothstep() fades from 0.3 to 0.7
float hard = step(0.5, uv.x);
float soft = smoothstep(0.3, 0.7, uv.x);
float t = mix(soft, hard, step(0.5, uv.y));
float3 col = mix(float3(0.08, 0.1, 0.22), float3(0.2, 0.85, 0.8), t);
return float4(col, 1.0);
} Practise it in the lessons
- step() and lerp()Split the screen down the middle: color A on the left, color B on the right.
- smoothstep(): soft edgesInstead of a hard split, fade from the dark color to the orange between x = 0.3 and x = 0.7.
- Distance fields & antialiasingTurn the solid disc into a glowing ring: radius 0.3, soft edge width 0.02.
Questions
What is the difference between step and smoothstep?
step(edge, x) jumps from 0 to 1 at one value. smoothstep(a, b, x) changes gradually between two values, along a curve with no sharp corners.
Is smoothstep the same in HLSL, GLSL, WGSL and Metal?
Yes. All of them have smoothstep with the same three arguments and the same result.
Can I get a smoother curve than smoothstep?
Yes: smootherstep uses t*t*t*(t*(t*6-15)+10), which is flat in the second derivative too. You write it yourself because it is not built in.