In the previous tutorial, we learned how to repeat patterns using fract(). By restarting UV coordinates over and over, we created stripes, tiles, and repeating gradients.
Those patterns were still smooth because gradients change continuously.
Sometimes, however, you do not want a smooth transition.
You may want a perfectly sharp line.
Or perhaps you want a line that fades gently instead of changing instantly.
That is exactly what step() and smoothstep() are designed to do.
These two functions are used everywhere in GLSL. They are the building blocks for lines, borders, circles, masks, and many procedural shapes.
This tutorial builds on the previous lessons.
Part 1: Your First Shader: Painting the Entire Screen One Color
Part 2: Understanding UV Coordinates: The Secret Behind Every Shader
Part 3: Your First Gradient: Using UV Coordinates to Paint Space
Part 4: Mixing Colors with mix(): Creating Smooth Color Transitions
Part 5: Keeping Values Under Control with clamp()
Part 6: Adding Contrast with pow(): Making Gradients More Interesting
Part 7: Repeating Patterns with fract(): Creating Infinite Tiles
Now we are ready to turn smooth gradients into clean shapes.
step()?The step() function creates an instant change between two values.
Its structure is
step(edge, value)
If the value is smaller than the edge, the result is 0.0.
If the value is greater than or equal to the edge, the result is 1.0.
That is all it does.
Suppose we write
step(0.5, vUv.x)
Everything to the left of the middle of the screen becomes
0.0
Everything to the right becomes
1.0
Instead of a smooth gradient, we now have a sharp dividing line.
Here is a complete shader.
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 vUv;
void main() {
float line = step(0.5, vUv.x);
gl_FragColor = vec4(vec3(line), 1.0);
}
The screen is divided exactly in half.
One side is black.
The other side is white.
There is no gradual transition.
The edge does not have to stay in the middle.
Move it closer to the left.
step(0.2, vUv.x)
Or move it toward the right.
step(0.8, vUv.x)
Changing the first number changes where the line appears.
smoothstep()?Sometimes a perfectly sharp edge looks too harsh.
That is where smoothstep() comes in.
Its structure is
smoothstep(start, end, value)
Instead of changing instantly, it creates a smooth transition between two values.
smoothstep(0.4, 0.6, vUv.x)
Instead of an instant jump at the center, the color slowly changes between 0.4 and 0.6.
Everything before 0.4 stays black.
Everything after 0.6 becomes white.
Only the area between them fades smoothly.
Using step().
████████░░░░░░░░
Using smoothstep().
███████▓▒▒░░░░░░
Both functions divide the screen.
The difference is how they handle the edge.
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 vUv;
void main() {
float value = smoothstep(0.3, 0.7, vUv.x);
gl_FragColor = vec4(vec3(value), 1.0);
}
Instead of a hard cut, the brightness slowly changes across the center of the screen.
step() with fract()These two functions work very well together.
float stripe = step(0.5, fract(vUv.x * 8.0));
gl_FragColor = vec4(vec3(stripe), 1.0);
Instead of repeating gradients, you now get repeating black and white stripes.
This is one of the simplest procedural patterns you can create.
smoothstep() with fract()Now replace step().
float stripe = smoothstep(0.4, 0.6, fract(vUv.x * 8.0));
gl_FragColor = vec4(vec3(stripe), 1.0);
The stripes now have soft edges instead of perfectly sharp ones.
This small change makes the pattern look much smoother.
You will find step() and smoothstep() in shaders that create
Almost every shader artist uses them.
Draw a line in the middle.
step(0.5, vUv.x)
Move the line.
step(0.25, vUv.x)
Create a soft edge.
smoothstep(0.3, 0.7, vUv.x)
Create repeating stripes.
step(0.5, fract(vUv.x * 10.0))
Try changing the numbers and observe how the result changes.
Can you create these effects?
Spend some time experimenting before moving on.
These two functions become much easier to understand once you see them in action.