Welcome to the second tutorial of GPU Notes. As we’ve mentioned in the last note, perhaps the most important core of any graphic or game engine is math; so we are going to cover some essential math for graphics programming in the next following tutorials. Before getting started, keep in mind that they are already a number of c/c++ math libraries or headers out there, which you can integrate into your engine (for instance Intel Math Library, GLM, DirectXMath, Boost Math, etc).

To cover all the bases, first we are going to review some essential math and then we will see how to apply them in implementing our GPU rendering algorithms. I think it is safe to assume that everyone here has a good knowledge about memory types in c/c++ (such as floating-point numbers, integers, etc). So let’s start with the basic: the Vectors.

Vector

The first thing to discuss is how we are going to use vectors and then we will see how we can manipulate them in GPU APIs. As shown below, a vector can contain geometric data.

The length of a vector represents its magnitude and the arrowhead indicates its direction. Calling a vector “normalized” means the magnitude of the vector is 1.

So what is the usage of vectors in game programming, you might ask. The answer is rather simple: Have you ever played Pro Evolution Soccer? When you press L2 (PlayStation) or LT (on Xbox) button on your gamepad, the direction of your chosen player, the one who controls the ball, is shown with an arrow vector (it is called player’s direction).

Another usage of vector in games is calculating direction of lights or shadows. Each vector is represented by at least two floating numbers (most of the times referred to as “x” and “y”). The structure is shown below:

  struct vec2
  {
      float x;
      float y;
  }
  

You may also came across the following types: vector3, vector4 in graphic programming that contains more than two floating numbers, for example in 3D coordinate system, each 3D object has a position which is presented as a vector3 type.

In symbolic mathematics we can represent any vector in 3D space by using the specific linear combination with the following equation:

  v = xi + yj + zk

The x, y, and z are the amount of movements of vector in the i, j, and k directions, but we just store the vector’s x, y and z. Any arithmetic operation can be applied on vectors, for example if we add two 3D vectors v0 and v1 together and expand and rearrange terms, the result is:

  v0 + v1 = (x0*i + y0*j + z0*k) + (x1*i + y1*j + z1*k) = x0*i + x1*i + y0*j + y1*j + z0*k + z1*k=(x0+x1)i+(y0+y1)j+(z0+z1)k

If we remove i, j, and k to create an ordered triple, the result is the sum of two vectors. Then we can take each component as xyz order:

  (x0, y0 , z0) + (x1, y1, z1) = (x0 + x1, y0 + y1, z0 + z1)

Further, scalar multiplication works just the same:

  a*v = a(xi + yj + zk) = a(xi) + a(yj) + a(zk) = (ax)i + (ay)j + (az)k

and taking out i, j, and k gives us:

  a(x, y, z) = (ax, ay, az)

As I have mentioned before, a vector is an entity with length and direction, since x is the distance along i and y is the distance along j and z is the distance along k; thus the length of v is:

  |v| =  √( xˆ2 + yˆ2 )

Take a look at the following image to have a better understanding of the length of vector2:

As mentioned before the “normalized vector” means the magnitude of vector is equal to 1, so we can calculate the “normalized vector” by dividing the vector to its magnitude, or in mathematical terms:

  V nom = v / |v|

The direction of vector presented by a function is called the “dot product”, although not commonly used, it is also known as the “Euclidean inner product”. It is probably the most useful vector operation for graphic programming.

Given two vectors v and w with an angle θ between them, then the dot (v·w) is:

  v·w =  |v| * |w| * cos(θ)

If two vectors have the same direction, then multiplying lengths of two vectors will lead us to dot product, however when directions are not the same, then multiplying by cos(θ) is necessary.

Suppose we have two vectors, v and w, and we want to find a new vector u orthogonal for both. Such operation is called “cross product”, also known as the “vector product”. This can be computed using the following formula:

  v × w = |v| * |w| * sin(θ)

Unlike dot product, cross product is only specified to three-dimensional vectors and the result of cross product is a vector as opposed to the result of a dot product, which is a scalar.

Like dot product, cross product can also be used to determine if two vectors are parallel by checking whether the resulting vector is close to zero. The cross product will be calculated as the following:

  vec3 cross( const vec3& a,  const vec3& b)
  {
      return vec3( a.y * b.z - a.z * b.y,
                   a.z * b.x - a.x * b.z,
                   a.x * b.y - a.y * b.x);
  } 

Thus far, we’ve looked at vectors and some other functional details more closely. Needless to say, this is very important for graphic programming. In our next tutorial we will take a look at 3D coordinate systems. So, as always, stay tuned!

Thanks for reading this note.