If you have not already read the previous lesson in this mini series, I also recommend starting there.
Understanding fract() in GLSL: The Function That Makes Patterns Repeat
@hey2d/understanding-fract-in-glsl-the-secret-behind-repeating-patterns-fcr
In that article, we learned what the fract() function does and why it is responsible for creating repeating patterns. We saw that fract() removes the whole number from a value and keeps only the decimal part, allowing coordinates to restart over and over again. In this lesson, we will look at the line that makes fract() useful in the first place.
When beginners first encounter this line of code,
vec2 g = fract(vUv * density);
their attention usually goes straight to fract().
While that function is important, there is another operation hiding in plain sight.
vUv * density
Without this multiplication, fract() would not create any visible repetition.
Understanding why this works will make many procedural shader techniques much easier to understand.
Before we multiply anything, remember what vUv looks like.
Across the width of the screen, the x coordinate moves smoothly from 0 to 1.
Left ------------------------ Right
0.0 1.0
The y coordinate behaves the same way from bottom to top.
That means every pixel already knows its position.
If we display the x coordinate directly,
vec3 color = vec3(vUv.x);
we get one smooth gradient stretching across the entire screen.
The important word here is one.
The coordinates only make one trip from 0 to 1.
Now imagine multiplying every UV coordinate by 2.
vUv * 2.0
Instead of moving between 0 and 1, the values now move between 0 and 2.
Before
0 → 1
After
0 → 2
Nothing has repeated yet.
The numbers have simply been stretched.
Now try multiplying by 5.
vUv * 5.0
The range becomes
0 → 5
And with
vUv * 10.0
the values now travel from
0 → 10
The larger the multiplier, the larger the coordinate values become.
At this point you might wonder why larger numbers are useful.
After all, the screen itself has not changed.
The answer is that fract() only becomes interesting when the values grow beyond 1.
Suppose the coordinates look like this after multiplication.
0.0
0.3
0.7
1.1
1.5
1.9
2.2
These values now contain whole numbers.
That means fract() has something to remove.
The result becomes
0.0
0.3
0.7
0.1
0.5
0.9
0.2
Instead of continuing upward forever, the values restart every time they pass a whole number.
That is exactly how repeated patterns are created.
The multiplier is usually stored in a variable called density.
float density = 10.0;
The name is descriptive.
A higher density means more repeated sections fit into the same screen space.
A lower density means fewer repetitions.
Think of drawing squares on a piece of paper.
If you divide the page into four squares, each square is large.
If you divide the same page into one hundred squares, every square becomes much smaller.
Nothing about the paper changes.
Only the number of divisions changes.
The density variable works in exactly the same way.
Now the complete line begins to make sense.
vec2 g = fract(vUv * density);
First, the UV coordinates are multiplied.
This stretches the values beyond their normal range.
Next, fract() removes the whole number from every coordinate.
The decimal portion remains.
Each time a whole number is crossed, the coordinates begin again from zero.
Every restart becomes another tile.
Use the following shader.
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 vUv;
void main()
{
float density = 2.0;
vec2 g = fract(vUv * density);
vec3 color = vec3(g.x);
gl_FragColor = vec4(color, 1.0);
}
Start with a density of 2.0.
Notice how the gradient repeats only a few times.
Now change the value to
float density = 5.0;
The repeated sections become smaller.
Next, try
float density = 10.0;
Finally, experiment with
float density = 20.0;
Each increase creates more repetitions because the coordinates are being stretched further before fract() resets them.
Many people think the multiplication itself creates the repeating pattern.
It does not.
If you remove fract() and write
vec3 color = vec3(vUv.x * 10.0);
you will not get ten repeating gradients.
Instead, the values simply become brighter because they continue increasing beyond 1.
The repetition only appears when fract() continuously removes the whole number.
The multiplication and fract() work together.
Neither one produces the effect on its own.
Once you understand this idea, you will start recognizing it in many shaders.
Repeating circles.
Checkerboard patterns.
Bricks.
Tiles.
Noise.
Pixel art.
Many of these effects begin with the same two steps.
Stretch the coordinates.
Restart them with fract().
Everything else is built on top of that foundation.