Skip to main content
GameDev.net gamedev.net

Aspect-Correct Coordinates

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

Normalized coordinates do not automatically give equal visual units on both axes. This example draws a distance-based circle and provides a checkbox that compares raw normalized space with an aspect-correct space. On a wide viewport, disabling correction stretches the circle into an ellipse.

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 Image5
1 uniform bool uCorrectAspect; // @param = 1 "Correct aspect ratio"
2
3 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
4 vec2 centered = fragCoord / iResolution.xy - 0.5;
5 vec2 displaySpace = centered;
6 if (uCorrectAspect) {
7 displaySpace.x *= iResolution.x / iResolution.y;
8 }
9
10 float radius = length(displaySpace);
11 float circle = 1.0 - smoothstep(0.24, 0.255, radius);
12 vec3 color = mix(vec3(0.04, 0.06, 0.10), vec3(0.20, 0.78, 0.96), circle);
13 fragColor = vec4(color, 1.0);
14 }
15

Learn from this shader

How it works

The shader first normalizes pixel coordinates and subtracts one half, placing the origin at the viewport center. Both resulting axes span the same numeric range, but one unit along x covers the viewport width while one unit along y covers its height. When correction is enabled, x is multiplied by width divided by height. A horizontal shader-space unit then covers the same number of screen pixels as a vertical unit. length measures radial distance in that corrected space, and smoothstep turns the distance into a circle with a narrow soft edge.

Try changing

Resize the preview while toggling Correct aspect ratio. Replace the width-over-height multiplier with its reciprocal and predict the distortion. In a fork, divide both centered coordinates by iResolution.y directly, or change the radius thresholds to study how size and edge softness are separate concerns.

Using it in a game

Aspect correction matters for full-screen reticles, radial transitions, procedural UI shapes, and screen-space particles. A material using mesh UVs may not need this exact correction. In an engine, use the render target or viewport dimensions supplied by that pipeline rather than assuming ShaderLab uniform names are available.

Explore the techniques

Continue with curated explanations and progressively related examples.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...