Why `min()` Matters in GLSL: Fixing Grid Intersections the Right Way

avatar

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

This lesson explained how step() creates crisp vertical lines by detecting the edge of every repeated tile.

Building Your First Procedural Grid in GLSL
Why Multiplying UV Coordinates Changes Everything in GLSL

Finally, we combined two step() functions to draw both vertical and horizontal lines, producing our first procedural grid.

Today we will finish the shader by fixing one small problem that appears where those lines intersect.


The Grid Already Works... So Why Change It?

At first glance, the previous shader looks complete.

It draws a clean grid.

The spacing is correct.

The line width behaves exactly as expected.

So why add another line of code?

The answer is hidden inside the values the shader is producing.

Although the image looks correct, some pixels contain numbers that are larger than we actually need.

Understanding this is an important step toward writing cleaner GLSL code.


Looking at the Line Mask

Our previous lesson ended with this line.

float line =
    step(1.0 - lw, g.x)
  + step(1.0 - lw, g.y);

Remember what each step() function returns.

0

or

1

Nothing else.

That means every pixel receives one of four possible combinations.

VerticalHorizontalResult
000
101
011
112

Most of the grid behaves perfectly.

Only the intersection points become different.


What Happens at the Corners?

Imagine a point where a vertical line crosses a horizontal line.

The vertical step() says

1

The horizontal step() also says

1

Adding them together produces

2

Mathematically, there is nothing wrong with this.

The shader simply adds two numbers.

The question is whether we actually need that value.

The answer is no.


Why a Value of Two Is Unnecessary

Think about what the variable line represents.

It is not measuring brightness.

It is not storing a distance.

It is simply acting as a mask.

We only need two states.

Background.

Or line.

Once a pixel belongs to a line, giving it a value of 2 does not make it any more of a line than a value of 1.

Both mean exactly the same thing.

Keeping the mask between 0 and 1 makes it much easier to reuse later in other shaders.


The Simple Solution

GLSL provides a very convenient function for this.

min()

The function compares two values and returns the smaller one.

For example,

min(4.0, 2.0)

returns

2.0

while

min(0.6, 1.0)

returns

0.6

We can use exactly the same idea for our grid.


Clamping the Grid Mask

The fix is only one line.

line = min(line, 1.0);

Now think about every possible value.

If line equals

0

the result stays

0

If line equals

1

nothing changes.

If line equals

2

min() compares

2

and

1

and returns

1

The intersections are immediately brought back into the normal range.


The Exercise

The previous shader already contains

float line =
    step(1.0 - lw, g.x)
  + step(1.0 - lw, g.y);

Your task is simply to add

line = min(line, 1.0);

Run the shader before adding the line.

Then run it again afterward.

Visually, you may notice very little difference.

That is perfectly normal.

The real improvement is happening inside the values your shader produces.


Why Good Shader Code Matters

This lesson demonstrates something that becomes increasingly important as shaders grow more complex.

Sometimes we are not fixing what the image looks like.

We are fixing the data.

Imagine using this line mask later to mix colors.

Or to animate an effect.

Or to create glowing edges.

If the mask unexpectedly contains values greater than 1, those later calculations may behave differently than expected.

Keeping values inside their intended range makes every future calculation more reliable.


Complete Shader

#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)
      + step(1.0 - lw, g.y);

    line = min(line, 1.0);

    vec3 color = mix(
        vec3(0.1, 0.15, 0.25),
        vec3(1.0),
        line
    );

    gl_FragColor = vec4(color, 1.0);
}

This is the completed version of the original Grid Lines exercise.

Every concept from this five part series now works together.


This exercise began with a single shader, but inside it were several important GLSL concepts.

We learned how fract() creates repeating coordinates.

We discovered why multiplying UV coordinates changes the number of repeated tiles.

We explored how step() turns smooth values into crisp edges.

We combined two step() functions to build a procedural grid.

Finally, we cleaned the result using min() so the mask stays within the correct range.

Although each lesson focused on one small piece, together they form a workflow that appears in countless procedural shaders.


Posted Using INLEO



0
0
0.000
0 comments