Chapter 6. 3D Primer

Video games are very mathematics intensive. Everything from graphics, physics, collisions, audio, and so much more requires various levels of mathematics. Whether the application being written is by a beginner or by an experienced game developer, math is a subject that cannot be avoided.

Everything you’ve done up to this point has happened in a flat, two-dimensional environment. Your sprites have moved either horizontally or vertically, but there’s another dimension yet to explore—the third dimension, depth. Sure, some great games can be made that take advantage of only the technology you’ve explored so far, but the latest cutting-edge games take place in a 3D world.

This chapter introduces the concepts and math you’ll need to build a 3D world. DirectX provides a math library that is highly optimized, portable (between Windows and the Xbox 360), and very easy to use. Even if you do not have much experience with the math topics commonly seen in video games, it can at least help to understand the math structures and functions available, as well as the parameters they accept.

In this chapter:

  • What coordinate systems are and how they’re used

  • How to define points within 3D space

  • What vectors are

  • The wonderful world of matrices

  • How to position and move objects

XNA Math

XNA is not an acronym. XNA Math is the math library that is part of the DirectX 11 SDK. In previous versions of DirectX there was a library called D3DXMath. This library is still available in the SDK if you link to the DirectX 10 and earlier libraries, but it is recommended to learn and use XNA Math.

XNA Math is not to be confused with XNA Game Studio. XNA Game Studio is a C# framework that allows developers to create games for Windows, Zune media devices (last supported version was XNA Game Studio 3.1), Windows Phone 7 mobile devices, and the Xbox 360. Although the math library in the DirectX 11 SDK is called XNA Math, it is not in C# or part of the XNA Game Studio.

To use XNA Math we must include the xnamath.h header file. XNA Math also works on the Xbox 360 with the Xbox SDK.

Utilities

XNA Math has a set of utility functions for raising assertions, calculating the Fresnel term, and checking for CPU support. The first function, XMAssert, has the following prototype:

VOID XMAssert(CONST CHAR* pExpression, CONST CHAR* pFileName, UNIT LineNumber);

The first parameter to XMAssert is the logical expression being tested. If this logical expression results in a value of false, the code execution is halted. The second and third parameters to XMAssert are the filename of the code where the assertion was raised from and the line where the call to XMAssert can be found within the source file.

The next function is the one to calculate the Fresnel term, and it has the following function prototype:

XMVECTOR XMFresnelTerm( XMVECTOR CosIncidentAngle, XMVECTOR RefractionIndex );

The Frensel term can be used for various advanced graphical effects. As a beginner, this is not likely to be a function that you will encounter in the near future. The first parameter to the XMFresnelTerm is the cosine values of the incident angles, while the second parameter is the reflection indices for the materials for the incident angles.

The last function in the utilities section is called XMVerifyCPUSupport, and it has the following prototype:

BOOL XMVerifyCPUSupport( );

On Windows, the XMVerifyCPUSupport function is implemented using the Win32 function IsProcessorFeaturePresent to check for the necessary features required by XNA Math.

Points

Any position within a coordinate system can be represented using a point. A point is simply an infinitely small location in space. When locating a point in space, it is described using a single value for each axis. This value is the offset from the origin along each respective axis. For example, a point located in 2D space would need two values, an X and a Y value, to describe its location, such as <1, 3>.

For each axis added, it takes one additional value to represent the point. In 3D space, three values, X, Y, and Z, are needed to describe a point, such as <1, 2, 4>.

Points can be used in many ways in the creation of a game, from player position to the location of a planet. Even though each point is tiny, that one point can be used to represent the location of any object in your game. We have been using the position of vertex points to represent the point of a triangle throughout this book.

Vectors

Vectors have multiple uses in 3D graphics, from describing distance and direction to describing speed. Unlike a point, which has only a position, a vector has both a direction and a length (magnitude), allowing it to be utilized to determine which direction a polygon is facing, which direction an object or particle is heading, or just describe its position. Typically, vectors are designated as arrows, with a head and tail showing the direction and magnitude. Vectors typically fall into two categories: free vectors and fixed vectors.

A free vector is a vector that can be placed in an arbitrary location within a coordinate system, and its meaning doesn’t change.

A fixed vector remains fixed to the origin of the coordinate system. This places the tail of these vectors at the origin, while the head is placed at a location in space. Because fixed vectors are centered on the origin, it allows them to be used to designate a position. This position is comprised of three scalar values called components. The number of components within the vector corresponds to the number of axes. For example, if the coordinate system is describing 3D space, then three components are needed to describe a position within it. For example, the vector <3, 2, 7> corresponds to a position 3 units away from the origin on the X axis, 2 units away on the Y axis, and 7 units away on the Z axis.

The members of a vector are basic data types such as floats, integers, and so forth, and there is one for each axis of the structure. Examples of 2D, 3D, and 4D vectors can be seen as follows:

struct Vector2
{
    float x;
    float y;
};


struct Vector3
{
    float x;
    float y;
    float z;
};


struct Vector4
{
    float x;
    float y;
    float z;
    float w;
};

While you could define these types yourself, the Direct3D structures contain more than just the definitions of the embedded components. In XNA Math we can represent vectors using XMFLOAT2 for 2D vectors, XMFLOAT3 for 3D vectors, and XMFLOAT4 for 4D vectors. Additional vector types, such as XMHALF4, for example, are discussed later in this chapter in the section “Structures and Types.” It is very useful to use the XNA Math library structures because they are portable (between Windows and the Xbox 360), optimized, and easy to use.

In general when using vectors you’re bound to come across a few different types; here’s a small list:

  • Position Vector—A type of vector used to describe a position within a coordinate system, with the tail being at the origin and the head at a point.

  • Normal Vector—A vector perpendicular to a plane. This is useful for determining whether a polygon is front or back facing.

  • Unit Vectors—Vectors that have a length that equals one. Not all vectors require a large length. When creating a directional light, only the direction of the vector is important.

  • Zero Vectors—Vectors with a length of 0.

Another structure available is the XMVECTOR, which is an optimized 4D vector. The XMVECTOR structure is not only mapped to fast hardware registers but is also portable and memory aligned.

The implementation of XMVECTOR is platform dependent, and because of this there are very efficient functions for accessing data. These functions include accessor, store, and loading functions. These functions must be used to safely access the structure’s data in a portable manner.

The first function is a loading function called XMLoadFloat2. This function has a version for every type of vector, such as XMLoadInt2, XMLoadFl oat3, XMLoadByte4, and so forth. The purpose of these functions is to load the corresponding vector type into an XMVECTOR. This is useful when there were times in code where you were using, let’s say, XMFLOAT2 for example, and wanted to move that data into an XMVECTOR so that you can perform fast hardware operations on it.

Each of the loading functions takes as a parameter the address of the vector we want to convert to an XMVECTOR. As an example, the function prototype for taking an XMFLOAT2 and returning a XMVECTOR can be seen in XMLoadFloat2 below:

XMVECTOR XMLoadFloat2( CONST XMFLOAT2* pSource );

The simple answer is that this function copies an XMFLOAT2 to a XMVECTOR, although internally it uses fast instructions to perform the operation. If we wanted to copy a 2D vector to a 4D vector using our custom structures, it would look like the following:

Vector4 dest;
Vector2 src;

dest.x = src.x;
dest.y = src.y;
dest.z = <undefined>;
dest.w = <undefined>;

There is a large number of loading functions, and each can be found in the SDK documentation. In addition to loading functions we also have storing functions. All functions are identical to one other, with the difference of the first parameter’s type. Using XMStoreFloat2, the function takes as parameters an address to an XMFLOAT2 that will receive the data as its first parameter and the XMVECTOR that has the data we want to copy. Its function prototype can be seen as follows:

VOID XMStoreFloat2( XMFLOAT2* pDestination, XMVECTORV );

The accessor functions can be used to retrieve the value of any member of the XMVECTOR structure. These functions include XMVectorGetX, XMVectorGetY, XMVectorGetZ, and XMVectorGetW. Each of these functions takes as a parameter an XMVECTOR object and returns the value of the corresponding member. The prototypes of each of these functions can be seen as follows:

FLOAT XMVectorGetX( XMVECTOR V );

FLOAT XMVectorGetY( XMVECTOR V );

FLOAT XMVectorGetZ( XMVECTOR V );

FLOAT XMVectorGetW( XMVECTOR V );

Also available are functions that return the value via a pointer as the first parameter, which can be seen as follows:

VOID XMVectorGetXPtr( FLOAT* x, XMVECTOR V );

VOID XMVectorGetYPtr( FLOAT* y, XMVECTOR V );

VOID XMVectorGetZPtr( FLOAT* z, XMVECTOR V );

VOID XMVectorGetWPtr( FLOAT* w, XMVECTOR V );

And in addition to the functions used to get a member, we also have functions that can set them. As an example, the following are the prototypes for the setting of the X member of a XMVECTOR object:

XMVECTOR XMVectorSetX( XMVECTOR V, FLOAT x );
XMVECTOR XMVectorSetXPtr( XMVECTOR V, CONST FLOAT* x );

Vector Arithmetic

The most basic operations we can do with vectors are arithmetic operations. Adding, subtracting, multiplying, and dividing vectors with each other is as simple as performing the operation on each member in one vector to the corresponding member in a second vector. An example of this can be seen with performing various operations to a 2D vector:

Vector2D a, b, result;

// Adding
result.x = a.x + b.x;
result.y = a.y + b.y;

// Subtracting
result.x = a.x - b.x;
result.y = a.y - b.y;

// Multiplying
result.x = a.x * b.x;
result.y = a.y * b.y;

// Dividing
result.x = a.x / b.x;
result.y = a.y / b.y;

It is also common to perform these operations between a vector and a scalar (floating-point value), as well as using overloaded operators. Although overloaded operators are convenient to look at in code, they are often less optimal since they often require multiple functions to be executed (one for the equals, one for the operator, constructors for the vector parameters to the overloaded function, etc.) and multiple load/store operations. An example of adding a scalar to a vector or using an overloaded operation can be seen as follows:

Vector2D a, result;
float value = 100.0f;

// Adding a float
result.x = a.x + value;
result.y = a.y + value;

// Adding as an overloaded operator
result = a + value;

In XNA Math we have XMVectorAdd, XMVectorSubtract, XMVectorDivide, and XMVectorMultiply as a means of performing each of these operations between XMVECTOR objects. Their prototypes are as follows, and they use optimized instruction sets:

XMVECTOR XMVectorAdd( XMVECTOR V1, XMVECTOR V2 );

XMVECTOR XMVectorSubtract( XMVECTOR V1, XMVECTOR V2 );

