Math Playground
Back to Graphics

Graphics › Coordinate spaces

Aspect ratio & the viewport

NDC is a square; your window usually isn't. Bake the width-to-height ratio into the projection matrix or your perfect cube turns into an egg.

Projection — orthographic vs perspective
image planeeyenear (d=1.4)mid (d=2.6)far (d=4)

what the camera records

Rays converge on the eye, so the far object lands closer to the centre and is recorded smaller — that's the ÷ depth that gives a sense of distance. Widen the FOV and more of the scene crams onto the same image.

The squashing problem

NDC maps to a square(−1…1 in both x and y), but the viewport transform then stretches that square to fill a window that's, say, 1600×900. If the projection didn't account for it, that horizontal stretch (1600/900 ≈ 1.78×) would make every circle an ellipse and every cube a shoebox.

The fix: aspect ratio in the projection matrix

Define aspect = width / height and divide the x-scale of the projection matrix by it. In a perspective projection the matrix's [0][0] entry becomes (1 / tan(fov/2)) / aspect: the x direction is “pre-squeezed” by exactly the amount the viewport will later stretch it, so the two cancel and shapes come out true. Convention is to keep the vertical FOV fixed and let the horizontal FOV widen on wide screens (“Hor+”).

Resize handling, in one line

On every window resize: update the viewport to the new pixel size and rebuild the projection matrix with the new aspect = newWidth / newHeight. Do one but not the other and you get stretched graphics or a scene that doesn't fill the window.

Letterboxing & pillarboxing

When you must preserve a specific framing (a cutscene, a fixed-camera puzzle), you instead pick a target aspect and add black bars: shrink the viewport to that ratio and centre it — letterbox (bars top & bottom) on a too-wide window, pillarbox (bars left & right) on a too-tall one. The projection stays fixed; only the viewport rectangle changes.

Orthographic cameras need it too

An orthographic camera defined by a half-height h should use half-width h · aspect, for the same reason — otherwise your 2D/isometric scene scrolls faster horizontally than vertically.