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