If you have not read the previous lessons in this series, I recommend doing that first.
Understanding fract() in GLSL: The Function That Makes Patterns Repeat
Understanding fract() in GLSL: The Secret Behind Repeating Patterns
In that lesson, we explored how fract() continuously resets UV coordinates, making repeating patterns possible.
Why Multiplying UV Coordinates Changes Everything in GLSL
Why Multiplying UV Coordinates Changes Everything in GLSL
There, we learned why multiplying vUv by a density value creates more repeated sections before fract() resets them.
Today, we finally use those repeated coordinates to draw something visible.

From Smooth Values to Sharp Edges
Until now, almost every value we have worked with has changed smoothly.
A gradient slowly transitions from black to white.
mix() blends one color into another.
Even vUv.x increases little by little across the screen.
But sometimes we do not want a smooth transition.
Sometimes we want a hard edge.
We want a pixel to be either on or off.
Black or white.
Line or background.
That is exactly what the step() function does.
What Does step() Do?
The step() function is surprisingly simple.
It accepts two values.
step(edge, value)
The first number is called the edge.
The second is the value you want to test.
If the value is smaller than the edge, the function returns
0.0
If the value is greater than or equal to the edge, it returns
1.0
There are no values in between.
Unlike mix() or a gradient, step() never returns 0.25 or 0.73.
It only produces two possible answers.
Understanding It with Numbers
Imagine writing
step(0.5, value)
Now test different values.
| Value | Result |
|---|---|
| 0.10 | 0 |
| 0.25 | 0 |
| 0.49 | 0 |
| 0.50 | 1 |
| 0.70 | 1 |
| 1.00 | 1 |
As soon as the value reaches the edge, the output instantly changes.
Instead of fading smoothly, it jumps.
That sudden jump is what makes step() useful for drawing shapes and lines.
Using step() with UV Coordinates
Now let's combine step() with the repeated coordinates we created in the previous lessons.
float lw = 0.05;
float line = step(1.0 - lw, g.x);
At first glance, this looks confusing.
Let's break it into smaller pieces.
The variable
lw
stands for line width.
In this example,
float lw = 0.05;
means the line should be about five percent of the width of each repeated tile.
Next we calculate
1.0 - lw
which becomes
0.95
The shader is now checking whether g.x has reached 0.95.
Why Use 0.95?
Remember what g.x looks like inside every repeated tile.
0 -------------------- 1
When g.x reaches 0.95, it is almost at the right edge.
That means
step(0.95, g.x)
returns
0
through most of the tile.
Then, very close to the edge, it suddenly changes to
1
Instead of drawing a gradient, we have drawn a thin vertical stripe.
Visualizing the Result
Imagine one repeated tile.
Most of the tile produces
0
Only the last few pixels produce
1
That creates a white line against a dark background.
Because fract() repeats the coordinates over and over again, the same line appears in every tile.
Without writing a loop, the shader has created an entire series of vertical stripes.
The Exercise
The original shader contains
float line = 0.0;
Since the value never changes, every pixel receives the background color.
Replace that line with
float line = step(1.0 - lw, g.x);
Nothing else needs to change.
The background immediately transforms into evenly spaced vertical lines.
Although the code changed by only one line, the result looks dramatically different.
Experiment with the Line Width
The line width controls how thick each stripe becomes.
Try
float lw = 0.02;
The lines become very thin.
Next try
float lw = 0.10;
Now every stripe becomes much thicker.
You are not changing the number of stripes.
You are only changing how much of each tile is occupied by the line.
Complete Example
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 vUv;
void main()
{
float density = 10.0;
vec2 g = fract(vUv * density);
float lw = 0.05;
float line = step(1.0 - lw, g.x);
vec3 color = mix(
vec3(0.1, 0.15, 0.25),
vec3(1.0),
line
);
gl_FragColor = vec4(color, 1.0);
}
This shader creates repeating vertical stripes because every tile draws a line near its right edge.
Why This Function Is Used Everywhere
The step() function is one of the simplest functions in GLSL, yet it appears in countless procedural shaders.
It is used to draw borders.
Create checkerboards.
Generate pixel art.
Build user interface elements.
Separate one region from another.
Any time you need a sharp edge instead of a smooth blend, step() is usually one of the first functions to consider.
Posted Using INLEO