The Clip Space to Device Space Map

by William Shoaff with lots of help


Contents

The Clip Space to Device Space Map

You might want to read Blinn's article on pixel coordinates now, see [1].

We want to map directly from clip space to device space, never stopping in normalized device space. Normalized device space, or simply normalized space is a rectangle that runs from $-1 \leq x \leq 1$ and $-b \leq y \leq b$, where b is the physical aspect ratio of the display device. Normalize device space hides from us:

1.
The actual number of pixels in x and y.
2.
Non-uniform pixel spacing in x and y: Distances in normalized space are uniform.
3.
Up versus down for the y coordinate. The normalized to device space map will invert y if necessary.
Normalized device space is a standardized screen coordinate system that does not include explicit pixel information, which helps to make programs more portable among differing display devices.

Notice that we are not really much interested in z from now on, except later we may want to do some hidden surface analysis using the z value. But for right now, we can work in a 2 dimensional space. We know how to get from clip space to normalized space; well not really, perhaps. Our clipping algorithms are often executed prior to the homogeneous divide, so we need to

1.
Divide our coordinates $(x,\,y,\,z,\,w)$ by w (provided $w\neq 0$ which should always be the case)
2.
Map the clipping unit square $0\leq x \leq 1$, $0\leq y \leq 1$, into a display window or viewport $-1\leq x_l \leq x \leq x_r \leq 1$, $-b \leq y_b \leq y \leq y_t \leq b$ that has aspect ratio a=(yt-yb)/(xr-xl) of the region of prospective space that was mapped to clip space.
But this last map will be rolled into our map directly into device space. Notice the distinction between the display window, which has aspect ratio a, and the display device, which has aspect ratio b. The display device is the entire screen, the display window is just a portion of it.

Now we need a map from normalize space to device space. Given our definition of clip space at

\begin{displaymath}0 \leq x \leq 1 \quad 0\leq y \leq 1\end{displaymath}

which is mapped to viewport

\begin{displaymath}-1 \leq x_{l} \leq x \leq x_{r} \leq 1, \quad -b \leq y_{b} \leq y \leq
y_{t} \leq b,\end{displaymath}

where $(x_l,\,x_b)$ is the left-bottom corner and $(x_r,\,x_t)$ is the right-top corner of the display window with aspect ratio

\begin{displaymath}a = \frac{y_t-y_b}{x_r-x_l},\end{displaymath}

it is easy to see that the matrix

\begin{displaymath}M_{CN} =\left[\begin{array}{cccc}
x_{r} - x_{l} & 0 & 0 & 0 \...
...
0 & 0 & 1 & 0 \\
x_{l} & y_{b} & 0 & 1 \\
\end{array}\right]\end{displaymath}

maps points in clip space into points in the viewport. What is needed is a map from normalized coordinates to device coordinates.

Before proceeding with this though, let's mention that viewports are most often specified in pixel coordinates, so how does one compute their normalized range? Let's pretend the display has resolution $N_x \times N_y$. For concreteness assume Nx = 1280 and Ny=1024 so the aspect ratio of the physical display device is b = 1024:1280 = 0.80. Horizontal pixels are numbered from 0 to Nx-1=1023 and vertical scan lines are numbered from 0 to Ny-1=1279.

Let's pick a pixel viewport where it will be easy to verify the arithmetic, say $P_l=0\leq p_x \leq 639=P_r$ by $P_t=512\leq p_y \leq 767=P_b$, so, intuitively, x in normalized space runs from -1 to about 0 and y runs from about 0 to about 1/2 of 0.8. One way to to look at this is that the normalized range expressed above should map to the pixel range express here. That is,

\begin{eqnarray*}x_l \rightarrow P_l \\
x_r \rightarrow P_r \\
x_b \rightarrow P_t \\
x_t \rightarrow P_b \\
\end{eqnarray*}


Notice the reversal of top and bottom in device space.

We are given the pixels and wish to find the real coordinates in normalized space. We know that this maps is a scale and translation, so in x we have

\begin{displaymath}s_x P_l + t_x = x_l \quad \mbox{and} \quad s_x P_r + t_x = x_r. \end{displaymath}

But this affine map works for the entire display as well as for a display window within it. Thus,

\begin{displaymath}s_x \cdot 0 + t_x = -1 \quad \mbox{and} \quad s_x N_x + t_x = 1, \end{displaymath}

