Posts

Mastering step() in GLSL: Drawing Sharp Lines Instead of Smooth Gradients

0 comments·0 reblogs
hey2d
25
·
0 views
·
min-read

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.
Image from thread


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.

ValueResult
0.100
0.250
0.490
0.501
0.701
1.001

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