Introduction to 3D Game Programming with DirectX.9.0 - F. D. Luna
.pdf
4 Part I
We usually denote a vector in lowercase bold but sometimes in uppercase bold as well. Examples of two-, three-, and four-dimensional
vectors, respectively: u = (ux, uy), N = (Nx, Ny, Nz), c = (cx, cy, cz, cw). We now introduce four special 3D vectors, which are illustrated in
Figure 4. The first is called the zero vector and has zeros for all of its components; it is denoted by a bold zero: 0 = (0, 0, 0). The next three special vectors are referred to as the standard basis vectors for 3.
These vectors, called the i, j, and k vectors, run along the x-, y-, and |
||||
|
|
|
|
Y |
z-axis of our coordinate system, respectively, and have a magnitude of |
||||
|
|
|
L |
|
one: i = (1, 0, 0), j = (0, 1, 0), and k = (0, 0, 1). |
||||
|
|
|
F |
|
Note: A vector with a magnitude of one is called a unit vector. |
||||
|
|
|
M |
|
|
|
|
|
|
|
|
|
A |
|
|
|
E |
|
|
|
|
|
Figure 4: The zero vector and the standard basis vectors |
|
|
|
|
for 3 |
|
|
In the D3DXTlibrary, we can use the D3DXVECTOR3 class to represent a |
|||
vector in 3-space. Its class definition is:
typedef struct D3DXVECTOR3 : public D3DVECTOR { public:
D3DXVECTOR3() {}; D3DXVECTOR3( CONST FLOAT * );
D3DXVECTOR3( CONST D3DVECTOR& ); D3DXVECTOR3( FLOAT x, FLOAT y, FLOAT z );
//casting operator FLOAT* ();
operator CONST FLOAT* () const;
//assignment operators
D3DXVECTOR3& operator += ( CONST D3DXVECTOR3& );
D3DXVECTOR3& operator -= ( CONST D3DXVECTOR3& );
D3DXVECTOR3& operator *= ( FLOAT );
D3DXVECTOR3& operator /= ( FLOAT );
// unary operators
D3DXVECTOR3 operator + () const; D3DXVECTOR3 operator - () const;
// binary operators
D3DXVECTOR3 operator + ( CONST D3DXVECTOR3& ) const; D3DXVECTOR3 operator - ( CONST D3DXVECTOR3& ) const; D3DXVECTOR3 operator * ( FLOAT ) const;
D3DXVECTOR3 operator / ( FLOAT ) const;
Team-Fly®
Mathematical Prerequisites
friend D3DXVECTOR3 operator * ( FLOAT,
CONST struct D3DXVECTOR3& );
BOOL operator == ( CONST D3DXVECTOR3& ) const;
BOOL operator != ( CONST D3DXVECTOR3& ) const;
} D3DXVECTOR3, *LPD3DXVECTOR3;
Note that D3DXVECTOR3 inherits its component data from D3DVECTOR, which is defined as:
typedef struct _D3DVECTOR { float x;
float y; float z; } D3DVECTOR;
Like scalar quantities, vectors have their own arithmetic, as you can see from the mathematical operations that the D3DXVECTOR3 class defines. Presently you are not expected to know what these methods do. The following subsections introduce these vector operations as well as some other D3DX vector utility functions and other important details about vectors.
5
P a r t I
Note: Although we are primarily concerned with 3D vectors, we sometimes work with 2D and 4D vectors in 3D graphics programming. The D3DX library provides a D3DXVECTOR2 and D3DXVECTOR4 class for representing 2D and 4D vectors, respectively. Vectors of different dimensions have the same properties as 3D vectors, namely they describe magnitudes and directions, only in different dimensions. In addition, the mathematical operations of vectors can be generalized to any dimensional vector with the exception of the vector cross product (see the section titled “Cross Products”), which is only defined in 3. Thus, with the exception of the cross product, the operations we discuss for 3D vectors carry over to 2D, 4D, and even n-dimensional vectors.
Vector Equality
Geometrically, two vectors are equal if they point in the same direction and have the same length. Algebraically, we say they are equal if they are of the same dimension and their corresponding components are
equal. For example, (ux, uy, uz) = (vx, vy, vz) if ux = vx, uy = vy, and uz = vz. In code we can test if two vectors are equal using the overloaded
equals operator:
D3DXVECTOR u(1.0f, 0.0f, 1.0f);
D3DXVECTOR v(0.0f, 1.0f, 0.0f);
if( u == v ) return true;
6 Part I
Similarly, we can test if two vectors are not equal using the overloaded not equals operator:
if( u != v ) return true;
Note: When comparing floating-point numbers, care must be taken because, due to floating-point imprecision, two floating-point numbers that we expect to be equal may differ slightly; therefore, we test if they are approximately equal. We do this by defining an EPSILON constant, which is a very small value that we use as a “buffer.” We say two values are approximately equal if their distance is less than EPSILON. In other words, EPSILON gives us some tolerance for floating-point imprecision. The following function illustrates how EPSILON can be used to test if two floating-point values are equal:
const float EPSILON = 0.001f; bool Equals(float lhs, float rhs)
{
// if lhs == rhs their difference should be zero return fabs(lhs - rhs) < EPSILON ? true : false;
}
We do not have to worry about doing this when using the D3DXVECTOR3 class, as its overloaded comparison operations will do this for us, but comparing floating-point numbers properly is important to know in general.
Computing the Magnitude of a Vector
Geometrically, the magnitude of a vector is the length of the directed line segment. Given the components of a vector, we can algebraically compute its magnitude with the following formula:
(1)u
ux 2 uy 2 uz 2
The double vertical bars in 
u
denotes the magnitude of u.
Example: Find the magnitude of the vectors u = (1, 2, 3) and v = (1, 1).
Solution: For u we have:
(2)u
12 22 32
1 4 9
14
Generalizing formula (1) to two dimensions, for v we have:
(3)v
12 12
2
Using the D3DX library, we can compute the magnitude of a vector using the following function:
Mathematical Prerequisites
FLOAT D3DXVec3Length( // Returns the magnitude.
CONST D3DXVECTOR3* pV // The vector to compute the length of.
);
D3DXVECTOR3 v(1.0f, 2.0f, 3.0f);
float magnitude = D3DXVec3Length( &v ); // = sqrt(14)
7
P a r t I
Normalizing a Vector
Normalizing a vector makes a vector’s magnitude equal to one, which is called a unit vector. We can normalize a vector by dividing each component by the vector’s magnitude, as shown here:
u |
u |
|
u |
|
, |
uy |
, |
u |
|
|
|
|
|||
|
|
|
|
|
x |
|
|
z |
|
|
|||||
ˆ |
u |
|
|
|
|
u |
|
u |
|
u |
|
|
|
||
|
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
||||
We denote a unit vector by putting a hat over it: û.
Example: Normalize the vectors u = (1, 2, 3) and v = (1, 1).
Solution: From equations (2) and (3) we have 
u


