Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (11.88 MB, 1,060 trang )

TeamLRN

This works correctly when we are assuming that the frame buffer is taking up the entire screen (or the

entire window). When rendering to a viewport however, DirectX Graphics also has to take into

account the viewport origin and its width and height so that all of the projection space coordinates in

the –1 to +1 range get mapped to coordinates that fall only within the viewport rectangle.

ScreenX = projVertex->x * ViewportWidth / 2 + ViewportX + ViewportWidth / 2;

ScreenY = -projVertex->y * ViewportHeight / 2 + ViewportY + ViewportHeight / 2;

When we discussed the DirectX transformation pipeline in earlier lessons we examined the core

matrices that are used in the process: World, View and Projection. There is actually a fourth matrix

which the vertices are multiplied by which contains the above formula to map projection space

coordinates into screen space coordinates. This matrix is shown below. The third column maps the

vertex depth value into the [minZ, maxZ] range of the view port.

The Viewport Matrix

ViewportWidth / 2

0

− ViewportHeight / 2

0

0

0

ViewportMaxZ − ViewportMinZ

0

0

ViewportX + ViewportWidth / 2 ViewportHeight / 2 + ViewportY

ViewportMinZ

0

0

0

1

All of this is managed behind the scenes by the device object. We simply fill in the details of the

D3DVIEWPORT9 structure and send it to the device with a call to:

IDirect3DDevice9::SetViewport(CONST D3DVIEWPORT9 *pViewport);

The function will force the device to rebuild its viewport matrix based on the settings that we have

passed in with the D3DVIEWPORT9 structure. When the device is first created, the default state of the

viewport matrix is to map projection space coordinates to the entire area of the frame buffer. If we

created a 640x480 frame buffer, the default viewport will be 640x480 also, with its top left corner at

(0, 0). Just to be clear, when we set a viewport, this does not simply truncate the portions of the scene

that are outside the viewport. The entire scene is rendered into the view port as shown in Fig 4.12:

www.gameinstitute.com Graphics Programming with DX9

Page 23 of 64

TeamLRN

Figure 4.12

In the above example, we have a 640x480 frame buffer and a viewport rectangle of (0, 0, 320, 160).

Note that the entire scene is rendered into the viewport rectangle within the frame buffer. When we

present the frame buffer (assuming we do not provide a presentation rectangle) the entire frame buffer

is still displayed.

Viewports can be very useful. For example, you may use them when programming a split screen two

player game. You could set the view port so that it takes up the top half of the frame buffer and then

render the scene in that viewport from player one’s position. Then you could set the view port such

that it takes up the bottom half of the frame buffer and render the scene again, this time from the

second player’s position.

Viewport Aspect Ratios

We must ensure that if we use a viewport that does not span the entire frame buffer, that we use the

ViewportWidth/ViewportHeight

rather

than

aspect

ratio

calculated

using

FrameBufferWidth/FrameBufferHeight when we build the projection matrix. The previous image

showed us that the frame buffer had an aspect ratio of 1.3333333, but when we set the viewport to

320x160 the aspect ratio of the viewport was 2.0. It is important that we use the aspect ratio of the

viewport since this is where the scene will be rendered. Using the 320x160 viewport shown above, the

image would look squashed if we did not adjust the aspect ratio to reflect the new settings. Just

because we may have an elongated viewport, does not mean we wish to see the geometry in our scene

elongated. Fig 4.13 shows how the same image of the terrain would look using a wide but shallow

viewport without recalculating the aspect ratio of the viewport.

www.gameinstitute.com Graphics Programming with DX9

Page 24 of 64

TeamLRN

Figure 4.13

Note: As with all device states, when the device is lost, the viewport information is also reset.

Therefore you must remember to reset your viewport settings when resetting a device.

Camera Manipulation I

In Chapter 1 we discovered that we can multiply one matrix with another matrix to generate a resulting

matrix that will transform vectors in the same way that the two source matrices would have done

individually. Therefore, it is safe to assume that if we were to build a rotation matrix, let us say a

matrix that rotates vectors 45 degrees around the X axis, and then multiply our view matrix by that

rotation matrix, we would have created a resulting matrix that not only transforms the vertices from

world space into view space, but one that also rotates them around the world X axis. The following

code snippet retrieves the currently set view matrix from the device and rotates it 45 degrees about the

X axis (pitching it down).

D3DXMATRIX matView, matRotx;

// Get View matrix from device (will not work on a pure device)

pDevice->GetTransform(D3DTS_VIEW, &matView);

// Built Rotation matrix about X axis

D3DXMatrixRotationX(&matRotx, D3DXToRadian(-45));

// Multiply the view matrix with rotation matrix

D3DXMatrixMultiply(&matView, &matView, &matRotx);

//Set the new modified view matrix

pDevice->SetTransform(D3DTS_VIEW, &matView);

