Fract, floor & mod
Split a coordinate into a cell number and a position inside the cell, to repeat space.
How it works
Any number splits into a whole part and a fractional part: 3.75 is 3 plus 0.75. In a shader that split is how you tile space. The whole part says which cell a pixel is in, and the fractional part says where it is inside that cell.
floor and fract
UV coordinates A 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.floor(x) rounds down to a whole number, and fract(x) is what is left over, x - floor(x), always at least 0 and
below 1. Both work per component on vectors. Scale the UVA 2D position on a surface or the screen, usually from 0 to 1 on both axes. Shaders draw shapes and patterns as functions of it.uv = gl_FragCoord.xy / resolution
vec2 grid = uv * 8.0; // 0..8 across the screen
vec2 cell = floor(grid); // which cell: (0, 0) to (7, 7)
vec2 local = fract(grid); // where in the cell: 0..1 again
Anything you draw with local repeats in every cell, because each cell now sees its own 0–1 range, a small UV of its
own. cell is different in every cell, so it can make the copies differ. A checkerboard alternates on the sum of the
cell coordinates:
float checker = mod(cell.x + cell.y, 2.0); // 0 or 1, alternating
floor rounds toward negative infinity, not toward zero: floor(-0.25) is -1.0, and fract(-0.25) is 0.75. That
keeps every cell the same size on both sides of zero. Truncating toward zero instead would give the cell around zero
twice the width of the others.
mod
mod(x, y) repeats with any period. It returns x - y * floor(x / y), which for a positive y lies between 0 and
y; fract(x) is mod(x, 1.0). Use it when the period isn’t 1:
float stripes = step(0.1, mod(uv.x, 0.2)); // five stripes, half on and half off
Because GLSL’s mod is built on floor, it stays positive for negative x: mod(-0.25, 1.0) is 0.75. HLSL’s fmod
and C’s fmod truncate instead and return -0.25, which breaks tiling to the left of or below the origin when a shader
is ported. In HLSL, write x - y * floor(x / y) where the GLSL had mod.
Centering and seams
For a shape centered in each cell, shift the local coordinate: fract(grid) - 0.5 runs from -0.5 to 0.5 with the
cell’s center at zero, ready for length and the other tools from centered UVs.
fract jumps from almost 1 back to 0 at every cell border. Math evaluated per pixel doesn’t mind, but anything that
compares neighboring pixels does: a texture sampled with local can show a one-pixel seam at the borders, because
the GPU picks the mipmap level from how fast the coordinate changes between pixels, and the jump looks like a huge
change. Screen-space derivatives and texture sampling, later in the order, explain why and how to avoid it.
Further reading
- fract (opens in a new tab)Reference
- mod (opens in a new tab)Reference
- Patterns (opens in a new tab)Book