14 and 
v


2, so:
|
u |
|
1 |
|
2 |
|
3 |
||||||
uˆ |
|
|
|
|
|
, |
|
|
|
, |
|
|
|
14 |
14 |
14 |
|
||||||||||
|
|
|
|
14 |
|||||||||
|
v |
|
1 |
|
|
1 |
|
|
|||||
ˆv |
|
|
|
|
|
, |
|
|
|
|
|
|
|
2 |
|
|
2 |
|
|
|
|
|
|||||
|
|
|
|
|
|
2 |
|
|
|||||
Using the D3DX library, we can normalize a vector using the following function:
D3DXVECTOR3 *D3DXVec3Normalize(
D3DXVECTOR3* pOut, |
// Result. |
CONST D3DXVECTOR3* pV // The vector to normalize.
);
Note: This function returns a pointer to the result so that it can be passed as a parameter to another function. For the most part, unless otherwise stated, a D3DX math function returns a pointer to the result. We will not explicitly say this for every function.
Vector Addition
We can add two vectors by adding their corresponding components together; note that the vectors being added must be of the same dimension:
u v (ux vx , uy vy , uz vz )
8 Part I
Figure 5 illustrates the geometric interpretation of vector addition.
Figure 5: Vector addition. Notice how we translate v parallel to itself so that its tail coincides with the head of u; then the sum is the vector originating at the tail of u and ending at the head of the translated v.
To add two vectors in code, we use the overloaded addition operator:
D3DXVECTOR3 u(2.0f, 0.0f, 1.0f);
D3DXVECTOR3 v(0.0f, -1.0f, 5.0f);
// (2.0 + 0.0, 0.0 + (-1.0), 1.0 + 5.0) D3DXVECTOR3 sum = u + v; // = (2.0f, -1.0f, 6.0f)
Vector Subtraction
Similar to addition, two vectors are subtracted by subtracting their corresponding components. Again, the vectors must be of the same dimension.
u v u v (ux vx , uy vy , uz vz )
Figure 6 illustrates the geometric interpretation of vector subtraction.
Figure 6: Vector subtraction
To subtract two vectors in code, we use the overloaded subtraction operator:
D3DXVECTOR3 u(2.0f, 0.0f, 1.0f);
D3DXVECTOR3 v(0.0f, -1.0f, 5.0f);
D3DXVECTOR3 difference = u - v; // = (2.0f, 1.0f, -4.0f)
Mathematical Prerequisites
As Figure 6 shows, vector subtraction returns a vector from the head of v to the head of u. If we interpret the components of u and v as the coordinates of points, we can use vector subtraction to find the vector from one point to another. This is a very convenient operation because we will often want to find the vector describing the direction from one point to another.
9
P a r t I
Scalar Multiplication
We can multiply a vector by a scalar, as the name suggests, and this scales the vector. This operation leaves the direction of the vector unchanged, unless we scale by a negative number, in which case the direction is flipped (inverted).
ku kux , kuy , kuz
The D3DXVECTOR3 class provides a scalar multiplication operator:
D3DXVECTOR3 u(1.0f, 1.0f, -1.0f);
D3DXVECTOR3 scaledVec = u * 10.0f; // = (10.0f, 10.0f, -10.0f)
Dot Products
The dot product is one of two products that vector algebra defines. It is computed as follows:
u v ux v x uy vy uz v z s
The above formula does not present an obvious geometric meaning. Using the law of cosines, we can find the relationship
u v 
u


