Posts

Creating Repeating Grid Lines in GLSL with fract() and step()

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

Before continuing with this lesson, if you are joining this series for the first time, I highly recommend reading the complete overview of everything we have covered so far.

A Beginner's Journey Through GLSL So Far

That article covers the foundations we have been building throughout this series, including:

  • Drawing a solid color on the screen
  • Understanding UV coordinates
  • Horizontal and vertical gradients
  • The mix() function
  • The clamp() function
  • Several small exercises that help build intuition instead of memorizing code

Once you are comfortable with those ideas, this lesson will feel like a natural next step.


Image from thread

From Gradients to Patterns

Until now, we have used UV coordinates to create smooth transitions across the screen. Whether it was a horizontal gradient, a vertical gradient, or even a diagonal one, every pixel smoothly blended into the next.

Now we are going to do something different.

Instead of creating one large gradient, we are going to divide the screen into many smaller sections and draw a line around each one. The result is a grid.

To do that, we need two functions that work extremely well together.

  • fract()
  • step()

One repeats our coordinates, while the other decides where the lines should appear.


Step One: Divide the Screen into Tiles

The first piece of code is

float density = 10.0; 
vec2 g = fract(vUv * density); 

At first glance, this might look confusing, but it becomes simple once you break it apart.

First we multiply the UV coordinates by 10.

vUv * 10.0 

Normally vUv goes from 0 to 1.

After multiplying by 10, the coordinates now travel from 0 to 10.

That does not create ten copies of the image yet. It simply stretches the coordinate values.

The real magic happens with fract().


What Does fract() Do?

The fract() function keeps only the decimal part of a number.

For example,

Valuefract()
0.250.25
1.200.20
3.800.80
7.050.05

Every time the value reaches a whole number, it starts over at zero.

That means instead of counting

0 → 10 

the coordinates repeatedly become

0 → 1 
0 → 1 
0 → 1 
0 → 1 

ten times.

The result is ten repeating tiles across the screen.

Since we do this for both the x and y directions, we end up with a 10 × 10 grid.

Inside every cell, the coordinates once again range from 0 to 1.

That is exactly what the variable g stores.


Finding the Edge of Each Tile

Now that every tile has its own coordinates, we need to figure out where the borders are.

We start by deciding how thick the lines should be.

float lw = 0.05; 

This means every line will be about five percent of the width of a single grid cell.

Next comes the important part.

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

Understanding step()

The step() function works like a simple switch.

It returns either 0 or 1.

step(edge, value) 

If the value is smaller than the edge, it returns 0.

If the value is greater than or equal to the edge, it returns 1.

For example,

step(0.95, g.x) 

returns 1 only when g.x is greater than 0.95.

Since g.x always goes from 0 to 1 inside every tile, values above 0.95 are very close to the right edge.

That creates a thin vertical line.


Drawing Both Directions

The same idea works for the y coordinate.

step(1.0 - lw, g.y) 

This creates a horizontal line near the top edge of every tile.

When we add the two together,

step(1.0 - lw, g.x) 
+ 
step(1.0 - lw, g.y) 

we now have both vertical and horizontal lines.

Together they form a grid.


Why Use min()?

Something interesting happens where two lines meet.

At every corner intersection,

  • the vertical line contributes 1
  • the horizontal line contributes 1

Adding them together gives

2 

But we only want our mask to contain values between 0 and 1.

So we write

line = min(line, 1.0); 

If the value is already 0 or 1, nothing changes.

If it becomes 2, it is reduced back to 1.

This keeps the line mask clean and predictable.


The Exercise

The exercise begins with

float line = 0.0; 

Since the value never changes, the shader only shows the background.

Replace it with

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

Without changing anything else, a grid appears across the entire screen.


Experiment with the Numbers

Once your grid works, try changing a few values.

Make the lines thinner.

float lw = 0.02; 

Or make them thicker.

float lw = 0.10; 

You can also change the number of tiles.

float density = 5.0; 

creates larger squares.

float density = 20.0; 

creates many smaller ones.

Just by changing these two numbers, you can create very different patterns.


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) 
      + step(1.0 - lw, g.y); 
 
    line = min(line, 1.0); 
 
    vec3 color = vec3(line); 
 
    gl_FragColor = vec4(color, 1.0); 
} 

Posted Using INLEO