www.gameinstitute.com Graphics Programming with DX9

Page 25 of 64

TeamLRN

Because all of the same the transformations can be applied to the view matrix as to any world matrix,

we can think of the view matrix as a physical camera object even if this is not technically correct. Let

us assume that the view matrix was set to an identity matrix before the above code was executed. We

already know that if the view matrix is an identity matrix then the Look, Up, and Right vectors in the

view matrix exactly match the axes of the world coordinate system.

Because of the order of the above matrix multiplication, we are in fact performing what is known as a

camera local rotation. Instead of rotating the camera about the world X axis, we are in fact rotating the

camera about its own Right vector. The red arrow in Fig 4.14 shows the direction of rotation the code

would generate.

Figure 4.14

Because the code performed a camera relative rotation, we see now that we can perform accumulative

rotations. Regardless of the orientation of the camera in world space, the above code will always pitch

the camera down (or up if we negate the rotation angle) relative to itself and not the world. For

example, if you stand on your head and look up, you are looking at the floor. But it is still ‘up’ with

respect to your current situation. If somebody was observing you however, they might describe you as

looking down at the floor, given their perspective.

If we were to change the matrix multiplication order from ViewMatrix*RotationMatrix to

RotationMatrix*ViewMatrix this would rotate the camera about the world X axis. This would not

perform a localized rotation but would instead perform a world rotation. So take care to use the matrix

multiplication order that produces the results you desire. When rotating a non-inverted matrix (an

object world matrix for example) the opposite is true: WorldMatrix*RotationMatrix would perform a

non-localized rotation and RotationMatrix*WorldMatrix would apply localized rotation.

D3DX includes helper functions that allow us to build rotation matrices for the Y and Z axes also.

D3DXMATRIX matView, matRoty;

// Get View matrix from device (will not work on a pure device)

pDevice->GetTransform(D3DTS_VIEW , &matView);

// Built Rotation matrix about Y axis

www.gameinstitute.com Graphics Programming with DX9

Page 26 of 64

TeamLRN

D3DXMatrixRotationY(&matRoty, D3DXToRadian(45));

// Multiply the view matrix with rotation matrix

D3DXMatrixMultiply(&matView, &matView, &matRoty);

//Set the new modified view matrix

pDevice->SetTransform(D3DTS_VIEW, &matView);

Notice the matrix multiplication order we are using to rotate the camera about its own Up vector (view

space Y axis). This allows us to yaw the camera left or right relative to itself. Changing the

multiplication order would change this so that the camera was always rotated about the world Up

vector rather than the camera Up vector

Figure 4.15

This system provides us with a convenient way to handle rotating left and right in a game. Notice that

the red arrow in the Fig 4.15 shows the direction of rotation about the Up vector that the camera will

have applied to it. As we now know, a positive angle would create a matrix that would rotate vectors

right, but because we are not inverting the rotation matrix before multiplying it with the view matrix

(which is already inversed) the rotation direction is switched. Therefore, a positive rotation angle

would rotate the camera left.

Finally the above code could also be changed to rotate the camera about the Z axis to create a Roll

effect. Rolling is the effect you get in a flight simulation where pushing left and right on the joystick

banks the plane.

D3DXMATRIX matView, matRotz;

// Get View matrix from device (will not work on a pure device)

pDevice->GetTransform(D3DTS_VIEW, &matView);

// Built Rotation matrix about z axis

D3DXMatrixRotationZ(&matRotz, D3DXToRadian(-45));

// Multiply the view matrix with rotation matrix

D3DXMatrixMultiply (&matView, &matView, &matRotz);

//Set the new modified view matrix

pDevice->SetTransform(D3DTS_VIEW, &matView);

www.gameinstitute.com Graphics Programming with DX9

Page 27 of 64

TeamLRN

Figure 4.16

The red arrow in Fig 4.16 shows the direction of rotation that this code would apply to the cameras Up,

Right and Look vectors stored in the view matrix. Again, we would typically associate a negative

rotation angle as applying a clockwise rotation (roll right) when rotating a world matrix, but since we

are applying the rotation matrix (without inverting it) to the view matrix, the rotational direction is

flipped.

So we now have the ability to easily rotate a virtual camera about all three of its axes. As we know,

matrix multiplication is not commutative and the order in which the matrices are passed to the

multiplication function is critically important.

Camera Manipulation II

In this section we are going to look at an easier way to ensure proper local camera rotations. We are

going to abandon the D3DXMatrixRotate functions as a means of applying rotations to our view

matrix. Instead, we will manually rotate the Look, Up, and Right vectors in the view matrix ourselves

so that the rotations are always relative to any desired arbitrary axis. We can maintain and rotate these

vectors separately and simply rebuild the view matrix each time they change. Not only will this allow

us to perform the relative rotations that matrix multiplication provided, but it will allow us to rotate our

