Skip to content
Fundamental · ShapingBeginner

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

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 UV up first, so several cells fit across the screen:

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