v
cos , which says that the dot product between two vectors is the cosine of the angle between them scaled by the vectors’ magnitudes. Thus, if both u and v are unit vectors, then u · v is the cosine of the angle between them.
Some useful properties of the dot product:
If u · v = 0, then u v.
If u · v > 0, then the angle, , between the two vectors is less than 90 degrees.
If u · v < 0, then the angle, , between the two vectors is greater than 90 degrees.
Note: The symbol means “orthogonal,” which is synonymous with the term “perpendicular.”
10 Part I
We use the following D3DX function to compute the dot product between two vectors:
FLOAT D3DXVec3Dot( |
// Returns the result. |
CONST D3DXVECTOR3* pV1, |
// Left sided operand. |
CONST D3DXVECTOR3* pV2 |
// Right sided operand. |
); |
|
D3DXVECTOR3 u(1.0f, -1.0f, 0.0f);
D3DXVECTOR3 v(3.0f, 2.0f, 1.0f);
//1.0*3.0 + -1.0*2.0 + 0.0*1.0
//= 3.0 + -2.0
float dot = D3DXVec3Dot( &u, &v ); // = 1.0
Cross Products
The second form of multiplication that vector math defines is the cross product. Unlike the dot product, which evaluates to a scalar, the cross product evaluates to another vector. Taking the cross product of two vectors, u and v, yields another vector, p, that is mutually orthogonal to u and v. By that we mean p is orthogonal to u, and p is orthogonal to v. The cross product is computed like so:
p u v [(uy vz uz vy ), (uz vx ux vz ), (ux vy uy vx )]
In component form:
px (uy vz uz vy )
py (uz v x ux v z )
pz (ux vy uy v x )
Figure 7: Cross product. The vector u v = p is orthogonal to both u and v.
Example: Find j = k i = (0, 0, 1) (1, 0, 0) and verify that j is orthogonal to both k and i.
Mathematical Prerequisites 11
Solution:
|
I |
jx (0 0 1 0 ) 0 |
rt |
jy (11 0 0 ) 1 |
Pa |
|
|
jz (0 0 0 1 ) 0 |
|
So, j = (0, 1, 0). Recall from the section titled “Dot Products” that if |
|
u · v = 0, then u v. Since j · k = 0 and j · i = 0, we know j is orthogo- |
|
nal to both k and i. |
|
We use the following D3DX function to compute the cross product |
|
between two vectors: |
|
D3DXVECTOR3 *D3DXVec3Cross( D3DXVECTOR3* pOut, // Result.
CONST D3DXVECTOR3* pV1, // Left sided operand. CONST D3DXVECTOR3* pV2 // Right sided operand.
);
It is obvious from Figure 7 that the vector –p is also mutually orthogonal to both u and v. The order in which we perform the cross product determines whether we get p or –p as a result. In other words, u v = –(v u). This shows that the cross product is not commutative. You can determine the vector returned by the cross product by the left hand thumb rule. (We use a left hand rule because we are using a left-handed coordinate system. We would switch to the right hand rule if we were using a right-handed coordinate system.) If you curve the fingers of your left hand in the direction of the first vector toward the second vector, your thumb points in the direction of the returned vector.
Matrices
In this section we concentrate on the mathematics of matrices. Their applications to 3D graphics are explained in the next section.
An m n matrix is a rectangular array of numbers with m rows and n columns. The number of rows and columns give the dimension of the matrix. We identify a matrix entry by specifying the row and column that it is in using a double subscript, where the first subscript identifies the row and the second subscript identifies the column. Examples of a 33 matrix M, a 2 4 matrix B, and a 3 2 matrix C follow:
|
m11 |
m12 |
m13 |
|
|
b11 |
b12 |
b13 |
b14 |
|
|
c11 |
c12 |
|
||||
M |
|
m m m |
|
B |
C |
|
c |
|
c |
|
|
|||||||
|
|
|
|
|
|
|
|
21 |
22 |
|
||||||||
|
21 |
22 |
23 |
|
b22 |
b23 |
|
|
|
|
||||||||
|
m |
m |
m |
|
|
b21 |
b24 |
|
c |
31 |
c |
32 |
|
|||||
|
|
31 |
32 |
33 |
|
|
|
|
|
|
|
|
|
|
|
|
||
12 Part I
We generally use uppercase bold letters to denote matrices. Sometimes a matrix will contain a single row or column. We give
the special names row vector and column vector to describe such matrices. Examples of a row and column vector follow:
ux v v1, v2 , v3 , v4 u uy
uz
When using row or column vectors, we only need a single subscript, and sometimes we use letters as the subscripts used to identify an entry in the row or column.
Equality, Scalar Multiplication, and Addition
Refer to the following four matrices throughout this subsection:
1 |
5 |
6 |
2 |
1 |
5 |
1 2 |
1 3 |
||||||
A |
2 |
|
B |
|
8 |
|
C |
2 |
|
D |
6 3 |
|
|
|
|
|
5 |
|
|
|
|
0 0 |
|
||||
|
3 |
|
|
|
3 |
|
|
||||||
Two matrices are equal if they are of the same dimension and their corresponding entries are equal. For example, A = C because A
and C have the same dimension and their corresponding entries are equal. We note that A B and A D because either the corresponding entries are not equal or the matrices are of different dimensions.
We can multiply a matrix by a scalar by multiplying each entry of the matrix by the scalar. For example, multiplying D by the scalar k gives:
k 1 |
|
k 2 k 1 k 3 |
|
|
|
|
|
|||||
kD |
k 6 |
k 3 |
k 0 |
k 0 |
|
|
|
|
|
|
||
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
If k = 2, we have: |
|
|
|
|
|
|
|
|
||||
|
|
2 1 |
2 2 2 1 2 3 2 4 |
2 6 |
||||||||
kD 2D |
|
2 6 |
2 3 |
2 0 |
2 0 |
|
|
12 6 |
0 0 |
|
||
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|||
Two matrices can be added only if they are of the same dimension. The sum is found by adding the corresponding entries of the two matrices together. For example:
1 |
5 |
6 |
2 |
1 6 |
5 2 |
7 7 |
||||||||
A B |
2 |
|
|
5 |
8 |
|
|
2 |
|
5 3 8 |
|
|
3 |
|
|
3 |
|
|
|
|
|
5 |
|||||||
|
|
|
|
|
|
|
|
|
|
|||||
Mathematical Prerequisites
As with addition, in order to be able to subtract two matrices, they must have the same dimensions. Matrix subtraction is illustrated by the following example:
1 |
5 |
6 |
2 |
1 |
5 |
6 |
2 |
1 6 5 2 |
5 3 |
|||||||||||
A B A B |
2 |
|
|
|
8 |
|
|
2 |
|
|
5 |
|
|
|
2 |
|
5 3 8 |
|
|
|
|
|
|
5 |
|
|
|
|
8 |
|
|
|
|
|
|||||||
|
3 |
|
|
|
3 |
|
|
|
|
|
7 11 |
|||||||||
13
P a r t I
Multiplication
Matrix multiplication is the most important operation that we use with matrices in 3D computer graphics. Through matrix multiplication we can transform vectors and combine several transformations together. Transformations are covered in the next section.
In order to take the matrix product AB, the number of columns of A must equal the number of rows of B. If that condition is satisfied, the product is defined. Consider the following two matrices, A and B, of dimensions 2 3 and 3 3, respectively:
a11 |
|
|
|
|
b11 |
b12 |
b13 |
|
a12 |
a13 |
B |
|
b22 |
|
|
||
A |
|
|
|
b21 |
b23 |
|||
a21 |
a22 |
a23 |
|
b |
b |
b |
|
|
|
|
|
|
|
31 |
32 |
33 |
|
We see that the product AB is defined because the number of columns of A equals the number of rows of B. Note that the product BA, found by switching the order of multiplication, is not defined because the number of columns of B does not equal the number of rows of A. This suggests that matrix multiplication is generally not commutative (that is, AB BA). We say “generally not commutative” because there are some instances where matrix multiplication does work out to be commutative.
Now that we know when matrix multiplication is defined, we can give its definition as follows: If A is an m n matrix and B is an n p matrix, the product AB is defined and is an m p matrix C, where the ijth entry of the product C is found by taking the dot product of the ith row vector in A with the jth column vector in B:
(4)cij ai b j
where ai denotes the ith row vector in A, and bj denotes the jth column vector in B.