vectors around any axis we choose. This might not sound so easy until you realize that D3DX has a

function for building a matrix that rotates vectors about any arbitrary axis. We simply send the

function a unit vector and an angle:

D3DXMatrixRotationAxis(D3DXMATRIX *pOut, CONST D3DXVECTOR3 *pV,

FLOAT Angle);

D3DXMATRIX *pOut

This is the address of a D3DXMATRIX structure that will contain the newly generated matrix.

D3DXVECTOR3 *pV

This is in an arbitrary unit length vector that is treated as the axis of rotation. For example, if you

passed in a vector of (1,0,0) then this would produce the same rotation matrix as

D3DXMatrixRotationX. Because we can pass in vectors that are not limited to the world space axes, it

www.gameinstitute.com Graphics Programming with DX9

Page 28 of 64

TeamLRN

means that we can generate a rotation matrix that will rotate the camera about any arbitrary world

space axis, as well as the camera Look, Up and Right vectors when camera-relative rotations need to

be applied.

FLOAT Angle

The angle in radians to rotate about the passed axis.

Now let us imagine that we are trying to create a spacecraft camera system. Assume that we want the

left and right actions on the joystick to produce local yaw, the forward/back actions on the joystick to

produce local pitch and a left/right action on the joystick with the fire button down to produce local

roll. Fig 4.17 shows the where the virtual camera might be in the world:

Figure 4.17

Now let us see what the code might look like that reacts to the user pulling the joystick backwards.

Your input routine may call a function like the following to rotate the camera about its local X axis by

the specified angle. Note that we are extracting the vectors from the view matrix but you would

probably store the four view matrix vectors (look, up, right, and position) as variables for easier access

and to run this with a pure device.

void Pitch(IDirect3DDevice9* pDevice, float Angle)

{

D3DXMATRIX matView , matRotx;

D3DXVECTOR3 RightVector, UpVector, LookVector;

pDevice->GetTransform (D3DTS_VIEW , &matView);

RightVector.x = matView._11;

RightVector.y = matView._21;

RightVector.z = matView._31;

UpVector.x = matView._12;

UpVector.y = matView._22;

www.gameinstitute.com Graphics Programming with DX9

Page 29 of 64

TeamLRN

UpVector.z = matView._32;

LookVector.x = matView._13;

LookVector.y = matView._23;

LookVector.z = matView._33;

D3DXMatrixRotationAxis (&matRotx , &RightVector , Angle );

D3DXVec3TransformNormal (&UpVector , &UpVector , &matRotx);

D3DXVec3TransformNormal(&LookVector , &LookVector , &matRotx);

matView._12=UpVector.y;

matView._22=UpVector.y;

matView._32=UpVector.y;

matView._13=LookVector.z;

matView._23=LookVector.z;

matView._33=LookVector.z;

pDevice->SetTransform(D3DTS_VIEW , &MatView);

}

This example assumes we are not using a PURE device since it uses the GetTransform function to

retrieve the current view matrix from the device. Our final code will manage its own copy of the view

matrix making this call unnecessary but we have used that method here to better show the process. The

code does the following:

•

•

•

•

•

It retrieves the current view matrix

It manually extracts the cameras local axes from the view matrix and stores them in

RightVector, UpVector and LookVector for the local X,Y and Z axes respectively.

Because we are pitching up, we wish to rotate the camera about the RightVector (local X axis).

We build a rotation matrix that will rotate vectors about that axis (whatever orientation it may

be). Because the rotation is about the Right vector, the vector itself will be unchanged. All we

have to do is rotate the Look and Up vectors about the Right vector.

Once we have multiplied the Look and Up vectors with the rotation matrix, we place them back

into the view matrix so that the view matrix now contains the new orientation.

Notice that we do not have to place the Right Vector into the view matrix because it has not

been changed by this function.

Fig 4.18 shows what the view matrix and its vectors would look like if the above function was called to

pitch the camera up 45 degrees (a negative angle would rotate downwards). Notice how the right

vector is unchanged, but the Look vector and the Up vector have been rotated such that they are no

longer aligned with the world Y and Z axes:

www.gameinstitute.com Graphics Programming with DX9

Page 30 of 64

TeamLRN

Figure 4.18

So in order to rotate the camera about its local X axis, all we have to is rotate the Up and Look vectors

about the Right vector. Regardless of the orientation of the Right vector in the world, this will always

pitch the camera up and down relative to itself. Hopefully, the above code snippet has given you

everything you need to write a function that Yaws. Looking at the diagram, you should be able to see

that in order to perform local Yaw we have to rotate the Right and Look vectors about the Up vector.

Fig 4.19 shows what the camera should look like if we were to apply a 45 degree Yaw.

Figure 4.19

The next piece of code is a function that allows the camera to rotate left and right about its own Y axis.

