Skip to content
Fundamental · TransformsBeginner

2D transforms

Translate, rotate and scale UV space with vectors and a mat2, and why you move space instead of the shape.

How it works

A shape in a fragment shader is a function of the pixel’s coordinate: a circle is “the pixels where length(p) is below 0.3”. There is no circle object to move. To move, turn or resize it, you change the coordinate before the shape function sees it. Every pattern on this site, from a checkerboard to a noise field, moves the same way.

Translate

Subtracting an offset from the coordinate moves the shape by that offset:

vec2 p = uv * 2.0 - 1.0;                // centered, -1..1
vec2 q = p - vec2(0.4, 0.2);            // the circle's center is now at (0.4, 0.2)
float circle = step(length(q), 0.3);

It feels backward at first. The shape is drawn where q is near zero, and q is zero at the pixel p = (0.4, 0.2). Subtracting moves the shape forward. That reversal is the heart of this fundamental: you are transforming the space the shape is drawn in, so every operation runs as the inverse of the one you want the shape to do.

Scale

Dividing the coordinate makes the shape bigger: p / 2.0 lets the coordinate grow half as fast, so everything appears twice the size. Multiplying makes it smaller, and repeats more of a pattern: uv * 8.0 fits eight checker cells across. Dividing x and y by different amounts stretches the shape, as uncorrected aspect ratio does. A distance measured in the scaled space is scaled too; that matters for the distance fields later in the order.

Rotate

Rotation by an angle a around the origin sends (x, y) to (x cos a − y sin a, x sin a + y cos a). That is a matrix multiplication, and GLSL has a type for it:

mat2 rotate2d(float a) {
    float c = cos(a);
    float s = sin(a);
    return mat2(c, s, -s, c); // columns: (c, s) and (-s, c)
}

vec2 q = rotate2d(-angle) * p; // turns the shape by +angle, counterclockwise

The minus sign is the same inverse as before: to turn the shape counterclockwise, turn the space clockwise. angle is in radians: a full turn is 2π, about 6.283, and 90° is π / 2.

GLSL matrices are column-major: the constructor fills the first column, then the second. mat2(c, s, -s, c) has (c, s) as its first column, so it is the matrix written in math books as [c −s; s c]. Reading the four numbers as rows instead gives the transposed matrix, which for a rotation is the inverse: it turns the other way. That is the most common rotation bug. The same fact gives a shortcut: p * m multiplies by the transpose, so p * rotate2d(angle) is the same as rotate2d(-angle) * p.

Order and pivots

Translate, rotate and scale don’t commute: rotating then moving is not the same as moving then rotating. To spin a shape around its own center instead of the origin, move the space so the center is at the origin, rotate, and draw there:

vec2 center = vec2(0.4, 0.2);
// the shape, at center, turned by angle around its own center
vec2 q = rotate2d(-angle) * (p - center);

For a full transform of a shape (scale s, then rotate by a, then move to t), the space undoes those steps in the opposite order: subtract t, rotate by -a, divide by s.

vec2 q = (rotate2d(-a) * (p - t)) / s;

Fix the aspect ratio before any rotation. Rotating in stretched UVs mixes x and y while they have different units, and the shape shears as it turns.

Transforming patterns and textures

The same operations work on anything that reads a coordinate. Rotate uv before fract and the whole grid turns. Scale it over time and a pattern zooms. Add time * speed and it scrolls. For a texture, rotate around its middle by centering first: rotate2d(-a) * (uv - 0.5) + 0.5, then sample with the result. Applying a different transform at every pixel, from a wave or from noise, is UV warping and domain warping: the same idea, with the offset varying across the screen.

A mat2 holds rotation and scale, but not translation, which is why the offset is added separately. Folding translation into the matrix too takes one more dimension, a mat3 on vec3(p, 1.0); in 3D that becomes the mat4 of the model, view and projection matrices.

Further reading