Skip to content
Fundamental · CoordinatesBeginner

UV & aspect ratio

Turn a pixel's position into 0–1 coordinates, center them, and keep circles round.

How it works

A fragment shader runs once per pixel, and the first thing most shaders need to know is where that pixel is. GLSL gives you gl_FragCoord, the pixel’s position in the window, in pixels: gl_FragCoord.xy is (0.5, 0.5) at the center of the bottom-left pixel and grows toward the top right. Pixels are an awkward unit, though: a shader written in them looks different on every screen size.

Dividing by the size of the canvas fixes that. The result is the UV coordinate: 0 at the left and bottom edges, 1 at the right and top, whatever the resolution.

// at file scope:
uniform vec2 uResolution; // the canvas size in pixels, set by the host code

// inside main():
vec2 uv = gl_FragCoord.xy / uResolution; // 0..1 on both axes

On a mesh, UVs come from the model instead: each vertex stores a 2D coordinate saying where it sits on a flat texture, and the rasterizer interpolates it across the triangle. Either way, the fragment shader sees a vec2 that runs from 0 to 1 across the surface. The cards on this site hand it to you as i.uv, with the origin at the bottom left.

See it as color

The quickest way to understand a coordinate is to output it. Writing uv into the red and green channels paints black at the bottom left, red at the bottom right, green at the top left and yellow at the top right:

#version 300 es
precision highp float;

uniform vec2 uResolution;
out vec4 fragColor;

void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
    fragColor = vec4(uv, 0.0, 1.0);
}

This is also your first debugging tool: any value between 0 and 1 can be checked by writing it to a color channel.

Centering

Radial shapes are easier to write around the middle of the screen. Doubling and shifting moves the origin to the center and the range to -1..1:

vec2 p = uv * 2.0 - 1.0; // -1..1, with (0, 0) in the middle

Now length(p) is the distance from the center, the start of every circle and ring (see vectors).

Aspect ratio

UVs stretch with the canvas. On a 1600×900 canvas, 0.1 in x covers 160 pixels but 0.1 in y only 90, so a circle drawn as length(p) < 0.5 comes out as an ellipse. Multiplying x by the aspect ratio (width divided by height) makes one unit the same number of pixels on both axes:

p.x *= uResolution.x / uResolution.y;

A common shorthand centers and corrects in one line, dividing by the height only:

vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / uResolution.y;

Here y runs from -1 to 1 and x runs a little further on a wide screen. Pick one convention per shader and keep it: every number in the shapes that follow depends on it.

Further reading