XMVECTOR XMVectorDivide( XMVECTOR V1, XMVECTOR V2 );

XMVECTOR XMVectorMultiply( XMVECTOR V1, XMVECTOR V2 );

Distance Between Vectors

Occasionally you will need to determine the distance between two points. These points can be either the origin and a fixed location or two completely arbitrary points.

For example, imagine you’re creating a real-time strategy game. Each of the monsters of the opposing army has the opportunity to move toward a common goal. During the turn of the AI, it can choose to move one of these monsters toward the goal, but which one? This is where the ability to figure out distance comes in handy. By calculating the relative distance between each monster and the common goal, the AI can choose which one of the creatures is more advantageous to move.

Whether you are determining the distance within a 2D or 3D space, the calculation is essentially the same. An example can be seen in the following pseudo code:

Vector2D x1, x2;

float xDistance = x2.x - x1.x;
float yDistance = y2.x - y1.x;

float distance = square_root( xDistance * xDistance + yDistance * yDistance );

In this example x2 is the X axis value of the second vector and x1 is the X axis of the first vector. The result of the distance calculation will be a single value that represents the distance between the two points. When determining the distance between two points in 3D space, make sure to take the Z value into account as well.

To put it another way, the distance between one vector and another is the square root of the dot product of the vector between the points. We’ll cover dot products in the upcoming subsection titled “Dot Product.” The square root of the vector between the points is also known as the vector’s length. In this case the length can be used to give us the distance between vectors.

Determining the Length of a Vector

Occasionally it is useful to know the length of a vector, since the length or magnitude of the vector can be used as acceleration or velocity when applied to a game object. The length is also used as an input when normalizing the vector.

To calculate the length of a vector, each of the components must be first squared and then added together. This is known as the dot product. Finally, the square root of the dot product is taken to give the output length.

sqrt(vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ);

Direct3D provides several functions to calculate the length of a vector. The length of a 2D vector is obtained with a call to XMVector2Length, the length of a 3D vector via XMVector3Length, and a 4D vector via XMVector4Length. Additional functions also exist to estimate the length of a vector as well as to square the length. The squared-length functions end with Sq and will essentially perform length × length, while the estimation functions that end with Est are used to increase performance at the expense of accuracy. All of the length functions in XNA Math can be seen as follows:

XMVECTOR XMVector2Length( XMVECTOR V );
XMVECTOR XMVector3Length( XMVECTOR V );
XMVECTOR XMVector4Length( XMVECTOR V );

XMVECTOR XMVector2LengthEst( XMVECTOR V );
XMVECTOR XMVector3LengthEst( XMVECTOR V );
XMVECTOR XMVector4LengthEst( XMVECTOR V );

XMVECTOR XMVector2LengthSq( XMVECTOR V );
XMVECTOR XMVector3LengthSq( XMVECTOR V );
XMVECTOR XMVector4LengthSq( XMVECTOR V );

Normalize a Vector

Normalizing a vector is the process of reducing any vector into a unit vector, which as you should remember is a vector with a length equal to 1. This is best done when only the direction of a vector is needed and the length is unimportant. Vectors can be normalized simply by dividing each of the components by the vector’s length— an example of which can be seen in the following:

Vector3 vec;

float length = Length( vec );

vec.X = vec.X / length;
vec.Y = vec.Y / length;
vec.Z = vec.Z / length;

The final vector will still point in the same direction, but the length of the vector is reduced to 1. In XNA Math we have XMVector2Normalize to normalize a 2D vector, XMVector2NormalizeEst to calculate an estimated normal vector (which can lead to a increase of performance at the expense of accuracy), and versions of each for 3D and 4D vectors. The function prototypes are as follows:

XMVECTOR XMVector2Normalize( XMVECTOR V );
XMVECTOR XMVector3Normalize( XMVECTOR V );
XMVECTOR XMVector4Normalize( XMVECTOR V );

XMVECTOR XMVector2NormalizeEst( XMVECTOR V );
XMVECTOR XMVector3NormalizeEst( XMVECTOR V );
XMVECTOR XMVector4NormalizeEst( XMVECTOR V );

Cross Product

A cross product of a vector is used to calculate a vector that is perpendicular to two other vectors. One common use for this is to calculate the normal of a triangle. In this situation you can use the cross product of each edge vector to obtain the triangle’s normal. An edge vector is essentially point A of the triangle — point B for edge 1, and point B — point C for the second edge. The cross product is also known as the vector product.

An example of calculating the cross product can be seen in the following, where a new vector is created by multiplying the components of the two input vectors:

newVectorX = (vector1Y * vector2Z) –(vector1Z * vector2Y);
newVectorY = (vector1Z * vector2X) –(vector1X * vector2Z);
newVectorZ = (vector1X * vector2Y) –(vector1Y * vector2X);

In XNA Math we have XMVector2Cross , XMVector3Cross , and XMVector4Cross for calculating the cross product of 2D, 3D, and 4D vectors. Their function prototypes are as follows:

XMVECTOR XMVector2Cross( XMVECTOR V1, XMVECTOR V2 );
XMVECTOR XMVector3Cross( XMVECTOR V1, XMVECTOR V2 );
XMVECTOR XMVector4Cross( XMVECTOR V1, XMVECTOR V2 );

Dot Product

The final vector calculation we’ll go over is the dot product. The dot product, also known as the scalar product, is used to determine the angle between two vectors and is commonly used for many calculations in computer graphics such as back-face culling, lighting, and more.

Back-face culling is the process by which polygons that are not visible are removed to reduce the number of polygons being drawn. If two vectors have an angle less than 90 degrees, then the dot product is a positive value; otherwise, the dot product is negative. The sign of the dot product is what’s used to determine whether a polygon is front or back facing. If we use the view vector as one vector, the dot product between the view vector and the polygon’s normal tells us which direction it is facing, and thus which polygons we should draw or not. This is usually handled internally by the graphics hardware and is not anything we would do manually when using Direct3D.

We could also use this for lighting, which we’ll discuss in more detail in Chapter 7 in the section titled “Lighting.” One way we can use the dot product for lighting is to use the value to see how much diffuse light is reaching the surface. Light sources directly above a surface will be fully lit, surfaces at an angle will be partially lit, and surfaces where the light is behind it will receive no light. The vector used for the dot product is usually the surface normal and the light vector, where the light vector is calculated as the light’s position — the vertex position (or the pixel’s position when doing per-pixel lighting).

The dot product is calculated by squaring each axis of the vector and adding together their results to form a single scalar value. An example of this can be seen in the following:

float dotProduct = vecl.X * vec2.X + vecl.Y * vec2.Y + vecl.Z * vec2.Z;

XNA Math has XMVector2Dot for the dot product between 2D vectors, XMVector3Dot for 3D vectors, and XMVector4Dot for 4D vectors. Their prototypes are shown as follows:

XMVECTOR XMVector2Dot( XMVECTOR V1, XMVECTOR V2 );
XMVECTOR XMVector3Dot( XMVECTOR V1, XMVECTOR V2 );
XMVECTOR XMVector4Dot( XMVECTOR V1, XMVECTOR V2 );

Although the dot product returns a scalar, in XNA Math it seems to be returning a vector of XMVECTOR. This is because the result is copied into all components of XMVECTOR. Since the optimized instruction set used in XNA Math uses vectors for stream parallel processing, technically that is what we are using even if we ultimately only really use one component. SIMD instructions work on four floating-point values at a time with a single instruction, so in a case such as this we only need to access one of them to get our scalar result.

3D Space

The basis to any three-dimensional world is the space it takes place in. Look around you. The keyboard and monitor on your desk, the chair on the floor, all of these items exist in a 3D space. If you had to describe the location of one of these objects to someone over the phone, how would you do it? Would you describe your desk as located in front of you or would you say it was near a certain wall? If the person on the phone knew absolutely nothing about the room you were in, from that description, would they understand? Probably not; they’re missing a point of reference.

Coordinate Systems

A point of reference is a location that both you and the other person understand. For instance, if the point of reference was a doorway, you could then explain that the desk was located about 10 feet from the door on the left side. When you’re building a 3D world, a point of reference is crucial.

You need to be able to place objects in relation to a point of reference that both you and the computer understand. When working with 3D graphics, this point of reference is the coordinate system. A coordinate system is a series of imaginary lines that run through space and are used to describe locations within it. The center of this coordinate system is called the origin; this is the core of your point of reference. Any location within this space can be described precisely in relation to the origin.

For example, you can describe the location of an object by saying it is four units up from the origin and two units to the left. By using the origin as the point of reference, any point within the defined space can be described.

If you remember from working with sprites, any point on the screen can be explained using X and Y coordinates. The X and Y coordinates determine the sprite’s position in a coordinate system consisting of two perpendicular axes, a horizontal and a vertical. Figure 6.1 shows an example of a 2D coordinate system.

When working with three dimensions, a third axis will be needed: the Z axis. The Z axis extends away from the viewer, giving the coordinate system a way to describe depth. So now you have three dimensions, width, height, and depth, as well as three axes.

A 2D coordinate system.

Figure 6.1. A 2D coordinate system.

When dealing with 3D coordinate systems, you have to be aware that they come in two flavors: left-handed and right-handed. The handedness of the system determines the direction the axes face in relation to the viewer.

Left-Handed Coordinate Systems

A left-handed coordinate system extends the positive X axis to the left and the positive Y axis upward. The Z axis in a left-handed system is positive in the direction away from the viewer, with the negative portion extending toward them. Figure 6.2 shows how a left-handed coordinate system is set up.

A left-handed coordinate system.

Figure 6.2. A left-handed coordinate system.

A right-handed coordinate system.

Figure 6.3. A right-handed coordinate system.

Right-Handed Coordinate Systems

The right-handed coordinate system extends the X and Y axes similarly to the left-handed system, but it reverses the X and Z axis directions. The positive Z values extend toward the viewer, whereas the negative values continue away. For the X axis, the positive X axis increases to the right, whereas the positive X axis in the left-handed system is to the left. Figure 6.3 shows a right-handed system.

Transformations

3D models are for the most part created outside of your game code. For instance, if you’re creating a racing game, you’ll probably create the car models in a 3D art package. During the creation process, these models will be working off of the coordinate system provided to them in the modeler. This causes the objects to be created with a set of vertices that aren’t necessarily going to place where the model will end up in the game and how you want it oriented in your game environment. Because of this, you will need to move and rotate the model yourself. You can do this by using the geometry pipeline. The geometry pipeline is a process that allows you to transform an object from one coordinate system into another. Figure 6.4 shows the transformations a vertex goes through.

Geometry pipeline.

Figure 6.4. Geometry pipeline.

