Skip to main content
GameDev.net gamedev.net

Repeating and Tiling UVs

by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026

Use in your engine

Run the shader to adjust these controls.

What it demonstrates

This shader repeats one circular motif across a configurable grid. It separates global UVs into a cell identifier and a local coordinate inside each cell. Integer Rows and Columns controls show how repetition density can change while the motif code remains constant.

Source Revision 1

Author notes are linked to specific lines.

Shader inputs

void mainImage(out vec4 fragColor, in vec2 fragCoord)

Called once per pixel. Write the colour to fragColor.

iResolution vec3
Viewport size in pixels (z is the pixel aspect ratio).
iTime float
Seconds since the shader started.
iTimeDelta float
Seconds since the previous frame.
iFrameRate float
Frames per second, smoothed.
iFrame int
Frames rendered since the start.
iMouse vec4
Mouse position: xy while held, zw of the last click.
iDate vec4
Year, month, day, and seconds within the day.
iChannel0 sampler2D
Texture bound to channel 0.
iChannel1 sampler2D
Texture bound to channel 1.
iChannel2 sampler2D
Texture bound to channel 2.
iChannel3 sampler2D
Texture bound to channel 3.
iChannelResolution vec3[4]
Pixel size of each bound channel texture.
iChannelTime float[4]
Playback time of each channel, in seconds.
iSampleRate float
Audio sample rate, always 44100.
Main Image6
1 uniform int uColumns; // @param 1..12 = 7 "Columns"
2 uniform int uRows; // @param 1..12 = 5 "Rows"
3
4 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
5 vec2 uv = fragCoord / iResolution.xy;
6 vec2 gridSize = vec2(float(uColumns), float(uRows));
7 vec2 tiled = uv * gridSize;
8 vec2 cellId = floor(tiled);
9 vec2 cellUv = fract(tiled) - 0.5;
10 float dotShape = 1.0 - smoothstep(0.25, 0.32, length(cellUv));
11 float checker = mod(cellId.x + cellId.y, 2.0);
12 vec3 tileColor = mix(vec3(0.12, 0.38, 0.70), vec3(0.82, 0.28, 0.32), checker);
13 vec3 color = mix(vec3(0.025, 0.035, 0.06), tileColor, dotShape);
14 fragColor = vec4(color, 1.0);
15 }
16

Learn from this shader

How it works

Multiplying normalized UV coordinates by the grid dimensions expands their range to cover several cells. floor extracts the integer part, which identifies the current cell. fract extracts the repeating fractional part, resetting from zero to one in every cell; subtracting one half centers that local space. A radial distance and smoothstep draw the same soft circle in each local coordinate system. Repetition does not erase global structure: the stored cell identifier is summed and reduced modulo two to alternate colors like a checkerboard.

Try changing

Use unequal Rows and Columns and notice that each dot follows its cell proportions because the lesson stays in UV space. In a fork, correct each cell to a circle in screen pixels, replace fract(tiled) - 0.5 with fract(tiled + 0.5) - 0.5 to shift boundaries, or hash cellId to vary size and color. Draw cell borders from the absolute local coordinates.

Using it in a game

Tiled coordinates support procedural floors, grids, halftones, repeated decals, and atlas-like material logic. Mesh UV scale and texture wrapping may already perform part of this job, but retaining cell identity enables controlled per-tile variation without an external asset. Watch filtering and derivatives at discontinuous fract boundaries in detailed production materials.

Explore the techniques

Continue with curated explanations and progressively related examples.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...