and

\begin{displaymath}s_y \cdot 0 + t_y = b \quad \mbox{and} \quad s_y N_y + t_y = -b. \end{displaymath}

From which we discover that

\begin{displaymath}s_x = 2/N_x \quad \mbox{and} \quad t_x = -1\end{displaymath}

and

\begin{displaymath}s_y = -2b/N_y \quad \mbox{and} \quad t_y = b.\end{displaymath}

For our particular example,

\begin{displaymath}s_x = 2/1280=1/640 \quad \mbox{and} \quad t_x = -1\end{displaymath}

and

\begin{displaymath}s_y = -1.6/1024=1/640 \quad \mbox{and} \quad t_y = 0.8.\end{displaymath}

From which we can find the normalized coordinates xl, xr, yb, and yt using the upper left and lower right coordinates of the prescribed display window:

\begin{displaymath}\left[\begin{array}{ccc}
0 & 512 & 1 \\
639 & 767 & 1 \end...
...egin{array}{cc}
x_l & y_b \\
x_r & y_t \\ \end{array}\right]\end{displaymath}

Thus, the map from clip space to normalized space, for this example is,

\begin{displaymath}M_{CN} =\left[\begin{array}{cccc}
639/640 & 0 & 0 & 0 \\
0 &...
...& 0 \\
0 & 0 & 1 & 0 \\
-1 & 0 & 0 & 1 \\
\end{array}\right]\end{displaymath}

The Classical Normalized Space to Device Space Map

Suppose the display has resolution $N_x \times N_y$. For concreteness assume Nx = 1280 and Ny=1024 so the aspect ratio of the physical display device is b = 1024:1280 = 0.80. Horizontal pixels are numbered from 0 to Nx-1=1023 and vertical scan lines are numbered from 0 to Ny-1=1279.

We will define normalized space as the rectangle

\begin{displaymath}-1 \leq x \leq 1 \quad -b \leq y \leq b,\end{displaymath}

so it is not really device independent, but it can be initialized during the startup of the graphics system.

The obvious transform is defined by the assignments:

\begin{displaymath}-1 \mapsto 0,\, 1 \mapsto N_x-1, -b \mapsto N_y-1, b \mapsto 0,\end{displaymath}

which yields scales and translates

\begin{eqnarray*}s_{x} & = & \frac{N_x-1}{2} \\
t_{x} & = & \frac{N_x-1}{2} \\
s_{y} & = & \frac{N_y-1}{-2b} \\
t_{y} & = & \frac{N_y-1}{2} \\
\end{eqnarray*}


that we store away in a matrix:

\begin{displaymath}M_{ND} =\left[\begin{array}{cccc}
\frac{N_x-1}{2} & 0 & 0 & 0...
...rac{N_x-1}{2} & \frac{N_y-1}{2} & 0 & 1 \\
\end{array}\right].\end{displaymath}

For our sample device

\begin{eqnarray*}s_{x} & = & 639.5 \\
t_{x} & = & 639.5 \\
s_{y} & = & -639.375 \\
t_{y} & = & 511.5 \\
\end{eqnarray*}


Of course, this derivation is wrong, even though this is how every graphics text describes the mapping. Why is is wrong? Well, because it applies a non-uniform scale in x and y, which, although small can cause visible errors in the rendered scene.

Pixels Have Size

The above map would be correct if pixels, like points, had no dimension, but they do occupy a region of space. If we do not allow for this our graphics images may have visible seams.

It is better to let a pixel coordinate (x, y) denote the center of a unit square defined by:

\begin{displaymath}x-0.5 \leq x \leq x+0.5 \quad y-0.5 \leq y \leq y+0.5.\end{displaymath}

With this convention, the assignment of corners is:

\begin{displaymath}-1 \mapsto -0.5,\, 1 \mapsto N_x-0.5, -b \mapsto N_y-0.5,
b \mapsto -0.5\end{displaymath}

and we find

\begin{eqnarray*}s_{x} & = & \frac{N_x}{2} \\
t_{x} & = & \frac{N_x-1}{2} \\
s_{y} & = & \frac{-N_y}{2b} \\
t_{y} & = & \frac{N_y-1}{2} \\
\end{eqnarray*}


that we store away in a matrix:

\begin{displaymath}M_{ND} =\left[\begin{array}{cccc}
\frac{N_x}{2} & 0 & 0 & 0 \...
...rac{N_x-1}{2} & \frac{N_y-1}{2} & 0 & 1 \\
\end{array}\right].\end{displaymath}