When a model starts out, it is centered on the origin. This causes the model to be centered in the environment with a default orientation. Not every model you load needs to be at the origin, so how do you get models where they need to be? The answer to that is through transformations.

Transformations refer to the actions of translating (moving), rotating, and scaling an object. By applying these actions, you can make an object appear to move around. These actions are handled as your objects progress through the stages of the geometry pipeline.

When you load a model, its vertices are in a local coordinate system called model space. Model space refers to a coordinate system where the parts of an object are relative to a local origin. For instance, upon creation, a model’s vertices are in reference to the origin point around which they were created. A cube that is two units in size centered on the origin would have its vertices one unit on either side of the origin. If you then wanted to place this cube somewhere within your game, you would need to transform its vertices from model space into the global system used by all the objects in your world. This global coordinate system is called world space, where all objects are relative to a single fixed origin. The process of transforming an object from model space into world space is called the world transformation.

World Transformations

The world transformation stage of the geometry pipeline takes an existing object with its own local coordinate system and transforms that object into the world coordinate system. The world coordinate system is the system that contains the position and orientation of all objects within the 3D world. The world system has a single origin point that all models transformed into this system become relative to.

The transformation from model space to world space usually occurs within the vertex shader when it comes to rendering geometry. You could transform the geometry yourself outside of shaders, but that will require repopulating vertex buffers with dynamic information, which would not always be the best course of action (especially for static geometry).

The next stage of the geometry pipeline is the view transformation. Because all objects at this point are relative to a single origin, you can only view them from this point. To allow you to view the scene from any arbitrary point, the objects must go through a view transformation.

View Transformations

The view transformation transforms the coordinates from world space into view space. View space refers to the coordinate system that is relative to the position of a virtual camera. In other words, the view transformation is used to simulate a camera that is applied to all objects in the game world with the exception of the graphical user interface (e.g., screen elements for the health bar, timers, ammo counter, etc.). When you choose a point of view for your virtual camera, the coordinates in world space get reoriented in respect to the camera. For instance, the camera remains at the origin, while the world itself is moved into the camera’s view.

At this point, you have the camera angle and view for your scene, and you’re ready to display it to the screen.

Projection Transformations

The next stage in the geometry pipeline is the projection transformation. The projection transformation is the stage of the pipeline where depth is applied. When you cause objects that are closer to the camera to appear larger than those farther away, you create an illusion of depth. This type of projection is known as perspective projection and is used for 3D games. Another type of projection will keep all rendered objects looking like they are the same distance apart from each other even when they are not. This is done through orthographic projection. A final type of projection is isometric projection, which is a projection used to display 3D objects in 2D technical drawings. This type of projection is simulated and not actually done in graphics APIs, where we usually just have either perspective projection or orthographic projection.

Finally, the vertices are scaled to the viewport and projected into 2D space. The resulting 2D image appears on your monitor with the illusion of being a 3D scene.

Transforming an Object

Now that you’re aware of the transformations, what are they really used for? As an example, say you were modeling a city. This city is made up of houses, office buildings, and a few cars here and there. Now, you load in a model of a new car and need to add it to your existing scene. When the model comes in, though, it’s centered on the origin and facing the wrong way. To get the car to the correct spot and orientation in the scene, the car model will have to be rotated properly and then translated to its world space location.

When putting an object through the transformation pipeline, you’re really transforming each vertex individually. In the case of the car, every vertex that makes up the car will be moved individually by a certain amount and then rotated. After each vertex is complete, the object appears in the new position. These transformations take place by multiplying each vertex in the model by a transformation matrix.

The world matrix used for the world transformation is created by combining the scale, rotation, and translation (positioning) matrices for that specific game object. Although we usually use these three matrices to create the world matrix, if the object doesn’t use one or more of these properties, you could just leave it out, since combining one matrix with an identity matrix is similar to multiplying a number by 1; it doesn’t change its value. An identity matrix is similar to a matrix with nothing applied to it.

Combining the world matrix and the view matrix will create a new matrix called the world-view matrix. Combining the world-view and projection matrices will create another matrix called the world-view-projection matrix. This matrix is often used by the vertex shader and is the one we’ve been working with throughout this book.

Transforming a vector by a transformation matrix is done by multiplying the vector by the matrix. This is an operation we’ve done in the large majority of demos in the vertex shader where we used the HLSL keyword mul to perform the multiplication. In XNA Math we can call, using 2D vectors as an example, the function XMVector2Transform to transform a vector by a matrix. There is also a version of this called XMVector2TransformCoord, which is used to transform a vector and then project it onto w = 1. The third type of transformation function, called XMVector2TransformNormal, is used to transform a vector by all parts of the matrix with the exception of its translational part. Each of these types of functions for 2D, 3D, and 4D vectors can be seen as follows:

XMVECTOR XMVector2Transform( XMVECTOR V, XMMATRIX M );
XMVECTOR XMVector3Transform( XMVECTOR V, XMMATRIX M );
XMVECTOR XMVector4Transform( XMVECTOR V, XMMATRIX M );

XMVECTOR XMVector2TransformCoord( XMVECTOR V, XMMATRIX M );
XMVECTOR XMVector3TransformCoord( XMVECTOR V, XMMATRIX M );
XMVECTOR XMVector4TransformCoord( XMVECTOR V, XMMATRIX M );

XMVECTOR XMVector2TransformNormal( XMVECTOR V, XMMATRIX M );
XMVECTOR XMVector3TransformNormal( XMVECTOR V, XMMATRIX M );
XMVECTOR XMVector4TransformNormal( XMVECTOR V, XMMATRIX M );

There is also a stream version of each of these functions. Using the 2D vector version of this function as an example, the parameters include the address to the output array the data will be saved, the size in bytes between each output vector, the input array of vectors you are supplying to be transformed, the size in bytes between each input vector, the total number of vectors being transformed, and the matrix by which these vectors will be transformed. The function prototype for the XMVector2TransformStream function can be seen as follows:

XMFLOAT4* XMVector2TransformStream(
    XMFLOAT4 *pOutputStream,
    UINT OutputStride,
    CONST XMFLOAT2 *pInputStream,
    UINT InputStride,
    UINT VectorCount,
    XMMATRIX M
);

An alternative to XMVector2Transform is XMVector2TransformNC, where NC refers to non-cached memory.

Matrices

You’ve probably come across matrices in a math class along the way and wondered what they would ever be used for. Well, here it is: game programming. Matrices are used to transform objects from one coordinate system to another. By applying a matrix to an existing point, it is transformed from its original coordinate system to the new one. For example, most objects start out in their own local coordinate system. This means that all the points that make up that object are relative to a local origin. To convert those points into a common world coordinate system, they must be transformed using a matrix.

You will probably find matrices to be the single most confusing concept to wrap your thoughts around when just starting out with 3D graphics. Matrices and the math involved can feel very daunting and overwhelming at the start. The important thing to take away from this section on matrices isn’t necessarily how they work, but what they’re for and how to use them. No one should expect you to be able to perform all the math required right off the bat; luckily, Direct3D provides optimized functionality to do a lot of this work for you. If you ever want to know all the details behind matrices, there is a ton of good math resources available on the web. Simply put, a matrix is used to represent a system of values where, in the case of graphics and game programming, we use these objects to store the necessary information to transform vectors to the system the matrix represents.

Mathematically, a matrix is a 4 × 4 grid of numbers that are arranged in columns and rows. Each row in the matrix corresponds to the three major axes. The first row is used for the X axis, the second row for the Y axis, and the third row for the Z axis. The final row is used to contain the translation offset values.

If you wanted to create your own matrix in code, you could define it as a simple 4 × 4 array of float values.

