2. Adding a Third Dimension—The Cube
The cube is almost as famous as the triangle when
it comes to teaching 3D graphics theories. Because of the cube’s simple
yet fully 3D shape, it is a great point to start from. The cube is
simply an extension of the triangle you’ve already created. Even though
the cube is made up of squares or faces, the faces themselves are made
up of triangles.
To create a full cube, four sides, a top, and
bottom faces need to be created. These faces are created in such a
manner to cause them to touch and form a solid cube. The triangles that
make up the cube are defined in the same way the single triangle was
defined in the last section. The following vertices array defines all the triangles needed to make up the cube.
VertexPosColorStruct vertices[] =
{
// 1st face - first triangle
{ D3DXVECTOR3(-5.0f, 5.0f, 5.0f), D3DXVECTOR4(1.0f,0.0f,0.0f,0.0f)},
{ D3DXVECTOR3(-5.0f, -5.0f, 5.0f), D3DXVECTOR4(1.0f,0.0f,0.0f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, 5.0f), D3DXVECTOR4(1.0f,0.0f,0.0f,0.0f)},
// 1st face - second triangle
{ D3DXVECTOR3(5.0f, 5.0f, 5.0f), D3DXVECTOR4(1.0f,0.0f,0.0f,0.0f)},
{ D3DXVECTOR3(-5.0f, -5.0f, 5.0f), D3DXVECTOR4(1.0f,0.0f,0.0f,0.0f)},
{ D3DXVECTOR3(5.0f, -5.0f, 5.0f), D3DXVECTOR4(1.0f,0.0f,0.0f,0.0f)},
// 2nd face - first triangle
{ D3DXVECTOR3(-5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.0f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.0f,0.0f)},
{ D3DXVECTOR3(-5.0f, -5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.0f,0.0f)},
// 2nd face - second triangle
{ D3DXVECTOR3(-5.0f, -5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.0f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.0f,0.0f)},
{ D3DXVECTOR3(5.0f, -5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.0f,0.0f)},
// 3rd face - first triangle
{ D3DXVECTOR3(-5.0f, 5.0f, 5.0f), D3DXVECTOR4(0.0f,0.0f,1.0f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, 5.0f), D3DXVECTOR4(0.0f,0.0f,1.0f,0.0f)},
{ D3DXVECTOR3(-5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.0f,0.0f,1.0f,0.0f)},
// 3rd face - second triangle
{ D3DXVECTOR3(-5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.0f,0.0f,1.0f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, 5.0f), D3DXVECTOR4(0.0f,0.0f,1.0f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.0f,0.0f,1.0f,0.0f)},
// 4th face - first triangle
{ D3DXVECTOR3(-5.0f, -5.0f, 5.0f), D3DXVECTOR4(1.0f,0.5f,0.0f,0.0f)},
{ D3DXVECTOR3(-5.0f, -5.0f, -5.0f), D3DXVECTOR4(1.0f,0.5f,0.0f,0.0f)},
{ D3DXVECTOR3(5.0f, -5.0f, 5.0f), D3DXVECTOR4(1.0f,0.5f,0.0f,0.0f)},
// 4th face - second triangle
{ D3DXVECTOR3(5.0f, -5.0f, 5.0f), D3DXVECTOR4(1.0f,0.5f,0.0f,0.0f)},
{ D3DXVECTOR3(-5.0f, -5.0f, -5.0f), D3DXVECTOR4(1.0f,0.5f,0.0f,0.0f)},
{ D3DXVECTOR3(5.0f, -5.0f, -5.0f), D3DXVECTOR4(1.0f,0.5f,0.0f,0.0f)},
// 5th face - first triangle
{ D3DXVECTOR3(5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.5f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, 5.0f), D3DXVECTOR4(0.0f,1.0f,0.5f,0.0f)},
{ D3DXVECTOR3(5.0f, -5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.5f,0.0f)},
// 5th face - second triangle
{ D3DXVECTOR3(5.0f, -5.0f, -5.0f), D3DXVECTOR4(0.0f,1.0f,0.5f,0.0f)},
{ D3DXVECTOR3(5.0f, 5.0f, 5.0f), D3DXVECTOR4(0.0f,1.0f,0.5f,0.0f)},
{ D3DXVECTOR3(5.0f, -5.0f, 5.0f), D3DXVECTOR4(0.0f,1.0f,0.5f,0.0f)},
// 6th face - first triangle
{D3DXVECTOR3(-5.0f, 5.0f, -5.0f), D3DXVECTOR4(0.5f,0.0f,1.0f,0.0f)},
{D3DXVECTOR3(-5.0f, -5.0f, -5.0f), D3DXVECTOR4(0.5f,0.0f,1.0f,0.0f)},
{D3DXVECTOR3(-5.0f, 5.0f, 5.0f), D3DXVECTOR4(0.5f,0.0f,1.0f,0.0f)},
// 6th face - second triangle
{D3DXVECTOR3(-5.0f, 5.0f, 5.0f), D3DXVECTOR4(0.5f,0.0f,1.0f,0.0f)},
{D3DXVECTOR3(-5.0f, -5.0f, -5.0f), D3DXVECTOR4(0.5f,0.0f,1.0f,0.0f)},
{D3DXVECTOR3(-5.0f, -5.0f, 5.0f), D3DXVECTOR4(0.5f,0.0f,1.0f,0.0f)},
};
You’re
probably wondering why I declared every triangle in the cube instead of
just the six unique vertices that make up the cube. Remember earlier
where I said that when you share vertices you have to share colors and
texture coordinates? The cube being created above has a different vertex
color for each face; sharing the vertices would have caused the colors
to blend between vertices.
The triangles were defined as a triangle list to allow the whole cube to be drawn with a single Draw call. Before drawing the newly defined cube, make sure to set the topology to D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST.
// Set primitive topology
pD3DDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// draw the cube using all 36 vertices and 12 triangles
pD3DDevice->Draw(36,0);
3.Object Translation and Rotation
If you were to run an example with the cube
vertices defined, you’d be disappointed to see just a single colored
square sitting in the center of your screen. Because the cube is a 3D
object, it exists within a three-dimensional space but is currently
oriented in a way where only the front part of the cube is visible. To
give you an idea as to what the rest of the cube looks like, you’re
going to learn how to rotate it. Rotating the cube or any object has a
few steps:
Translating
and rotating the object within world space using the world matrix. This
makes sure the object is positioned where you want it. Setting up and positioning of the virtual camera. The virtual camera is set up and positioned using the view matrix. Creation of the projection matrix. This translates the scene into screen space. The
final piece requires these three matrices to be combined and sent to
the shader. The shader then makes sure that all the vertices sent
through it will be oriented properly.
The World Space Transformation
Take a look at the following
code snippet. A rotation angle is created and applied to the WorldMatrix causing the cube to spin around the Y axis. You’ll see the rotationAngle variable update; this constantly gives the rotation a new angle to work with.
static float rotationAngle = 0.0f;
// create the rotation matrix using the rotation angle
D3DXMatrixRotationY(&WorldMatrix, rotationAngle);
// increment the rotationAngle for next time
rotationAngle += (float)D3DX_PI * 0.2125f;
By updating the world matrix with the cube’s rotation, the cube should be positioned correctly.
Setting Up the Virtual Camera
The view matrix positions the virtual camera
allowing you to view the scene from any point you want. For this
example, the view matrix is initialized and places a virtual camera
approximately 20 units back on the Z axis and 10 units up along the Y.
This moves the camera far enough back and up to be able to view the
rotating cube correctly. By placing the camera slightly above the cube,
you’ll be able to set the top of the cube as well. The virtual camera
will remain stationary, so the view matrix is set only once.
D3DXMatrixLookAtLH(&ViewMatrix,
new D3DXVECTOR3(0.0f, 10.0f, -20.0f),
new D3DXVECTOR3(0.0f, 0.0f, 0.0f),
new D3DXVECTOR3(0.0f, 1.0f, 0.0f));
The view matrix is initialized using one of Direct3D’s functions called D3DXMatrix-LookAtLH.
This function allows a view matrix to be created by using a vector
representing the virtual camera’s position, where this camera is
looking, and which direction is up.
The Projection Matrix
The projection matrix makes sure that your 3D
scene is mapped correctly into the 2D viewport you have available. The
matrix is initialized by the D3DXMatrixPerspectiveFovLH
function using the width and height of the target window. The projection
matrix controls the field of view (FOV) as well. The FOV is the viewing
angle for the virtual lens attached to your in-game camera.
For the most part, unless the viewport is
constantly resizing or you need to readjust the FOV, the projection
matrix can be set once at the start of your application.
D3DXMatrixPerspectiveFovLH(&ProjectionMatrix,
(float)D3DX_PI * 0.5f,
(float)width/(float)height,
0.1f,
100.0f);
Combining the Matrices for the Shader
At
this point you should have three matrices defined; two of them you
won’t have to mess with again and the world matrix, which will be
constantly updating. Each frame, the shader will need to know what the
current transformation matrices are so the objects in the scene can be
updated and placed correctly.
Before the matrices are sent into the shader,
you’ll need to combine them into a single matrix. This reduces the
amount of code needed in the shader to apply the transformation.
// Combine and send the final matrix to the shader
D3DXMATRIX finalMatrix = (WorldMatrix * ViewMatrix * ProjectionMatrix);
pProjectionMatrixVariable->SetMatrix((float*)&finalMatrix);
The shader is the last part I haven’t talked
about yet. Previously the position of each vertex was just passed along;
now each vertex is being multiplied in the shader by the Projection variable. The Projection variable contains the calculation from the variable finalMatrix. This correctly positions the cube based on the rotation values set earlier.
////////////////////////////////////////////////
// Vertex Shader - Main Function
///////////////////////////////////////////////
PS_INPUT VS(float4 Pos : POSITION, float4 Color : COLOR)
{
PS_INPUT psInput;
// Pass through the color
psInput.Pos = mul( Pos, Projection );
psInput.Color = Color;
return psInput;
}
The cube should now be rotating happily on the screen like in Figure 6.4. As it rotates, you’ll be able to see the colors are different for each face on the cube. You can update the rotationAngle variable to either speed up or slow down the rotation.
|