For our sample device

\begin{eqnarray*}s_{x} & = & 640 \\
t_{x} & = & 639.5 \\
s_{y} & = & -640 \\
t_{y} & = & 511.5 \\
\end{eqnarray*}


As you might have guessed this is not quite right either.

Pixels space is discrete

Pixels are addressed by integers pairs; when a normalized point, which is a floating point pair, is mapped to a pixel we must round to an integer pair. This is accomplished by adding 0.5 and truncating. We can build this into the normalized to device map.

\begin{displaymath}-1 \mapsto 0,\, 1 \mapsto N_x, -b \mapsto N_y, b \mapsto 0.\end{displaymath}

I'll let you determine the scales and translates that are needed now. But as you might have guessed their is another problem now.

Dangling Edges

A normalized point with x=1 or y=-b will map to a pixel with x=Nx or y=Ny which are outside the range of legitimate pixel addresses. Blinn's solution to this is to cheat! It isn't really; it's a matter of floating point error in trying to map a continuous space to a discrete space. For example, you never test a floating point number for exact equality (do you?).

In the old days, one often chose a tuning parameter to make code work correctly. He we want to choose a parameter $\epsilon$ so that no point in normalized space gets mapped to Nx or Ny. Blinn suggests $\epsilon = 0.001$ and the assignment:

\begin{displaymath}-1 \mapsto 0,\, 1 \mapsto N_x-\epsilon, -b \mapsto N_y-\epsilon, b \mapsto 0.\end{displaymath}

With this assignment we find

\begin{eqnarray*}s_{x} & = & \frac{N_x-\epsilon}{2} \\
t_{x} & = & \frac{N_x-\e...
...ac{N_y-\epsilon}{-2b} \\
t_{y} & = & \frac{N_y-\epsilon}{2} \\
\end{eqnarray*}


And for our sample device

\begin{eqnarray*}s_{x} & = & 639.9995 \\
t_{x} & = & 639.9995 \\
s_{y} & = & -639.9993 \\
t_{y} & = & 511.9995 \\
\end{eqnarray*}


This is the correct normalized to device map!

\begin{displaymath}M_{ND} =\left[\begin{array}{cccc}
\frac{N_x-\epsilon}{2} & 0 ...
...on}{2} & \frac{N_y-\epsilon}{2} & 0 & 1 \\
\end{array}\right].\end{displaymath}

Where Are We Now?

In the last few lectures we've seen that the tail of the graphics pipeline is implemented by:

1.
A map of points in perspective space to points in clip space using the matrix

\begin{displaymath}M_{PC} =\left[\begin{array}{cccc}
\frac{1}{2} & 0 & 0 & 0 \\ ...
... 0 \\
\frac{1}{2} & \frac{1}{2} & 0 & 1 \\
\end{array}\right]\end{displaymath}

where a is the aspect ratio of the viewport.
2.
Next a a clipping algorithm is run to cut off points that lie outside of the unit square of clip space.
3.
The homogeneous divide is performed.
4.
Then what's left is mapped to device space using the composition of matrix transforms:

\begin{displaymath}M_{CN} =\left[\begin{array}{cccc}
x_{r} - x_{l} & 0 & 0 & 0 \...
...
0 & 0 & 1 & 0 \\
x_{l} & y_{b} & 0 & 1 \\
\end{array}\right]\end{displaymath}

and

\begin{displaymath}M_{ND} =\left[\begin{array}{cccc}
\frac{N_x-\epsilon}{2} & 0 ...
...on}{2} & \frac{N_y-\epsilon}{2} & 0 & 1 \\
\end{array}\right].\end{displaymath}

The first map MCN takes clip space to the viewport; The second map MND takes all of normalize space to all of device space, in particular, it maps the viewport to the specified region on the display. The composition matrix is

\begin{displaymath}M_{CD} =\left[\begin{array}{cccc}
(x_{r} - x_{l})\frac{N_x-\e...
...on}{-2b}+\frac{N_y-\epsilon}{2} & 0 & 1 \\
\end{array}\right].\end{displaymath}

Bibliography

1
J. BLINN, Pixel coordinates, IEEE Computer Graphics and Applications, 11 (1991), pp. 81 - 85.



William D. Shoaff
2002-04-09