float matrix [4][4] =
{
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

XNA Math has several structures for matrices. There is XMFLOAT3X3 for 3 × 3 matrices, XMFLOAT4X3 for 4 × 3 matrices, and XMFLOAT4X4 for 4 × 4 matrices. Using XMFLOAT3X3 as an example, the structure looks as follows:

typedef struct _XMFLOAT3X3
{
    union
    {
        struct
        {
            FLOAT _11;
            FLOAT _12;
            FLOAT _13;
            FLOAT _21;
            FLOAT _22;
            FLOAT _23;
            FLOAT _31;
            FLOAT _32;
            FLOAT _33;
        };

        FLOAT m[3][3];
    };

} XMFLOAT3X3;

There is also XMMATRIX, which like XMVECTOR is the memory-aligned structure that maps to hardware registers. Additional variations of these types we’ll discuss later on in this chapter in the section titled “Structures and Types.”

The Identity Matrix

There is a special matrix that you should be aware of called the identity matrix. This is the default matrix. It contains values that reset scaling to 1, with no rotations and no translation taking place. The identity matrix is created by zeroing out all 16 values in the matrix and then applying ones along the diagonal. The identity matrix can be used as a starting point for all matrix operations. Multiplying a matrix with an identity matrix has no affect, and transforming an object by an identity matrix does nothing, since the matrix has no translation, no scaling, and no rotation.

Following is an identity matrix:

float matrix [4][4] =
{
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

Creating an identity matrix in XNA Math is as simple as calling the XMMatrixIdentity function. This function takes no parameters and returns an XMMATRIX object loaded as an identity matrix. The function prototype for this function is:

XMMATRIX XMMatrixIdentity( );

Matrix Scaling

One of the transformations you can apply to an object is scaling. Scaling is the ability to shrink or enlarge an object by a certain factor. For instance, if you have a square centered on the origin that was two units wide and two units tall and you scaled it twice in size, you would have a square that now went four units in each direction (i.e., twice its original size).

Remember the ones that were placed into the identity matrix? Those ones control the scaling on each of the three axes. When vertices are transformed using this matrix, their X, Y, and Z values are altered based on the scaling values. By changing the X axis value to 2 and the Y axis to 3, the objects will be scaled twice their size in the X direction and three times their size in the Y. An example ScaleMatrix array is shown next. The variables scaleX, scaleY, and scaleZ show where the appropriate scaling values would go.

float ScaleMatrix [4][4] =
{
    scaleX, 0.0f, 0.0f, 0.0f,
    0.0f, scaleY, 0.0f, 0.0f,
    0.0f, 0.0f, scaleZ, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

In XNA Math we can use the function XMMatrixScaling to create a scaling matrix. This function takes as parameters the X, Y, and Z scaling factors and returns an XMMATRIX that represents this scale in matrix form. The function prototype can be seen as follows:

XMMATRIX XMMatrixScaling( FLOAT ScaleX, FLOAT ScaleY, FLOAT ScaleZ);

Matrix Translation

The act of moving an object is called translation. Translation allows for an object to be moved along any of the three axes by specifying the amount of movement needed. For example, if you want to move an object right along the X axis, you would need to translate that object an appropriate number of units in the positive X direction. To do so, you would need to create a translation matrix.

Again, we’ll start with the identity matrix and change it to add in the variables that affect translation. Take a look at the following translation matrix; the variables moveX, moveY, and moveZ show you where the translation values would go. If you wanted to translate an object four units to the right, you would replace moveX with the value 4.

float matrix [4][4] =
{
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    moveX, moveY, moveZ, 1.0f
};

In XNAMath we create a translation matrix by calling XMMatrixTranslation , which takes as parameters the X, Y, and Z translation values and has the following function prototype:

XMMATRIX XMMatrixTranslation( FLOAT OffsetX, FLOAT OffsetY, FLOAT OffsetZ );

Matrix Rotation

The final effect we’ll describe is rotation. Rotation is the act of turning an object around a certain axis. This allows objects to change their orientation within space. For instance, if you wanted to rotate a planet, you would create a rotation matrix and apply it to the planet.

Rotation matrices take a little more explanation because the matrix needs to change based on the axis you’re trying to rotate around.

When rotating around any axis, it will require you to convert the angle of rotation into both a sine and a cosine value. These values are then plugged into the matrix to cause the proper amount of rotation. When rotating around the X axis, the sine and cosine values are placed into the matrix in the following manner.

float rotateXMatrix[4][4] =
{
    1.0f, 0.0f, 0.0f, 0.0f
    0.0f, cosAngle, sinAngle, 0.0f,
    0.0f, -sinAngle, cosAngle, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

Rotating around the Y axis requires only that the sine and cosine values be moved to different positions within the matrix. In this instance, the values are moved to affect the X and Z axes as shown in the following manner.

float rotateYMatrix[4][4] =
{
    cosAngle, 0.0f, -sinAngle, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    sinAngle, 0.0f, cosAngle, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

The final rotation matrix allows for rotation around the Z axis. In this instance, the sine and cosine values affect the X and Y axes.

float rotateZMatrix[4][4] =
{
    cosAngle, sinAngle, 0.0f, 0.0f,
    -sinAngle, cosAngle, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

In XNA Math we have quite a few different options for creating a rotation matrix. We can rotate along a single axis as we’ve already discussed to create three separate rotation matrices, which we can then combine into one with the following functions:

XMMATRIX XMMatrixRotationX( FLOAT Angle );
XMMATRIX XMMatrixRotationY( FLOAT Angle );
XMMATRIX XMMatrixRotationZ( FLOAT Angle );

We can also rotate using Yaw, Pitch, and Roll values by using the following XNA Math functions:

XMMATRIX XMMatrixRotationRollPitchYaw(
    FLOAT Pitch,
    FLOAT Yaw,
    FLOAT Roll
);


XMMATRIX XMMatrixRotationRollPitchYawFromVector(
    XMVECTOR Angles
);

Pitch is the rotation in radians (not degrees) for the X axis, Yaw is the rotation in radians for the Y axis, and Roll is the rotation in radians for the Z axis. By specifying all three of these angles in radians we can use the XMMatrixRota-tionRollPitchYaw function to build one rotation matrix that combines all three axis rotations instead of creating three separate rotation matrices using XMMa-trixRotationX etc. and combining the three together into one final rotation matrix.

Other options of rotation include rotating around an axis where we provide an axis to rotate about and an angle in radians that describes by how much to rotate. This is done using XMMatrixRotationAxis:

XMMATRIX XMMatrixRotationAxis( XMVECTOR Axis, FLOAT Angle );

We can also rotate around a normal vector by using XMMatrixRotationNormal:

XMMATRIX XMMatrixRotationNormal( XMVECTORNormalAxis, FLOAT Angle );

And we can also rotate around a quaternion using XMMatrixRotationQuaternion:

XMMATRIX XMMatrixRotationQuaternion( XMVECTOR Quaternion );

A quaternion is another mathematical object we can use to represent rotation in graphics programming. The topic of quaternions is more advanced than matrices and is definitely something you will encounter when you touch upon advanced character animation techniques, such as skinned animation using bones (skeleton).

Matrix Concatenation

The last thing you need to learn about matrices is how to combine them. Most of the time, you won’t need to only translate an object, you’ll need to scale it or maybe rotate it all in one go. Multiple matrices can be multiplied, or concatenated, together to create a single matrix that is capable of containing the previously single calculations. This means that instead of needing a matrix for rotation as well as one for translation, these two can be combined into one.

Depending on the platform and intrinsic used, in XNA Math we could use the overloaded multiplication operator. The following single line of code demonstrates this.

XMMATRIX finalMatrix = rotationMatrix * translationMatrix;

A new matrix called finalMatrix is created, which now contains both the rotation and translation. This new matrix can then be applied to any object going through the pipeline. One thing to mention though is to watch out for the order in which you multiply matrices. Using the previous line of code, objects will be rotated first and then translated away from that position. For example, rotating the object 90 degrees around the Y axis and then translating it four units along the X axis would cause the object to be rotated in place at the origin and then moved four units to the right. When creating a world matrix, you should combine the various matrices as scaling × rotation × translation.

If the translation appeared first, the object would be translated four units to the right of the origin first and then rotated 90 degrees. Think what would happen if you extended your hand to your right and then turned your body 90 degrees— where would your hand end up? Not exactly in the same place as before; you need to make sure that the order in which you multiply the matrices is the order you really want them applied.

In XNA Math we can also multiply matrices using the function XMMatrixMul-tiply, which takes as parameters two matrices to multiply together and returns the resulting matrix. Its prototype can be seen as follows:

XMMATRIX XMMatrixMultiply( XMMATRIX M1, XMMATRIX M2 );

Matrix concatenation is another term for matrix multiplication.

Cube Demo

In this chapter we will create our first true 3D demo, called the Cube demo, which is located on the companion website in the Chapter6/CubeDemo folder. By “first true 3D demo” we are referring to the fact that all demos up to this point have created a flat-colored or textured surface (e.g., triangles, sprites, etc.). Although technically these demos are 3D, since there was nothing to stop us from utilizing the Z axis of the vertices, we did not create a mesh with an actual volume. Let’s start with the “Hello World” of 3D demos by creating a 3D cube.

To begin we will update our base DirectX 11 class by adding a depth/stencil view. This is related to depth and stencil testing. A stencil buffer is used primarily to render to a destination where you can increment or decrement the values in the buffer for pixels that passed the stencil test. This is often used for more advanced rendering topics, with one very famous technique being stencil shadow volumes.

Depth testing, as we mentioned in Chapter 2, is used to ensure that surfaces are properly drawn and displayed in the correct order without the need to sort polygons. For example, if we have two triangles with the same X and Y axis but separated by 100 units along the Z axis, then the triangles are not touching or overlapping. If we draw the closer triangle first and the further triangle second, the second triangle’s color data will overwrite the triangle that is closer to the camera. This is not what we want, and if we want to draw those triangles properly, where the closer one is displayed to be closer than the further triangle, we’d have to spend precious resources sorting those polygons so we know the proper rendering order.

But sorting polygons is no cheap operation, and a scene can have millions of them, or at least thousands, that are visible in any given frame. The depth buffer is a simple concept that eliminates the need to presort all polygons before submitting them to the graphics hardware. Every time a surface is rendered, its vertices are transformed by the model-view projection matrix. The depth of each of these vertices as well as the interpolated depths of the pixels that lie within the surface is calculated by the hardware. The depth buffer stores the depths for each rendered pixel as long as the depth is determined (by hardware) to be closer to the viewer than what was previously rendered. If the depth is closer, then the various buffers are updated appropriately. In other words, the final rendered scene’s color buffer, depth buffer, and whatever other destination targets attached are updated for those pixels that pass the depth test. Pixels that do not pass the depth test are discarded.

Note

Early 3D games like Wolfenstein 3D by Id Software were sorting polygons before rendering the scenes. This was one of the early uses for Binary Space Partitioning (BSP) Trees.

We can represent a depth/stencil view as an ID3D11DepthStencilView object. As we already know, in Direct3D a view is simply a way for our hardware to access a resource. In the case of a depth buffer that resource is going to be a 2D texture, and that means that in addition to creating a new ID3D11DepthStencilView object in our base class, we’ll also need to create a ID3D11Texture2D object to store the contents of the depth buffer. Listing 6.1 shows our updated Dx11De-moBase class definition, where the new objects for a ID3D11DepthStencilView and a ID3D11Texture2D are added as member pointers.

Example 6.1. Our updated DxllDemoBase class that includes depth buffer objects.

class Dx11DemoBase
{
    public:
        Dx11DemoBase( );
        virtual ~Dx11DemoBase( );

        bool Initialize( HINSTANCE hInstance, HWND hwnd );
        void Shutdown( );
        bool CompileD3DShader( char* filePath, char* entry,
                             char* shaderModel, ID3DBlob** buffer );

        virtual bool LoadContent( );
        virtual void UnloadContent( );

        virtual void Update( float dt ) = 0;
        virtual void Render( ) = 0;

    protected:
        HINSTANCE hInstance_;
        HWND hwnd_;

        D3D_DRIVER_TYPE driverType_;
        D3D_FEATURE_LEVEL featureLevel_;

        ID3D11Device* d3dDevice_;
        ID3D11DeviceContext* d3dContext_;
        IDXGISwapChain* swapChain_;
        ID3D11RenderTargetView* backBufferTarget_;

        ID3D11Texture2D* depthTexture_;
        ID3D11DepthStencilView* depthStencilView_;
};

The base class’s Initialize function must be updated to create the depth buffer. In Listing 6.2 we will omit the code from the Initialize function that we’ve been using in every demo of this book and only focus on the code that is new. This new code includes creating the 2D texture, creating the depth/stencil view, and attaching that view to Direct3D.

We create the 2D texture that will act as the depth buffer by simply filling out a D3D11_TEXTURE2D_DESC object with the properties necessary for the depth buffer. The depth buffer must match our rendering width and height, have a bind flag of D3D11_BIND_DEPTH_STENCIL, allow for default usage, and have a format of DXGI_FORMAT_D24_UNORM_S8_UINT. The format essentially says that we are using 24 bits for a single component that will store the depth. Since a depth is a single value, we do not need an RGB or RGBA texture image and only need to create a single component (R) texture.

Once we have the depth texture, we then create the depth/stencil view. This is done by filling out a D3D11_DEPTH_STENCIL_VIEW_DESC object with the properties that define what we are viewing, which in our case requires us to set the format to match that of the depth texture (our examples use DXGI_FORMAT_D24_UNORM_ S8_UINT) and to use a view dimension flag of D3D11_DSV_DIMENSION_TEXTURE2D. The depth/stencil target is created with a call to CreateDepthStencilView, which takes as parameters the depth texture, the view description object, and a pointer to the object that will store the depth/stencil view.

Listing 6.2 shows the new code added to the Initialize function to create the depth texture and to create the render target view for the depth/stencil buffer.

Example 6.2. The updated Initialize function.

bool Dx11DemoBase::Initialize( HINSTANCE hInstance, HWND hwnd )
{
    // ... Previous initialize code ...

    D3D11_TEXTURE2D_DESC depthTexDesc;
    ZeroMemory( &depthTexDesc, sizeof( depthTexDesc ) );
    depthTexDesc.Width = width;
    depthTexDesc.Height = height;
    depthTexDesc.MipLevels = 1;
    depthTexDesc.ArraySize = 1;
    depthTexDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthTexDesc.SampleDesc.Count = 1;
    depthTexDesc.SampleDesc.Quality = 0;
    depthTexDesc.Usage = D3D11_USAGE_DEFAULT;
    depthTexDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    depthTexDesc.CPUAccessFlags = 0;
    depthTexDesc.MiscFlags = 0;

    result = d3dDevice_->CreateTexture2D( &depthTexDesc, NULL, &depthTexture_ );

    if( FAILED( result ) )
    {
        DXTRACE_MSG( "Failed to create the depth texture!" );
        return false;
    }

    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
    ZeroMemory( &descDSV, sizeof( descDSV ) );
    descDSV.Format = depthTexDesc.Format;
    descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;

    result = d3dDevice_-&gt;CreateDepthStencilView( depthTexture_, &descDSV,
        &depthStencilView_ );

    if( FAILED( result ) )
    {
        DXTRACE_MSG( "Failed to create the depth stencil target view!" );
        return false;
    }

    d3dContext_->OMSetRenderTargets( 1, &backBufferTarget_, depthStencil-
View_ );

    D3D11_VIEWPORT viewport;
    viewport.Width = static_cast<float>( width );
    viewport.Height = static_cast<float>( height );
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;
    viewport.TopLeftX = 0.0f;
    viewport.TopLeftY = 0.0f;

    d3dContext_->RSSetViewports( 1, &viewport );

    return LoadContent( );
}

Since our depth texture and view are objects that consume resources, we must remember to release these resources in the base class’s Shutdown function, which can be seen in Listing 6.3.

Example 6.3. The updated Shutdown function.

void Dx11DemoBase::Shutdown( )
{
    UnloadContent( );

    if( depthTexture_ ) depthTexture_->Release( );
    if( depthStencilView_ ) depthStencilView_->Release( );
    if( backBufferTarget_ ) backBufferTarget_->Release( );
    if( swapChain_ ) swapChain_->Release( );
    if( d3dContext_ ) d3dContext_->Release( );
    if( d3dDevice_ ) d3dDevice_->Release( );

    depthTexture_ = 0;
    depthStencilView_ = 0;
    backBufferTarget_ = 0;
    swapChain_ = 0;
    d3dContext_ = 0;
    d3dDevice_ = 0;
}

Our demo class will be called CubeDemo. This CubeDemo class will create three constant buffers for the model, view, and projection matrices. Since our demos really only need to combine these matrices into a model-view projection matrix, we’ll keep them separate to show an example of utilizing multiple constant buffers. Later in this section you’ll see how this relates to HLSL and setting usage trends.

Also in the demo we create an index buffer. In Chapter 3 we mentioned that an index buffer is used to specify array indices into a vertex list to specify which vertices form triangles. With large polygon count (closed volume) models this can save a lot of memory when representing our geometry. Knowing how to create an index buffer is important when it comes to 3D rendering, so we will cover this in this demo.

The CubeDemo can be seen in Listing 6.4. It builds off of the Texture Mapping demo of Chapter 3. We are also storing the view and projection matrices on the class level, since these never change in this specific demo and don’t need to be recalculated for each frame. In Chapter 8, when we talk about cameras, we will update the view matrix each frame in the Update function, and that camera demo will build directly off of this demo’s code.

Example 6.4. The CubeDemo class.

#include"Dx11DemoBase.h"
#include<xnamath.h>


class CubeDemo : public Dx11DemoBase
{
    public:
        CubeDemo( );
        virtual ~CubeDemo( );

        bool LoadContent( );
        void UnloadContent( );

        void Update( float dt );
        void Render( );

    private:
        ID3D11VertexShader* solidColorVS_;
        ID3D11PixelShader* solidColorPS_;

        ID3D11InputLayout* inputLayout_;
        ID3D11Buffer* vertexBuffer_;
        ID3D11Buffer* indexBuffer_;

        ID3D11ShaderResourceView* colorMap_;
        ID3D11SamplerState* colorMapSampler_;

        ID3D11Buffer* viewCB_;
        ID3D11Buffer* projCB_;
        ID3D11Buffer* worldCB_;
        XMMATRIX viewMatrix_;
        XMMATRIX projMatrix_;
};

We must update the LoadContent function to load a 3D cube into the vertex buffer instead of the square that we were loading in the Texture Mapping demo. This is done simply by specifying each triangle one at a time in the vertices list for the triangles of the cube. We also create an index buffer that specifies which vertices form which triangles. If you look up the vertices manually (as an exercise) using the array indices in the index buffer, then you can clearly see how each triangle is specified.

Creating the index buffer is done the exact same way as the vertex buffer, with the exception that we are using a bind flag of D3D11_BIND_INDEX_BUFFER. That’s it! At the end of the LoadContent function we create our three constant buffers, and we initialize our view and projection matrices. Since we don’t have a camera yet, the view matrix will equal an identity matrix (this will change in Chapter 8). The projection matrix is created as a perspective projection, which can be done with a call to XMMatrixPerspectiveFovLH. The XMMatrixPerspectiveFovLH function takes as parameters the field of view of the camera in radians, the aspect ratio of the camera, and the near and far clip planes. If you wish to specify the field of view in degrees, you’ll have to convert those degrees to radians yourself before passing it as a parameter to this function. Also, the aspect ratio is measured as the width divided by the height.

The updated LoadContent function can be seen in Listing 6.5. The bulk of the code deals with defining the triangle data manually. In Chapter 8 we will explore loading models from files exported by tools such as 3D Studio Max, which is necessary for complex and detailed models.

Example 6.5. The updated LoadContent function.

bool CubeDemo::LoadContent( )
{
    // ... Previous demo’s LoadContent code ...


    VertexPos vertices[] =
    {
        { XMFLOAT3( -1.0f,   1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3(   1.0f,   1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3(   1.0f,   1.0f,   1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3( -1.0f,   1.0f,   1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

        { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3(   1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3(   1.0f, -1.0f,   1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3( -1.0f, -1.0f,   1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
        { XMFLOAT3( -1.0f, -1.0f,   1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3( -1.0f,   1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3( -1.0f,   1.0f,   1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

        { XMFLOAT3(   1.0f, -1.0f,   1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3(   1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3(   1.0f,   1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3(   1.0f,   1.0f,  1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

        { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3(   1.0f, -1.0f, -1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3(   1.0f,   1.0f, -1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3( -1.0f,   1.0f, -1.0f ), XMFLOAT2( 0.0f, 1.0f ) },

        { XMFLOAT3( -1.0f, -1.0f,   1.0f ), XMFLOAT2( 0.0f, 0.0f ) },
        { XMFLOAT3(   1.0f, -1.0f,   1.0f ), XMFLOAT2( 1.0f, 0.0f ) },
        { XMFLOAT3(   1.0f,   1.0f,  1.0f ), XMFLOAT2( 1.0f, 1.0f ) },
        { XMFLOAT3( -1.0f,   1.0f,   1.0f ), XMFLOAT2( 0.0f, 1.0f ) },
    };
    
    D3D11_BUFFER_DESC vertexDesc;
    ZeroMemory( &vertexDesc, sizeof( vertexDesc ) );
    vertexDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexDesc.ByteWidth = sizeof( VertexPos ) * 24;
    
    D3D11_SUBRESOURCE_DATA resourceData;
    ZeroMemory( &resourceData, sizeof( resourceData ) );
    resourceData.pSysMem = vertices;
    
    d3dResult = d3dDevice_->CreateBuffer( &vertexDesc,
        &resourceData, &vertexBuffer_ );
    
    if( FAILED( d3dResult ) )
    {
         DXTRACE_MSG( "Failed to create vertex buffer!" );
         return false;
    }
    WORD indices[] =
    {
        3,   1,  0,  2,  1,  3,
        6,   4,  5,  7,  4,  6,
        11,  9,  8, 10,  9, 11,
        14, 12, 13, 15, 12, 14,
        19, 17, 16, 18, 17, 19,
        22, 20, 21, 23, 20, 22
    };
    
    D3D11_BUFFER_DESC indexDesc;
    ZeroMemory( &indexDesc, sizeof( indexDesc ) );
    indexDesc.Usage = D3D11_USAGE_DEFAULT;
    indexDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexDesc.ByteWidth = sizeof( WORD ) * 36;
    indexDesc.CPUAccessFlags = 0;
    resourceData.pSysMem = indices;
    
    d3dResult = d3dDevice_->CreateBuffer( &indexDesc,
        &resourceData, &indexBuffer_ );
    
    if( FAILED( d3dResult ) )
    {
         DXTRACE_MSG( "Failed to create index buffer!" );
         return false;
    }
    
    d3dResult = D3DX11CreateShaderResourceViewFromFile( d3dDevice_,
        "decal.dds", 0, 0, &colorMap_, 0 );
    
    if( FAILED( d3dResult ) )
    {
        DXTRACE_MSG( "Failed to load the texture image!" );
        return false;
    }
    
    D3D11_SAMPLER_DESC colorMapDesc;
    ZeroMemory( &colorMapDesc, sizeof( colorMapDesc ) );
    colorMapDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    colorMapDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    colorMapDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    colorMapDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    colorMapDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    colorMapDesc.MaxLOD = D3D11_FLOAT32_MAX;
    
    d3dResult = d3dDevice_->CreateSamplerState( &colorMapDesc,
        &colorMapSampler_ );
    
    if( FAILED( d3dResult ) )
    {
        DXTRACE_MSG( "Failed to create color map sampler state!" );
        return false;
    }
    
    
    D3D11_BUFFER_DESC constDesc;
    ZeroMemory( &constDesc, sizeof( constDesc ) );
    constDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    constDesc.ByteWidth = sizeof( XMMATRIX );
    constDesc.Usage = D3D11_USAGE_DEFAULT;
    
    d3dResult = d3dDevice_->CreateBuffer( &constDesc, 0, &viewCB_ );
    
    if( FAILED( d3dResult ) )
    {
        return false;
    }
    
    d3dResult = d3dDevice_->CreateBuffer( &constDesc, 0, &projCB_ );
    
    if( FAILED( d3dResult ) )
    {
        return false;
    }
    
    d3dResult = d3dDevice_->CreateBuffer( &constDesc, 0, &worldCB_ );
    
    if( FAILED( d3dResult ) )
    {
        return false;
    }
    
       viewMatrix_ = XMMatrixIdentity( );
       projMatrix_ = XMMatrixPerspectiveFovLH( XM_PIDIV4, 800.0f / 600.0f,
           0.01f, 100.0f );
    
       viewMatrix_ = XMMatrixTranspose(viewMatrix_ );
       projMatrix_ = XMMatrixTranspose( projMatrix_ );
    
       return true;
}

Remember that all of our new objects need to be released by the UnloadContent function. This includes our three constant buffers and well as our newly created index buffer. This can be seen in Listing 6.6 in the updated UnloadContent function.

Example 6.6. The updated UnloadContent function.

void CubeDemo::UnloadContent( )
{
    if( colorMapSampler_ ) colorMapSampler_->Release( );
    if( colorMap_ ) colorMap_->Release( );
    if( solidColorVS_ ) solidColorVS_->Release( );
    if( solidColorPS_ ) solidColorPS_->Release( );
    if( inputLayout_ ) inputLayout_->Release( );
    if( vertexBuffer_ ) vertexBuffer_->Release( );
    if( indexBuffer_ ) indexBuffer_->Release( );
    if( viewCB_ ) viewCB_->Release( );
    if( projCB_ ) projCB_->Release( );
    if( worldCB_ ) worldCB_->Release( );

    colorMapSampler_ = 0;
    colorMap_ = 0;
    solidColorVS_ = 0;
    solidColorPS_ = 0;
    inputLayout_ = 0;
    vertexBuffer_ = 0;
    indexBuffer_ = 0;
    viewCB_ = 0;
    projCB_ = 0;
    worldCB_ = 0;
}

The last code to cover is the HLSL shader and the rendering function. The rendering function adds a line of code to clear the depth buffer by calling the device context’s ClearDepthStencilView function, which takes as parameters the depth/stencil view, what we are clearing (in this case we use the clear depth flag of D3D11_CLEAR_DEPTH), a value between 0 and 1 to clear the depth buffer to, and a value to clear the stencil buffer to.

Before we begin rendering, we must set the index buffer to the input assembler, since we are making use of it. This is done with a call to IASetIndexBuffer , which takes as parameters the index buffer, the data format of the buffer, and a starting offset in bytes. The format we used to create the index buffer is a 16-bit unsigned integer, which means we must use DXGI_FORMAT_R16_UINT for the data format parameter ofthis function. Whatever format you use for the index buffer array must be passed to this function so that Direct3D knows how to properly access the elements of the buffer.

The last steps we take are to bind our constant buffer and draw our geometry. We bind each constant buffer to b0, b1, and b2, which are specified in the HLSL shader file. To draw the data, we don’t use a call to the Draw function but instead we call the DrawIndexed function. DrawIndexed takes as parameters the number of indices in the index buffer, the starting index we are drawing, and the base vertex location, which is an offset added to each index before reading from the vertex buffer. This can be useful when only drawing portions of the vertex buffer, which can happen if you batched multiple models in one larger vertex buffer. That is a topic for advanced rendering optimizations.

The updated rendering function can be seen in Listing 6.7.

Example 6.7. The updated Render function.

void CubeDemo::Render( )
{
    if( d3dContext_ == 0 )
        return;

    float clearColor[4] = { 0.0f, 0.0f, 0.25f, 1.0f };
    d3dContext_->ClearRenderTargetView( backBufferTarget_, clearColor );
    d3dContext_->ClearDepthStencilView( depthStencilView_,
        D3D11_CLEAR_DEPTH, 1.0f, 0 );

    unsigned int stride = sizeof( VertexPos );
    unsigned int offset = 0;

    d3dContext_->IASetInputLayout( inputLayout_ );
    d3dContext_->IASetVertexBuffers( 0, 1, &vertexBuffer_, &stride, &offset );
    d3dContext_->IASetIndexBuffer( indexBuffer_, DXGI_FORMAT_R16_UINT, 0 );
    d3dContext_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLE
LIST);

    d3dContext_->VSSetShader( solidColorVS_, 0, 0 );
    d3dContext_->PSSetShader( solidColorPS_, 0, 0 );
    d3dContext_->PSSetShaderResources( 0, 1, &colorMap_ );
    d3dContext_->PSSetSamplers( 0, 1, &colorMapSampler_ );

    XMMATRIX rotationMat = XMMatrixRotationRollPitchYaw( 0.0f, 0.7f, 0.7f );
    XMMATRIX translationMat = XMMatrixTranslation( 0.0f, 0.0f, 6.0f );

    XMMATRIX worldMat = rotationMat * translationMat;
    worldMat = XMMatrixTranspose( worldMat );


    d3dContext_->UpdateSubresource( worldCB_, 0, 0, &worldMat, 0, 0 );
    d3dContext_->UpdateSubresource( viewCB_, 0, 0, &viewMatrix_, 0, 0 );
    d3dContext_->UpdateSubresource( projCB_, 0, 0, &projMatrix_, 0, 0 );

    d3dContext_->VSSetConstantBuffers( 0, 1, &worldCB_ );
    d3dContext_->VSSetConstantBuffers( 1, 1, &viewCB_ );
    d3dContext_->VSSetConstantBuffers( 2, 1, &projCB_ );

    d3dContext_->DrawIndexed( 36, 0, 0 );

    swapChain_->Present( 0, 0 );
}

In the HLSL code we create three constant buffers that we register to b0, b1 , and b2. We must keep in mind that the buffers must be consistent to what we’ve specified in the Render function so that the correct buffer is registered to the correct HLSL object.

For the constant buffer we have cbChangesEveryFrame, cbNeverChanges, and cbChangeOnResize. This is done a lot in the DirectX SDK, and by grouping our constant buffer objects by how often they are updated, we can update all the necessary constants for a particular update frequency at the same time. Again, for our purposes we can do what we’ve done in past demos and use one constant buffer for the model-view projection matrix. The naming convention used for the constant buffers simply allows us to easily see how often the objects within are updated by the outside program.

The remainder of the HLSL code is essentially the same as the Texture Mapping demo, with the exception of the fact that we are transforming the incoming vertex by the model, view, and projection matrices separately. The demo’s HLSL shader file can be seen in Listing 6.8. A screenshot of the demo can be seen in Figure 6.5.

3D Cube demo screenshot.

Figure 6.5. 3D Cube demo screenshot.

Example 6.8. The HLSL shader for the CubeDemo.

Texture2D colorMap_ : register( t0 );
SamplerState colorSampler_ : register( s0 );


cbuffer cbChangesEveryFrame : register( b0 )
{
    matrix worldMatrix;
};

cbuffer cbNeverChanges : register( b1 )
{
    matrix viewMatrix;
};

cbuffer cbChangeOnResize : register( b2 )
{
    matrix projMatrix;
};


struct VS_Input
{
    float4 pos   : POSITION;
    float2 tex0 : TEXCOORD0;
};

struct PS_Input
{
    float4 pos   : SV_POSITION;
    float2 tex0 : TEXCOORD0;
};


PS_Input VS_Main( VS_Input vertex )
{
    PS_Input vsOut = ( PS_Input )0;
    vsOut.pos = mul( vertex.pos, worldMatrix );
    vsOut.pos = mul( vsOut.pos, viewMatrix );
    vsOut.pos = mul( vsOut.pos, projMatrix );
    vsOut.tex0 = vertex.tex0;

    return vsOut;
}


float4 PS_Main( PS_Input frag ) : SV_TARGET
{
    return colorMap_.Sample( colorSampler_, frag.tex0 );
}

Additional XNA Math Topics

XNA Math has hundreds of functions and structures. There would be too much to cover in just one chapter, and the math behind it could span multiple books. In the remainder ofthis chapter we will look at other areas ofXNA Math that might be important to know for a user at the beginner level ofworking with DirectX 11.

Compiler Directives

XNA Math has a list of compiler directives used to tune how XNA Math is compiled and used within an application. These compiler directives give developers the ability to specify their applications’ functionality and include the following:

  • _XM_NO_INTRINSICS_

  • _XM_SSE_INTRINSICS_

  • _XM_VMX128_INTRINSICS_

  • XM_NO_ALIGNMENT

  • XM_NO_MISALIGNED_VECTOR_ACCESS

  • XM_NO_OPERATOR_OVERLOADS

  • XM_STRICT_VECTOR4

_XM_NO_INTRINSICS_ defines that no intrinsic types are to be used and that the XNA Math library will only use standard floating-point precision when performing its operations. This can be seen as the behavior that will allow an application to use XNA Math with no special optimizations or functionality.

_XM_SSE_INTRINSICS_ is used to enable the use of SSE and SSE2 intrinsic types on platforms that support it. This define has no effect on platforms that do not support SSE and SSE2. SSE and SSE2 are types of SIMD (single-instruction multiple data) operations that aim to boost performance by providing instruction-level parallelism, or in other words allow multiple operations to be done on multiple pieces ofdata at the same time. SIMD is discussed in more detail later in this chapter under the subsection “SIMD.” SSE stands for Streaming SIMD Extension.

_XM_VMX128_INTRINSICS_ is used to define that the Xbox 360 target use the VMX128 intrinsic but has no effect on other platforms. VMX is another SIMD set of instructions. VMX128 is an extension of the VMX set for use in Xenon processors, which is found inside the Xbox 360. Using this intrinsic should be done if the game is being targeted toward the Xbox 360.

XM_NO_ALIGNMENT is used to define that no alignment should be made to the data. For example, the XMMATRIX type structure will not be aligned to 16 bytes using this directive. This is not defined by default since by default alignment takes places.

XM_NO_MISALIGNED_VECTOR_ACCESS is used to improve the performance of the VMX128 intrinsic when operating on write-combined memory. This affects the Xbox 360’s Xenon processor.

XM_NO_OPERATOR_OVERLOADS is used to disable the use of C++ style operator overloads. By default this is not defined, but it can be used to force the omission of overloaded operators.

XM_STRICT_VECTOR4 is used to determine if the X, Y, Z, and W components of the XMVECTOR and XMVECTORI types can be directly accessed (e.g., float × Val = vec.x). This only affects Xbox 360 targets that are defined using the __vector4 intrinsic type and has no effect on Windows targets using the __m128 type. Accessor functions can be used to access members of these structures when this strict directive is used.

Constants

Compiler constants provide useful information to developers using the XNA Math library. The list of constants and what each one represents can be seen in the following:

  • XNAMATH_VERSION—Used to obtain the XNA Math library version.

  • XM_CACHE_LINE_SIZE—The size in bytes of the cache line used by stream operations within the XNA Math library.

  • XM_PI—Represents the value of PI.

  • XM_2PI—Represents the value of 2 × PI.

  • XM_1DIVPI—Represents the value of 1 / PI.

  • XM_1DIV2PI—Represents the value of 2 / PI.

  • XM_PIDIV2—Represents the value of PI / 2.

  • XM_PIDIV4—Represents the value of PI / 4.

  • XM_PERMUTE_0X—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the first vector’s X component is copied to the result vector’s location dictated by the control vector.

  • XM_PERMUTE_0Y—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the first vector’s Y component is copied to the result vector’s location dictated by the control vector.

  • XM_PERMUTE_0Z—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the first vector’s Z component is copied to the result vector’s location dictated by the control vector.

  • XM_PERMUTE_0W—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the first vector’s W component is copied to the result vector’s location dictated by the control vector.

  • XM_PERMUTE_1X—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the second vector’s X component is copied to the result vector’s location dictated by the control vector.

  • XM_PERMUTE_1Y—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the second vector’s Y component is copied to the result vector’s location dictated by the control vector.

  • XM_PERMUTE_1Z—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the second vector’s Z component is copied to the result vector’s location dictated by the control vector.

  • XM_PERMUTE_1W—This is a constant used to create a control vector that controls how data is copied using the XMVectorPermute function. For this constant the second vector’s W component is copied to the result vector’s location dictated by the control vector.

  • XM_SELECT_0—Used to construct a control vector that controls how components are copied when using XMVectorSelect. The first vector’s component is copied to the index location in the result vector that is specified by the control vector.

  • XM_SELECT_1—Used to construct a control vector that controls how components are copied when using XMVectorSelect. The second vector’s component is copied to the index location in the result vector that is specified by the control vector.

  • XM_CRMASK_CR6—A mask used to obtain a comparison result.

  • XM_CRMASK_CR6TRUE—A mask used to obtain a comparison result and indicate if it is logically true.

  • XM_CRMASK_CR6FALSE—A mask used to obtain a comparison result and indicate if it is logically false.

  • XM_CRMASK_CR6BOUNDS—A mask used to obtain a comparison result and to verify if the results indicate some of the inputs were out of bounds.

Macros

Macros are available in XNA Math to provide extra functionality for common operations such as comparison operations, min and max determination, asserts, and the ability to mark objects as global. The complete list ofXNA Math macros is seen in the following:

  • XMASSERT

  • XMGLOBALCONST

  • XMComparisonAllFalse

  • XMComparisonAllTrue

  • XMComparisonAllInBounds

  • XMComparisonAnyFalse

  • XMComparisonAnyOutOfBounds

  • XMComparisonAnyTrue

  • XMComparisonMixed

  • XMMax

  • XMMin

XMASSERT provides debugging information by using assertions that are raised when a condition is false. This macro takes the expression to test and will include the file and line information for you.

XMGLOBALCONST is used to specify an object as a “pick-any” global constant and is used to reduce the size of the data segment and avoid reloads of global constants that are used and declared in multiple places within the XNA Math library.

XMComparisonAllFalse is used to compare all components and to return true if they all evaluate to false.

XMComparisonAllTrue is used to compare all components and to return true if they all evaluate to true.

XMComparisonAllInBounds is used to test if all components are within the allowed bounds. True is returned if they are all within the bounds.

XMComparisonAnyFalse returns true if any, not necessarily all, of the components evaluate to false.

XMComparisonAnyOutOfBounds returns true if any, not necessarily all, of the components are out of bounds.

XMComparisonAnyTrue returns true if any component, not necessarily all, evaluates to true.

XMComparisonMixed returns true if some of the components evaluated to true while others evaluated to false.

XMMax compares two types and returns the one determined to be larger using the operator <.

XMMin compares two types and returns the one determined to be smaller using the operator <.

Structures and Types

The goal of these various structures and types is to encapsulate their functionality to make them easier to code with, easier to obtain portability, and allows for data optimizations. All of these structures and types are exposed via the header file xnamath.h. So far we’ve briefly touched upon a few vector and matrix structures, but in this section we will look at all of them.

The first sets of structures we will look at are the following:

  • XMBYTE4

  • XMBYTEN4

  • XMCOLOR

  • XMDEC4

  • XMDECN4

  • XMDHEN3

  • XMDHENN3

The XMBYTE4 structure is used to create a four-component vector where each component is 1 byte (using the char data type). The XMBYTEN4 is similar to the XMBYTE4 structure with the exception that XMBYTEN4 is used to store normalized input. When XMBYTEN4 is initialized, its inputs are multiplied by 127.0f and scaled to fit the range 0.0 - 1.0f. Input data are to be in the range of -127.0f and 127.0f when using XMBYTEN4.

XMCOLOR is a 32-bit color structure that stores red, blue, green, and alpha color channels. Each channel is an unsigned 8-bit value, where all four 8-bit channels total 32 bits for the entire structure. This is the same as XMBYTE4.

The XMDEC4 structure uses 10 bits for the X, Y, and Z components and uses 2 bits for the W. This totals 32 bits for the structure. XMDECN4 stores normalized values. On the other hand, XMDHEN3 stores 10 bits for the X component and stores 11 bits for the Y and Z components. XMDHENN3 stores the normalized values of a XMDHEN3. These can be used if you need structures that provide more bits per component while totaling 32 bits in all.

The next set of structures deals with 2D components:

  • XMFLOAT2

  • XMFLOAT2A

  • XMHALF2

  • XMSHORT2

  • XMSHORTN2

  • XMUSHORT2

  • XMUSHORTN2

XMFLOAT2 was mentioned earlier in this chapter and is used to represent a 2D floating-point vector. XMFLOAT2A represents a 16-byte boundary aligned XMFLOAT2.

XMHALF2 is a 2D vector used to store half-precision 16-bit floating-point components (instead of 32 bits per component), whereas XMSHORT2 is a 2D vector storing half-precision 16-bit integer components. The U after XM for these structures are used to specify that they are unsigned, and the N that follows the number of components at the end of the names represents that they store normalized values. Therefore the appearance ofa U and N represents an unsigned normalized structure.

The next list displays all structures that are based on three components:

  • XMFLOAT3

  • XMFLOAT3A

  • XMFLOAT3PK

  • XMFLOAT3SE

  • XMHEND3

  • XMHENDN3

  • XMDHEN3

  • XMDHENN3

  • XMU555

  • XMU565

  • XMUDHEN3

  • XMUDHENN3

  • XMUHEND3

  • XMUHENDN3

XMFLOAT3, which was mentioned in the “Vectors” section earlier in this chapter, is a three-component structure of floating-point values. XMFLOAT3A is used for 16-byte aligned three-component vectors. XMFLOAT3PK is used for three-component vectors, where the X and Y components are 10 bits each, and the Z is 11 bits, whereas XMFLOAT3SE is a vector where the components use nine bits for the mantissa and five bits for the exponent.

XMDHEN3 (and its normalized form XMDHENN3) are 3D integer vectors with 10 bits for the X components and 11 bits for the Y and Z. The XMHEND3 and XMHENDN3 are used to store 11 bits for the X and Y and 10 bits for the Z components. The unsigned versions of these are XMUDHEN3 and XMUDHENN3.

The XMU555 is used to create a three-component vector that uses five bits for each component. The XMU565 creates a three-component vector that uses five bits for the X and Z components but uses six bits for the Y.

The next list displays the 4D vector structures in the XNA Math library:

  • XMFLOAT4

  • XMFLOAT4A

  • XMHALF4

  • XMICO4

  • XMICON4

  • XMSHORT4

  • XMSHORTN4

  • XMUBYTE4

  • XMUBYTEN4

  • XMUDEC4

  • XMUDECN4

  • XMUICO4

  • XMUICON4

  • XMUNIBBLE4

  • XMUSHORT4

  • XMUSHORTN4

  • XMXDEC4

  • XMXDECN4

  • XMXICO4

  • XMXICON4

XMFLOAT4 is a four-component floating-point vector that was mentioned in the “Vectors” section of this chapter. XMHALF4 is a half-precision 4D vector, and XMSHORT4 is half-precision using short integers. Again, the A that follows the structure names is for byte-aligned structures, while N is for normalized versions of them.

XMUBYTE4 is for a four-component unsigned byte (char) structure, and XMUNIB-BLE4 uses four 4-bit integer components. The structures with ICO in their names use 20 bits for the X, Y, and Z components while using four bits for the W, which totals 64 bits for the ICO structures. The structures with DEC in their names use 10 bits for the X, Y, and Z components and two bits for the W, which totals 32 bits for the DEC structures. The XICO and XDEC structures use signed values, whereas UICO and UDEC use unsigned values.

The next list of structures is related to matrices:

  • XMFLOAT3x3

  • XMFLOAT4x3

  • XMFLOAT4x3A

  • XMFLOAT4x4

  • XMFLOAT4x4A

  • XMMATRIX

XMFLOAT3x3 represents a 3 × 3 matrix of floating-point values (9 in total) and is usually used to represent rotation matrices. XMFLOAT4x3 represents 4 × 3 matrices (12 elements in total), and XMFLOAT4x4 represents 4 × 4 matrices (16 elements in total). The A that follows the structure names indicates that they are aligned structures. XMMATRIX is a row-major 4 × 4 byte-aligned matrix that maps to four vector hardware registers.

The final list that we’ll look at includes the additional types available using XNA Math. These additional types are listed as follows:

  • HALF

  • XMVECTOR

  • XMVECTORF32

  • XMVECTORI32

  • XMVECTORU32

  • XMVECTORU8

The HALF data type, which is aliased by USHORT, is a 16-bit floating-point number. It uses five bits for the exponent and 10 for the mantissa and has a sign bit.

The XMVECTOR structure is a four-component structure where each component is represented by 32 bits. This structure can use floating-point numbers or integers and is also optimally aligned and mapped to a hardware vector register. XMVECTOR was discussed in the “Vectors” section of this chapter.

The XMVECTORF32 is a portable 32-bit floating-point structure used by C++ initializer syntax to initialize a XMVECTOR structure to use floating-point values.

The XMVECTORI32 is a portable 32-bit integer structure used by C++ initializer syntax to initialize a XMVECTOR structure to use integer values.

The XMVECTORU32 is a portable structure used by C++ initializer syntax to initialize a XMVECTOR structure to use unsigned integer values (UINT).

The XMVECTORI32 is a portable 8-bit structure used by C++ initializer syntax to initialize a XMVECTOR structure to use byte values (char ).

Additional Functions

In this section we will briefly discuss the additional functions available through XNA Math that we have yet to touch upon. These functions deal with colors, conversions, and scalars. The first type we’ll discuss is the color functions.

Color Functions

XNA Math has several functions useful for performing color manipulation on data. The first is used to adjust the contrast of a single color and is called XMColorAdjustContrast, whose prototype can be seen as follows:

XMVECTORXMColorAdjustContrast( XMVECTOR C, FLOAT Contrast );

The first parameter of the XMColorAdjustContrast function is an XMVECTOR that represents a color value, where each component is to be in the range of 0.0 to 1.0f. If the contrast is 0.0f, then the color is adjusted by 50% gray; if the contrast is 1.0f, then the full color is returned. Anything between the two extremes will return a contrasted color.

The next function is called XMColorAdjustSaturation, and it is used to adjust the saturation of a color value. Its prototype is:

XMVECTORXMColorAdjustSaturation( XMVECTOR C, FLOAT Saturation );

The first parameter of the function XMColorAdjustSaturation is also a XMVECTOR that represents a color value, where each component is to be in the range of 0.0 to 1.0f. If the saturation is 0.0f, then a gray color is returned; if the saturation is 1.0f, then the full color is returned. Anything between 0.0 and 1.0 for the saturation will return a value that linearly interpolates between the two (i.e., gray and the original color).

The next six functions are comparison functions. These functions include testing if two vectors are equal, not equal, greater than, less than, greater than or equal to, or less than or equal to. The functions test the first parameter with the second and return a true/false result. Each of the comparison functions can be seen as follows:

BOOL XMColorEqual( XMVECTOR C1, XMVECTOR C2 );
BOOL XMColorNotEqual( XMVECTOR C1, XMVECTOR C2 );

BOOL XMColorGreater( XMVECTOR C1, XMVECTOR C2 );
BOOL XMColorGreaterOrEqual( XMVECTOR C1, XMVECTOR C2 );

BOOL XMColorLess( XMVECTOR C1, XMVECTOR C2 );
BOOL XMColorLessOrEqual( XMVECTOR C1, XMVECTOR C2 );

Another two functions that deal with comparison are XMColorIsInfinite, which tests if a color is equal to infinity, and XMColorIsNaN which looks to see if one of the components is defined as NaN (not a number).

BOOL XMColorIsInfinite( XMVECTOR C );
BOOL XMColorIsNaN( XMVECTOR C );

The next function is called XMColorModulate, and it is used to blend two colors together. The last color function is XMColorNegative, which is used to negate a color. The function prototypes for these last two color functions are:

XMVECTOR XMColorModulate( XMVECTOR C1, XMVECTOR C2 );
XMVECTOR XMColorNegative( XMVECTOR C );

The XMColorNegative function is similar to performing the following:

XMVECTOR col, result;

result.x = 1.0f - col.x;
result.y = 1.0f - col.y;
result.z = 1.0f - col.z;
result.w = 1.0f - col.w;

A black color component would become white using XMColorNegative, while white would become black. The same goes for intensities between 0.0 and 1.0, where their intensity of light and dark will be swapped.

Conversion Functions

The conversion set of XNA Math functions is used to convert values from one type to another. The XMConvertFloatToHalf function, the prototype of which can be seen below, converts a 32-bit floating-point value to a HALF (half-precision). All floating-point exceptions such as NaN, negative and positive infinity, etc., are converted to the value of 0x7FFF.

HALF XMConvertFloatToHalf FLOAT Value );

To convert a HALF back to a floating-point value, we could use the following function:

FLOAT XMConvertHalfToFloat( HALF Value );

Values passed to XMConvertHalfToFloat that equal 0x7FFF are converted to 131008.0f, according to the XNA Math documentation.

If an array of values needs to be converted, we can use the XMConvertFloatTo-HalfStream and XMConvertHalfToFloatStream. These functions take as parameters the address of the first value in the output stream array, the stride in bytes between each output value, the address ofthe first element in the input array, the stride in bytes between each input value, and the total number of elements we are converting. Both of these functions also return the first address of the output stream, and their prototypes can be seen as follows:

HALF* XMConvertFloatToHalfStream( HALF* pOutputStream, UINT OutputStride,
    CONST FLOAT* pInputStream, UINT InputStride, UINT FloatCount );

FLOAT* XMConvertHalfToFloatStream( FLOAT *pOutputStream, UINT OutputStride,
    CONST HALF* pInputStream, UINT InputStride, UINT HalfCount );

Another set of useful functions is those dedicated to converting degrees to radians and vice-versa. In games we often use one or the other, and sometimes it might be convenient to take one representation and adopt it to another. The functions to perform this conversion are XMConvertToDegrees and XMConvert-ToRadians, both of which take a floating-point value as a parameter and return the result as a floating-point value. Their prototypes are the following:

FLOAT XMConvertToDegrees( FLOAT fRadians );
FLOAT XMConvertToRadians( FLOAT fDegrees );

The last conversion functions are used to convert a float vector to an integer vector and vice-versa while performing a uniform bias. XMConvertVectorIntToFloat and XMConvertVectorUIntToFloat will perform the conversion of the first parameter and divide the results by the two raised to the DivExponent power. XMConvertVec-torFloatToInt and XMConvertVectorFloatToUInt will do something similar but will multiply the two to the MulExponent power. Their prototypes can be seen as follows:

XMVECTOR XMConvertVectorFloatToInt( XMVECTOR VFloat, UINT MulExponent );
XMVECTOR XMConvertVectorFloatToUInt( XMVECTOR VFloat, UINT MulExponent );

XMVECTOR XMConvertVectorIntToFloat( XMVECTOR VInt, UINT DivExponent );
XMVECTOR XMConvertVectorUIntToFloat( XMVECTOR VUInt, UINT DivExponent );

Scalar Functions

Next are the scalar functions. These functions take floating-point values and perform a specific operation on them. There are functions for calculating the cosine of a value, such as XMScalarCos, functions for calculating the sine of a value, such as XMScalarSin, functions to calculate the arccosine (XMScalarACos) and arcsine (XMScalarASin), and functions to calculate the estimation of these values. The function prototypes of these functions are as follows:

FLOAT XMScalarCos( FLOAT Value );
FLOAT XMScalarCosEst( FLOAT Value );

FLOAT XMScalarSin( FLOAT Value );
FLOAT XMScalarSinEst( FLOAT Value );

FLOAT XMScalarACos( FLOAT Value );
FLOAT XMScalarACosEst( FLOAT Value );

FLOAT XMScalarASin( FLOAT Value );
FLOAT XMScalarASinEst( FLOAT Value );

FLOAT XMScalarSinCos( FLOAT Value );
FLOAT XMScalarSinCosEst( FLOAT Value );

In addition to the trigonometry functions, we also have XMScalarModAngle and XMScalarNearEqual. XMScalarModAngle will compute an angle between -PI and +PI that is congruent to Value modulo 2PI. XMScalarNearEqual will test two values to see if they are close to being equal. Both function prototypes can be seen as follows:

FLOAT XMScalarModAngle( FLOAT Value );
BOOL XMScalarNearEqual( FLOAT S1, FLOAT S2, FLOAT Epsilon )

Additional Math Structures and Topics

XNA Math has support for colors, vectors, matrices, planes, and quaternions. All of these can be represented using 4D vectors, where a 4 × 4 matrix is four vectors representing each row. Planes and quaternions touch upon more advanced concepts that we won’t get to explore in this book, but XNA Math has functions to work with them.

A plane can be represented using a 4D vector. A plane is an infinitely small surface that extends along two axes infinitely. A plane is defined simply as a normal vector that specifies the direction the plane is facing (X, Y, and Z) and a distance value (stored in W) that represents how far from the origin this plane is.

Planes are commonly used for culling and collision detection, trigger volumes, and other concepts along those lines. We do not render planes like we do triangles, points, and lines.

Unit quaternions can be used to represent rotations and orientations. A quaternion has four floating-point members, so therefore a 4D vector such as XMFLOAT4 or XMVECTOR can be used as a quaternion.

Game Physics and Collision Detection

Everything in this chapter is related not only to graphics but also to game physics. Physics is a huge part of 3D game development and cannot be avoided when creating realistic simulations. Physics in games usually deals with point masses, rigid bodies, and soft bodies.

Point masses are physics objects that have their linear velocities and positions updated but have no applied rotations. These are often the easiest to start with for beginners and are common for many simulations in games, including particle systems.

Rigid bodies are often used for objects in the scene that can have forces acting upon them that causes them to not only have forces applied to their position and velocity but also their angular movements. This is used for things like crates and other objects as they react to explosions and other forces that cause them to realistically move throughout the scene by moving and rotating as they interact with the world. Another example can be seen with rag-doll bodies, where enemies can fall down a flight of stairs realistically as they collide with the environment. Rigid bodies do not deform in shape.

Soft bodies are not used as often as the other two but are used to simulate objects that can deform in shape. Epic’s Unreal game engine has support for soft bodies.

Collision detection and response is the act of detecting when one or more objects collide and resolving those collisions by applying forces on them or restricting their movements. This is done so that objects never penetrate one another and so that they react as you would expect them to when they touch. Physics in games is applying forces on objects so that they behave as designed within the environment. Collision detection allows us to catch objects that start to penetrate each other as a result of these forces acting upon them, and the response to collision allows us to deal with that collision realistically to add further to the simulation. Although they can go hand in hand, physics and collision detection are separate subjects that meet to create the complete simulation the designers intended.

Summary

Math is an important topic in video game development. XNA Math, which is part of the DirectX 11 SDK, is a huge library of optimized code that we have at our disposal. In this chapter we mainly covered the topics in XNA Math that were important for this book, but the library itself is made up of hundreds of structures and functions. It is a huge library and is definitely a very useful tool to have at our disposal. When it is used properly, you can obtain great performance using XNA Math.

What You Have Learned

  • Learned about vectors

  • Learned about matrices

  • Learned about coordinate systems and transformations

  • Learned about the various functions and structures available through XNA Math

  • Learned about projections

Chapter Questions

You can find the answers to the chapter review questions in Appendix A on this book’s companion website.

1.

Define a vector.

2.

Define a matrix.

3.

What is the difference between XMVECTOR and XMFLOAT4?

4.

What is the difference between XMFLOAT4 and XMFLOAT4A?

5.

How is a point defined in a 3D coordinate system?

6.

What does normalizing a vector do?

7.

What is the purpose of the dot product calculation?

8.

What primitive type consists of a series of connected lines?

9.

What is the identity matrix?

10.

What is another term for multiplying matrices?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset