SDL Programming in Linux: Meeting the Mathematical Prerequisites
Posted On September 21, 2015 by Sneha Latha filed under Enterprise
In other words, before proceeding further, the mathematical prerequisites have to be met as mathematics forms the core of 3-D programming.
Major themes that recur in 3-D graphics are vectors and matrices. It is impossible to implement even a 3-D primitive object without understanding the metrics that represents it. The situation is same in the case of shading and lighting with respect to a given surface. If the vectors perpendicular and parallel to it are not calculated accurately, the shade can become a bit lighter or vice versa.
Hence, the focus of this discussion would be on the geometry and algebra of vectors along with matrices and how they can be used in 3-D transformations. The first section deals with vectors and their algebraic/geometric details. Matrices and their utility in 3-D transformations are discussed in the second section. In the third section, the focus is on how 3-D matrix transformation is implemented to transform various 3-D objects. An example is used to illustrate this.
Grasping the Vectors
Anyone who has experience with DirectX would consider vectors as the basis of any 3-D primitive. However, things are different in OpenGL. In OpenGL, every 3-D object is decomposed into triangles. So, vectors rarely come into play during the creation of objects. And when the context is lighting, then it is all a different ball game. In lighting, vectors play a major role in bringing out the surfaces where ‘light falls’. Thus, to have a good understanding of lighting concepts, a firm grip on vectors and their algebra/geometry is required. So here we go…
The definition of vector states as follows: “It is a straight line segment whose length is its magnitude and whose orientation in space is direction”. In other words, a vector has both magnitude and direction. All operations on vectors involve magnitude and direction. These operations, most of the time, are not done directly on vectors but on their components. Components of a vector are parts of the vector. More specifically, components of a vector are the ones that run parallel to the coordinate axes. For example, a two-dimensional vector has two components – one in the direction of x-axis and another in the direction of y-axis. Pictorially, it would be as seen in figure 1.
In the case of 3-D, there will be one more component in the direction of z-axis. These component vectors have special symbols – i, j and k running along the x-, y- and z- axes, respectively. These are special because of their magnitude – they have a magnitude of one, i.e. i = (1, 0, 0), j = (0, 1, 0) and k = (0, 0, 1). Hence, they are also known as unit vectors.
Now that the basic concepts of vectors have been brushed up, let us move on to the operations on vectors. In the following section, a vector would be denoted by u and its components denoted by ux, uy and uz, which also means u=(ux, uy, uz). Of all the operations that can be performed on vectors, following are the ones that would come handy in OpenGL:
- Computing the magnitude; and
Computing the magnitude
Since a vector is a directed line segment, geometrically, the magnitude of the vector is the length of the line segment. However, the magnitude can also be computed algebraically using the components of the vector with the help of the following formula:
This is the normalized form of u=(1,2,3). That’s all there is to understand vector operations performed by OpenGL to work with OpenGL APIs. The advanced aspects would be introduced in due time.
Understanding MatricesA matrix is a rectangular array of elements set out in rows and columns. This is the common definition of a matrix. If a matrix were to be considered in the context of 3-D graphics, a new way of defining it would come to the fore. Before moving to that, for a second let’s go back to animation. In animation, the position with respect to the origin is changed continuously. This change in position is transformation. Now each of these transformations can be represented in the form of algebraic equations, which can be then solved using matrices. If an object can be transformed in ‘n’ different ways, each transformation can be represented as an equation that can be solved by representing them as matrices; this logic means a matrix can specify a set of n-dimensional transformations. From this, the other definition can be deduced – “A matrix is a representation through which n-dimensional transformations can be specified”. With this etched in our minds, let us move on to the basics of matrices.
OpenGL APIs support six types of matrices implicitly. They are:
- Identity matrix;
- Transpose matrix;
- Inverse matrix;
- Translation matrix;
- Rotation matrix; and
- Scaling matrix.
The last three corresponds to the transformations’ translation, rotation and scaling, respectively.
This is a special matrix. The reason is that it is a square matrix having zeroes for all elements except along the main diagonal. The values along the main diagonal are all ones. For example, following are 2 × 2, 3 × 3 and 4 × 4 identity matrices.
Here, the main diagonal means the diagonal from top left to bottom right. Identity matrix acts as multiplicative identity. That means if M is a 3 × 3 matrix and I is a 3 × 3 identity matrix then,
MI = IM=M
Transpose matrix, or transpose of a matrix, is formed by interchanging the rows and columns of a matrix. Thus, a transpose matrix of m × n matrix is n × m. The transpose of Matrix M is denoted by MT. For example, the transpose of the matrix
The procedure for finding out the inverse of a matrix is currently beyond the scope of this article. Also, just like the other two matrices, inverse is taken care of by OpenGL itself. Even transformation matrices are dealt by OpenGL internally without letting developers worry about it. However, it is always advantageous to know what is being done for us.
A matrix that represents a translated object is a translation matrix. Translation is an Affine transformation, i.e. translation consists of not only translation but also a linear transformation. Only after that, translation occurs. To represent such transformations, homogeneous coordinates have to be used. Homogenous coordinates utilize a mathematical trick to embed three-dimensional coordinates and transformations into a four-dimensional matrix format. Instead of representing each point (x,y,z) in three-dimensional space with a single three-dimensional vector – i.e. [x, y ,z] – homogenous coordinates allow each point (x,y,z) to be represented by any of an infinite number of four-dimensional vectors:
[w*x, w*y, w*z, w]
The three-dimensional vector corresponding to any four-dimensional vector can be computed by dividing the first three elements by the fourth while a four-dimensional vector corresponding to any three-dimensional vector can be created by simply adding a fourth element and setting it equal to one. Here, w is set to w = 1 to allow points to be translated correctly, and w is set to w = 0 to prevent translations on vectors. Hence, the vector (x, y, z, w) becomes (x, y, z, 1).
Now coming back to translation matrix, one can translate the vector (x,y,z,1) px units over x-axis, py over y-axis and pz over z-axis by multiplying it with the following matrix:
One thing to bear in mind is that angles are measured clockwise when looking down the axis of rotation towards the origin.
A vector can be scaled px units over x-axis, py over y-axis and pz over z-axis by multiplying it with the following matrix:
where S(q) is the scaling matrix.
That brings us to the end of this section on matrices. How to combine these transformations would be detailed in the future.
The next section discusses how OpenGL APIs map to these matrices.
Mapping matrices to OpenGL APIs
To demonstrate how matrices are mapped to OpenGL APIs, I would be using a piece of code from the animation related application featured in the last part. Have a good look at code 1.
void Draw3D(SDL_Surface *S) // OpenGL drawing code here
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//clear screen and
//depth buffer. Screen color has been cleared
glRotatef(angle,0.0f,1.0f,0.0f);// Rotate The Triangle On The Y axis
glLoadIdentity(); // reset the modelview matrix
glVertex3f( 0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,-1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
glFlush(); // flush the gl rendering pipelines
The first function that requires our attention is glRotatef(angle, 0.0f,1.0f,0.0f). This function rotates the vertices denoted by vectors according to the angle, specified by angle, and the axis specified by a non-zero number, which in this case is y-axis. The vector is determined by taking the values of the corresponding axes. So, what happens is that each vertex specified by glVertex3f() is multiplied with the rotation matrix for each axis, which are:
In this case, since x- and z- axes are not considered for rotation, only the vector denoting y-axis from the vertices is multiplied, i.e. (1.0,-1.0,-1.0) is multiplied with
to rotate the triangle. Next, look at the function glLoadIdentity(). This method resets the matrix that represents the current view of the model by initializing it to identity matrix. This is done by multiplying the inverse of the current model view matrix with the current model view matrix. Since OpenGL does all this internally, the developer doesn’t need to do all the calculations.
It should by now be clear how OpenGL APIs map transformations matrices!
That brings us to the end of this discussion in which the basics of 3-D mathematics were discussed. From the next part onwards, I will be continuing with OpenGL. The next part will discuss coloring techniques in OpenGL. Till next time…
The author is working as a Project Engineer with the Center for Development of Advanced Computing. You can reach him at: firstname.lastname@example.org.