This function is very similar to the Pitch function with the exception that we now wish to rotate the

Right and Look vectors about the Up vector. This means the Up vector will be unchanged.

void Yaw (IDirect3DDevice9* pDevice , float Angle)

www.gameinstitute.com Graphics Programming with DX9

Page 31 of 64

TeamLRN

{

D3DXMATRIX matView , matRoty;

D3DXVECTOR3 RightVector, UpVector, LookVector;

// Retrieve device view matrix

pDevice->GetTransform (D3DTS_VIEW , &matView);

// extract right, up and look vectors

RightVector.x = matView._11;

RightVector.y = matView._21;

RightVector.z = matView._31;

UpVector.x = matView._12;

UpVector.y = matView._22;

UpVector.z = matView._32;

LookVector.x = matView._13;

LookVector.y = matView._23;

LookVector.z = matView._33;

// build matrix to rotate vectors about the Up vector

D3DXMatrixRotationAxis ( &matRoty , &UpVector , Angle );

// rotate right and look vectors about the up vector

D3DXVec3TransformNormal (&RightVector , &RightVector , &matRoty);

D3DXVec3TransformNormal( &LookVector , &LookVector , &matRoty);

// place modified vectors back

matView._11=RightVector.y;

matView._21=RightVector.y;

matView._31=RightVector.y;

into the view matrix

matView._13=LookVector.z;

matView._23=LookVector.z;

matView._33=LookVector.z;

// send modified view matrix to the device

pDevice->SetTransform(D3DTS_VIEW , &MatView);

}

You should now have little trouble writing your own Roll function that rotates the camera about its

local Z axis. It would be a good idea if you opened up Notepad right now and had a go at this to make

sure that you understand what is happening. Remember to refer back to the table for the ViewMatrix to

remind yourself which vectors are stored in which columns. Once you have tried implementing this

function yourself, check it against the code listed below:

void Roll (IDirect3DDevice9* pDevice , float Angle)

{

D3DXMATRIX matView , matRotz;

D3DXVECTOR3 RightVector, UpVector, LookVector;

// Get Current View Matrix

pDevice->GetTransform (D3DTS_VIEW , &matView);

// Extract the right, up and look vectors

RightVector.x = matView._11;

RightVector.y = matView._21;

RightVector.z = matView._31;

UpVector.x = matView._12;

UpVector.y = matView._22;

UpVector.z = matView._32;

www.gameinstitute.com Graphics Programming with DX9

Page 32 of 64

TeamLRN

LookVector.x = matView._13;

LookVector.y = matView._23;

LookVector.z = matView._33;

// Build matrix to rotate vector about the LookVector

D3DXMatrixRotationAxis ( &matRotz , &LookVector , Angle );

// Rotate Up and Right vectors about the Look vector

D3DXVec3TransformNormal (&UpVector , &UpVector , &matRotz);

D3DXVec3TransformNormal( &RightVector , &RightVector , &matRotz);

// Place modified vectors back into view matrix

matView._11=RightVector.x;

matView._12=UpVector.y;

matView._21=RightVector.y;

matView._22=UpVector.y;

matView._31=RightVector.z;

matView._32=UpVector.y;

// Send the modified view matrix back to the device

pDevice->SetTransform(D3DTS_VIEW , &matView);

}

Another thing to bear in mind is that we can store the world space position of the camera and allow our

application to work with that position vector just like any other object position in the world. When the

position or orientation of the camera changes, we can place the Look, Up, and Right vectors into a

view matrix and calculate the inverse translation vector using the camera world space position. It is

much more intuitive for our application to move the camera using a world space position and calculate

the inverse translation vector when inserting it into the fourth row of the view matrix rather than have

to store the position as an inverse translation vector.

Vector Regeneration

The finite resolution of floating point numbers on the PC leads to some trouble as we continually rotate

our vectors. The vector/matrix multiplications we are performing involve many floating point

multiplications and over time, errors can start to accumulate. The problem is that a float can only store

a finite number of digits. Let us imagine that we want to store the value of PI (defined as

3.14159265358979323846…) within a single precision float. This value will be truncated before it is

stored, so that perhaps our float variable holds 3.141593.

If we multiply this float by 36.0 we should see a return value of 113.097384. However, because of the

floating-point limitation, the result is rounded to 113.0974 before storage. If we divide by the same

value again (36.0), we find that we end up with a value of 3.141594444444444, which is again

rounded to 3.141594.

So simply multiplying and then dividing by the same value produces a float which is 0.000001 adrift

from the original value. This may not seem like much, but over time this type of error accumulates.

When these errors creep into our vectors, we can end up with a situation where the camera coordinate

system axes are no longer perpendicular to each other:

www.gameinstitute.com Graphics Programming with DX9

Page 33 of 64

Tải bản đầy đủ (.pdf) (1,060 trang)