Transforming Space

Moving the coordinate system instead of objects

A Different Way of Thinking

In most programming, when you want to move something to the right, you add to its x position. In shader programming, we think differently. Instead of moving objects, we move the coordinate system itself.

This might seem backward at first, but it unlocks a powerful mental model. Rather than asking "where should this object go?", we ask "what coordinates should this pixel have?". We are not moving the painting; we are moving the canvas underneath it.

Translation: Shifting Space

To move a shape to the right, we subtract from the x coordinate. Yes, subtract. This is the counterintuitive core of coordinate space manipulation.

Think of it this way: if we subtract 0.3 from every pixel's x coordinate, a pixel that was at x = 0.3 now thinks it is at x = 0. If our shape is drawn at the origin, it will now appear at position 0.3 on the screen.

vec2 uv = gl_FragCoord.xy / u_resolution;
uv = uv * 2.0 - 1.0;  // center first
uv.x -= 0.3;          // shift shape right by 0.3
glsl

Interactive: Translation

uv=uv(0.00,0.00)uv = uv - (0.00, 0.00)

Subtracting (0.00, 0.00) moves the shape to (0.00, 0.00)

Watch how subtracting from coordinates moves the shape in the positive direction. The shape moves opposite to the subtraction because we are shifting where each pixel thinks the origin is.

Scaling: Zooming Space

Scaling follows a similar counterintuitive pattern. To make a shape larger (zoom in), we divide the coordinates by a value greater than 1. To make it smaller (zoom out), we multiply.

uv = uv * 2.0;  // shapes appear half as big (zoom out)
uv = uv / 2.0;  // shapes appear twice as big (zoom in)
glsl

Interactive: Scaling

uv=uv×1.0uv = uv \times 1.0

Scale of 1 leaves the shape unchanged

When we multiply coordinates by 2, every unit of our shape now spans only half a unit of screen space. The shape shrinks. When we divide by 2, every unit spans two screen units. The shape grows.

This is the same principle as translation: we are changing what the coordinates mean, not where the shape is drawn.

The Order of Operations

Transformations do not commute. Translating then scaling produces a different result than scaling then translating.

Consider this: if you scale coordinates by 2 first, then translate by (0.5, 0), the translation happens in the already-scaled coordinate space. But if you translate first by (0.5, 0) and then scale by 2, the translation gets amplified to (1.0, 0) in the final result.

// Scale then translate: translation is in scaled space
uv = uv * 2.0;
uv = uv - vec2(0.5, 0.0);
 
// Translate then scale: translation gets amplified
uv = uv - vec2(0.5, 0.0);
uv = uv * 2.0;
glsl

The general rule: transformations apply in reverse order to how they appear visually. If you want to first move an object then scale it, you write the scale operation first in your code, then the translation.

Interactive: Transformation Playground

1. uv = uv * 1.5

2. uv = uv - (0.30, 0.00)

Translation happens in scaled space

Experiment with the order of operations. Notice how the same values produce different results depending on whether you scale first or translate first.

Aspect Ratio Correction

Real screens are rarely square. A 16:9 monitor has pixels that represent different physical distances horizontally versus vertically. Without correction, a circle drawn with length(uv) appears as an ellipse.

The fix is elegant: multiply the x coordinate by the aspect ratio before doing any distance calculations.

vec2 uv = gl_FragCoord.xy / u_resolution;
uv = uv * 2.0 - 1.0;
uv.x *= u_resolution.x / u_resolution.y;
glsl

Aspect Ratio Correction

uv.x *= u_resolution.x / u_resolution.y;

With correction, one unit in x equals one unit in y, and circles stay circular. This is particularly important for any shader that relies on distance functions or radial symmetry.

Building a Transform Pipeline

In practice, shader transformations follow a consistent pattern:

vec2 uv = gl_FragCoord.xy / u_resolution;  // normalize to 0-1
uv = uv * 2.0 - 1.0;                        // center to -1 to 1
uv.x *= u_resolution.x / u_resolution.y;   // aspect correction
uv = uv * scale;                           // apply scale
uv = uv - offset;                          // apply translation
glsl

This pipeline becomes second nature. Normalize, center, correct aspect ratio, then apply your creative transformations. The consistency makes shaders easier to reason about and debug.

Looking Ahead

Translation and scaling are linear transformations. In the next chapter, we add rotation to our toolkit, introducing trigonometry into the mix. Understanding how to combine translation, scale, and rotation will let you position and orient anything on screen.

Key Takeaways

  • We transform coordinates, not objects; subtracting moves shapes in the positive direction
  • Scaling multiplies coordinates: larger multipliers make shapes smaller (zoom out)
  • Transformation order matters: transformations apply in reverse visual order
  • Aspect ratio correction ensures circles stay circular on non-square screens
  • A standard pipeline (normalize, center, correct, transform) makes shaders predictable