Quick answer: Float precision drops at large magnitudes — fract(uv * 1000.0) bands hard far from origin. Wrap the UV with mod(uv, 1.0) before scaling, or shift to local coordinates first.
A tiling detail shader looks crisp near the world origin but bands and stair-steps in distant chunks. The huge UVs feeding fract have lost precision.
fract Loses Bits at Distance
A 32-bit float has ~7 significant decimal digits. At UV = 1,000,000 the fractional part is the bottom few bits — barely any resolution. fract returns coarse, banded values.
Wrap First
// instead of:
vec2 tile_uv = fract(world_uv * tiles_per_unit);
// do:
vec2 tile_uv = fract(mod(world_uv, 1.0) * tiles_per_unit);
Bring the UV into [0, 1) before the big multiply — precision stays high throughout.
Local Coordinates Help Too
If you control the mesh, use object-space UVs (small magnitudes per object) instead of world-space. The shader never sees the huge numbers.
highp Where Available
On mobile, declare precision explicitly: precision highp float; in the vertex/fragment scope. The default mediump is the main reason fract bands so quickly there.
Verifying
The tiling pattern is crisp at the origin and in distant chunks alike. No visible step-banding as the camera moves through the world.
“Float precision shrinks with magnitude. Wrap UVs into a small range before fract, or use local coords.”
The same trick rescues distant noise functions — wrap inputs to a small domain before the hash/sin/whatever.