Non-square Viewports

How to 3D

Chapter 5: Graphics Pipeline

Non-square Viewports

We've just learned that WebGL renders only the unit cube to the viewport. What happens to that cube, then, if the viewport isn't square? Distortion. The cube will be stretched to fit the oblong viewport. Try it out in this renderer:

The model is a unit cube. Resize your browser and test it when the viewport's squarish and when it's oblong.

The way to avoid distortion is to ensure that the viewing volume and viewport have the same width-to-height ratio or aspect ratio.

The viewport's width and height is often determined by the browser window, and we as developers do not usually decide the window size. However, we do get to choose the six viewing volume parameters. To make the viewing volume's aspect ratio match the viewport's, we want to make this statement true:

$$\begin{aligned} \frac{\mathrm{viewport\ width}}{\mathrm{viewport\ height}} = \frac{\mathrm{right} - \mathrm{left}}{\mathrm{top} - \mathrm{bottom}} \end{aligned}$$

In the renderers we have written so far, the likely place to compute the viewport's aspect ratio is in resizeCanvas. We compute the aspect ratio at the end of that method with the following statement:

const aspectRatio = canvas.width / canvas.height;
const aspectRatio = canvas.width / canvas.height;

Many renderers will center the viewing volume around the eye. In such cases, the viewing volume has the following symmetry:

$$\begin{aligned} \mathrm{left} &= -\mathrm{right} \\ \mathrm{bottom} &= -\mathrm{top} \\ \end{aligned}$$

This symmetry allows us to simplify how we compute the aspect ratio of the viewing volume:

$$\begin{aligned} \frac{\mathrm{right} - \mathrm{left}}{\mathrm{top} - \mathrm{bottom}} &= \frac{\mathrm{right} - -\mathrm{right}}{\mathrm{top} - -\mathrm{top}} \\ &= \frac{2 \times \mathrm{right}}{2 \times \mathrm{top}} \\ &= \frac{\mathrm{right}}{\mathrm{top}} \end{aligned}$$

This then is the statement that we need to make true:

$$\frac{\mathrm{viewport\ width}}{\mathrm{viewport\ height}} = \frac{\mathrm{right}}{\mathrm{top}}$$

The browser decides \(\mathrm{viewport\ width}\) and \(\mathrm{viewport\ height}\). We as developers decide the value of either \(\mathrm{right}\) or \(\mathrm{top}\). Once we do, we solve for the remaining term. If we fix \(\mathrm{right}\), say at 8, then we calculate \(\mathrm{top}\) as follows:

const right = 8;
const top = right / aspectRatio;
const right = 8;
const top = right / aspectRatio;

If we fix \(\mathrm{top}\), say at 8, then we calculate \(\mathrm{right}\) as follows:

const top = 8;
const right = top * aspectRatio;
const top = 8;
const right = top * aspectRatio;

Once we have both variables assigned, we use them to generate a projection matrix whose aspect ratio matches the viewport:

const clipFromEye = Matrix4.ortho(-right, right, -top, top, near, far);
const clipFromEye = Matrix4.ortho(-right, right, -top, top, near, far);

The renderer will no longer distort the unit cube.

← Orthographic ProjectionPerspective Projection →