• No results found

Part Overview

Chapter 6: The Rendering Pipeline Overview

6.4 The Rendering Pipeline

6.4.4 Projection Transformation

Once all the vertices of the scene are in view space and lighting has been completed, a projection transformation is applied. After projection, Direct3D expects the coordinates of the vertices inside the frustum to be in so-called normalized device coordinates, where the x- and y-coordinates are in the range [-1, 1], and the z-coordinates are in the range [0, 1]. Projected vertices with z-coordinates outside these ranges correspond to unprojected vertices outside the frustum. Note, then, that the projection transform maps the frustum into a box (sometimes called the canonical view volume). This may seem counterintuitive because isn't the projection transformation supposed to generate 2D projected points? Indeed it actually does: The transformed x- and y-coordinates represent the 2D projected vertices, and the normalized z-coordinate is just used for depth buffering. (Remember that we still need to keep attached the relative depth information for each vertex for the depth buffering algorithm.)

6.4.4.1 Defining a Frustum and Aspect Ratio

To define a frustum centered at the origin of view space and looking down the positive z-axis, we specify a near plane n, a far plane f, a vertical field of view angle a, and the so-called aspect ratio. What about the horizontal field of view angle? As we will now show, the aspect ratio allows us to determine the horizontal field of view angle ß. The aspect ratio R is defined by the ratio R = w/h, where w is the width of the view window and h is the height of the view window (units in view space). For consistency, we like the ratio of the projection window dimensions to be the same as the ratio of the back buffer dimensions; therefore, the ratio of the back buffer dimensions is typically specified as the aspect ratio (it's a ratio so it has no units). For example, if the back buffer dimensions are 800×600, then we specify R = 800/600 = 4/3˜1.33333.

To see how R helps us find ß, consider Figure 6.17.

Figure 6.17: Right triangle relations on the xz- and yz-planes.

Using some trigonometry, we have (a/2) = d/n, and (ß/2) = c/n. (Note that h = 2d and w = 2c.) However, we also have the aspect ratio relationship: R = w/h ⇒ w = Rh ⇒ w/2 = R h/2. Therefore, we can write:

(6.3)

So given the vertical field of view angle a and the aspect ratio R, we can always get the horizontal field of view angle ß.

6.4.4.2 Projecting Vertices

We first handle the x- and y-coordinates. Consider Figure 6.17 again, and recall that we define the perspective projection transformation as the transformation that transforms a 3D vertex v to the point v' where its line of projection intersects the 2D projection plane (here the projection plane is the near plane). By considering the x- and y-coordinates separately and using trigonometry (similar triangles, specifically), we find:

So now we have the 2D projected point (x', y') on the near plane.

Observe from Figure 6.17 that projected points correspond to unprojected points inside the frustum if and only if both -c = x' = c and -d = y' = d. Projected points outside these ranges correspond to unprojected points outside the frustum.

6.4.4.3 Normalizing the Coordinates

Recall, however, that Direct3D wants the x- and y-coordinates of the projected points that are in the frustum to be in the normalized range [-1, 1], and projected points that are outside the frustum to be outside the normalized range [-1, 1]. Observe that if we divide x' and y' by c and d, respectively, then we get exactly what we want:

-c = x' = c ⇒ -1 = x'/c = 1 -d = y'< d ⇒ -1 = y'/d = 1

Thus we have the projected and normalized x- and y-coordinates:

(6.4)

(6.5)

Here we used Equation 6.3 and the trigonometric facts that c = n tan(ß/2) and d = n tan(a/2).

6.4.4.4 Transforming the z-coordinate

Equations 6.4 and 6.5 give the formulas for computing the projected and normalized x- and y-coordinates. So as far as projection in the geometric sense is

concerned, we are done. However, recall that Direct3D also wants the z-coordinates of points inside the frustum mapped to the normalized interval [0, 1], and points outside the frustum mapped outside [0, 1]. In other words, we want to map the interval [n, f] to [0, 1].

As we will explain in the next section, whatever transformation result we get here, we will multiply by z. After this multiplication by z, we want the transformation to have the form = zg(z) = uz + v so that we can write it in a matrix equation. Therefore, to transform [n, f] to [0, 1], we guess that a transformation of the form

= g(z) = u + v/z does the job. This equation has two unknown variables, u and v, but we also have two conditions that characterize the transformation we want:

Condition 1: g(n) = u + v/n = 0 (the near plane gets mapped to zero) Condition 2: g(f) = u + v/f = 1 (the far plane gets mapped to one)

Solving condition 1 for v yields: v = -un. Substituting this into condition 2 and solving for u gives:

u+(-un)/f =1 uf -un = f u = f /(f -n)

Then v = -un = -fn/(f - n). Therefore, (6.6)

Graphing g(z), as shown in Figure 6.18, we see that it does what we want: It maps only the points in [n, f] to [0, 1].

Figure 6.18: Note how [n, f] gets mapped to [0, 1]. Also notice for g(z, n = 10, f = 100) how an unfairly large portion of [0, 1] is devoted to depth values in close proximity to the near plane, whereas depth values close to the far plane get mapped to a very small portion of [0, 1] (i.e., the transformed depth values of objects close to the far plane only differ slightly from each other); this can lead to depth buffer precision problems (especially with 16-bit depth buffers, but not so much with 24- or 32-bit depth buffers) because the computer can no longer distinguish between slightly different transformed depth values due to the finite numerical representation. Observe that the problem is less drastic with g(z, n = 30, f = 100), as we bring the near plane closer to the far

plane.

6.4.4.5 Writing the Projection Equations with a Matrix

We now wish to write Equations 6.4, 6.5, and 6.6 as a matrix equation. However, this gets a bit tricky with the divide by z, which occurs in each equation. So what we do is first multiply each equation by z to get rid of the divisions by z so that we can write the equations with a matrix equation. Then, after the matrix

multiplication, in a post-step, we divide by z to invert the multiplication by z we initially did. The following perspective projection matrix contains Equations 6.4, 6.5, and 6.6 multiplied by z:

Note also how we copy the z-coordinate of the input vector to the w-coordinate of the output vector (this is done by setting entry [2][3] = 1 and entry [3][3] = 0). We do this so that we have the original z-coordinate available for the divide by z step (i.e., instead of storing the original z-coordinate in a new temporary variable, we just store it in the w-coordinate). We can now complete the projection with a post-step by dividing every coordinate in the output vector of the above matrix multiplication by the homogeneous coordinate w = z:

(6.7)

This is commonly called the perspective divide or homogeneous divide; after this divide, the coordinates are said to be normalized device coordinates or just normalized space. Observe that the x-, y-, and z-coordinates of Equation 6.7 match Equations 6.4, 6.5, and 6.6, respectively.

Note In the rendering pipeline, the perspective divide does not take place immediately after the projection matrix multiplication. After the projection matrix takes place, we say vertices are in projection space or homogeneous clip space. In this space, some operations like backface culling and clipping can be done in a simplified manner. These two operations remove geometry, as §6.4.5 and §6.4.6 will show. Thus it is advantageous to do these operations in homogeneous clip space before the perspective divide so that we have fewer vertices on which to perform the perspective divide.

6.4.4.6 D3DXMatrixPerspectiveFovLH

A perspective projection matrix can be built with the following D3DX function:

D3DXMATRIX *D3DXMatrixPerspectiveFovLH(

D3DXMATRIX* pOut, // returns projection matrix

FLOAT fovY, // vertical field of view angle in radians FLOAT Aspect, // aspect ratio = width / height

FLOAT zn, // distance to near plane FLOAT zf // distance to far plane );

The following code snippet illustrates how to use D3DXMatrixPerspective - FovLH . Here, we specify a 45° vertical field of view, a near plane at z = 1, and a far plane at z = 1000 (these dimensions are in view space).

D3DXMATRIX proj;

D3DXMatrixPerspectiveFovLH(

&proj, D3DX_PI * 0.25f,

(float)width / (float)height, 1.0, 1000.0f);