←Back

OpenGL Matrix Class (C++)

Related Topics: OpenGL Transform, OpenGL Projection Matrix, Quaternion
Download: matrix.zip, matrix_rowmajor.zip

Overview

OpenGL fixed pipeline provides 4 different types of matrices (GL_MODELVIEW, GL_PROJECTION, GL_TEXTURE and GL_COLOR) and transformation routines for these matrices; glLoadIdentity(), glTranslatef(), glRotatef(), glScalef(), glMultMatrixf(), glFrustum() and glOrtho().

These built-in matrices and routines are useful to develop simple OpenGL applications and to understand the matrix transformation. But once your application is getting complecated, it is better to manage your own matrix implementations by yourself for all movable objects. Furthermore, you cannot use these built-in matrix functions anymore in OpenGL programmable pipeline (GLSL) such as OpenGL v3.0+, OpenGL ES v2.0+ and WebGL v1.0+. You must have your own marix implementations then pass the matrix data to OpenGL shaders.

This article provides a stand-alone, general purpose 4x4 matrix class, Matrix4 written in C++, and describes how to integrate this matrix class to the OpenGL applications. This matrix class is only dependent on the sister classes; Vector3 and Vector4 defined Vectors.h. These vector classes are also included in matrix.zip.

Matrix4 Creation & Initialization

Column-major order Matrix
Matrix4 uses column-major

Matrix4 class contains an array of float data type to store 16 elements of 4x4 square matrix, and has 3 constructors to instantiate a Matrix4 class object.

Matrix4 class uses the column-major order notation same as OpenGL uses. (Array elements are filled in the first column first and move on the second column.) Note that row-major and column-major order are just different ways to store multi-dimensional arrays into a linear (1D) memory, and the results of matrix arithmetic and operations are no difference.

With default constructor (with no argument), Matrix4 object is created as an identity matrix. Other 2 constructors take either 16 arguments or an arry of 16 elements. You may also use the copy constructor and assignment operator (=) to initialize a Matrix4 object.

By the way, the copy constructor and assignment operator will be automatically generated by C++ compiler for you.

Here are example codes to construct Matrix4 objects in various ways. First, include Matrices.h in your code to use Matrix4 class.


#include "Matrices.h"   // for Matrix2, Matrix3, Matrix4
...

// create an identity matrix with default ctor
Matrix4 m1;

// create a matrix with 16 elements
Matrix4 m2(1, 1, 1, 1,   // 1st column
           1, 1, 1, 1,   // 2nd column
           1, 1, 1, 1,   // 3rd column
           1, 1, 1, 1);  // 4th column

// create a matrix with an array
float a[16] = {2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2};
Matrix4 m3(a);

// create a matrix with copy ctor
Matrix4 m4(m3);          // m3 and m4 have same elements now
Matrix4 m5 = m3;         // m3 and m5 have same elements now
...

Matrix4 Accessors (Setters/Getters)

Setters

Matrix4 class provides set() method to set all 16 elements.


Matrix4 m1;

// set matrix with 16 float elements (column-major order)
m1.set(1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1);

// set matrix with an array
float a1[] = {2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2};
m1.set(a1);

You can also set the column or row elements at once with setRow() or setColumn(). The first param of setRow() and setColumn() is zero-based index (0, 1, 2, or 3). The second param is the pointer to an array containing 4 elements.


Matrix4 m2;
float a2[4] = {2,2,2,2};

// set a row with the row index and array
m2.setRow(0, a2);      // 1st row

// set a column with the column index and array
m2.setColumn(2, a2);   // 3rd column

Identity Matrix

Matrix4 class has a special setter, identity() to make an identity matrix.


// set identity matrix
Matrix4 m3;
m3.identity();

Getters

Matrix4::get() method returns the pointer to the array of 16 elements. And, getTranspose() returns the transposed matrix elements. It can be used to find the inverse matrix of an Euclidean transform matrix. More details are in the example.


// get matrix elements as an array ptr
Matrix4 m4;
const float* a = m4.get();

// pass matrix to OpenGL
glLoadMatrixf(m4.get());

Access Individual Element

An individual element of a matrix can be also accessible by the subscript operator, [ ].


Matrix4 m5;
float f = m5[0];    // get 1st element

m5[1] = 2.0f;       // set 2nd element

Print Matrix4

Matrix4 also provides a handy print function with std::ostream operator <<, for debugging purpose.


// print matrix
Matrix4 m6;
std::cout << m6 << std::endl;

// output should be look like
[   1.00000    0.00000    0.00000    0.00000]
[   0.00000    1.00000    0.00000    0.00000]
[   0.00000    0.00000    1.00000    0.00000]
[   0.00000    0.00000    0.00000    1.00000]

Matrix4 Arithmetic

Matrix4 class provides basic arithmetic between 2 matrices.

Addition & Subtraction

You can add or subtract 2 matrices.


Matrix4 m1, m2, m3;

// addition
m3 = m1 + m2;
m3 += m1;       // short-hand op: m3 = m3 + m1

// subtract
m3 = m1 - m2;
m3 -= m1;       // short-hand op: m3 = m3 - m1

Multiplication

You can multiply 2 matrice together, and there is multiplications with 3D/4D vector as well, to transform the vector with the matrix. Note that matrix multiplication is not commutative.


Matrix4 m1, m2, m3;

// matrix multiplication
m3 = m1 * m2;
m3 *= m1;       // short-hand op: m3 = m3 * m1

// scalar product
m3 = 2 * m1;    // scale all elements

// vector multiplication
Vector3 v1, v2;
v2 = m1 * v1;
v2 = v1 * m1;   // pre-multiplication

Comparison

Matrix4 class provides comparison operators to compare all elements of 2 matrices.


Matrix4 m1, m2;

// exact comparison with no epsilon
if(m1 == m2)
    std::cout << "equal" << std::endl;
if(m1 != m2)
    std::cout << "not equal" << std::endl;

Matrix4 Transform Functions

OpenGL has several fuctions for matrix transformations; glTranslatef(), glRotatef() and glScalef(). Matrix4 calss provides the equivalent functions to transform matrix; translate(), rotate() and scale(). In addition, Matrix4 provides invert() to compute a inverse matrix.

Matrix4::translate(x, y, z)

translate matrix
Translation Matrix

translate() produces the current matrix translated by (x, y, z). First, it create a translation matrix, MT, then multiply it with the current matrix object to produce the final transform matrix:

Note that this function is equivalent to OpenGL's glTranslatef(), but OpenGL uses post multiplication instead of pre-multiplication (The translation matrix is multiplied back of the current matrix.) . If you apply multiple transforms, then the result is significantly different because matrix multiplication is not commutative.

Please see more examples of ModelView Matrix for applying sequential transforms.


// M1 = Mt * M1
Matrix4 m1;
m1.translate(1, 2, 3);   // move to (x, y, z)

Matrix4::rotate(angle, x, y, z)

rotation matrix
Rotation Matrix

rotate() can be used to rotate 3D models by an angle (degree) about a rotation axis (x, y, z). This function generate a rotation matrix MR, then multiply it with the current matrix object to produce the final rotation transform matrix:

It is equivalent to glRotatef(), but OpenGL uses post-multiplication to produce the final transform matrix:

rotate() is for rotation on an arbitary axis. Matrix4 class provides additional 3 functions for basis axis rotation; rotateX(), rotateY(), and rotateZ().


// M1 = Mr * M1
Matrix4 m1;
m1.rotate(45, 1,0,0);   // rotate 45 degree about x-axis
m1.rotateX(45);         // same as rotate(45, 1,0,0)

Matrix4::scale(x, y, z)

scale matrix
Scaling Matrix

scale() produces a non-uniform scaling transform matrix on each axis (x, y, z) by multiplying the current matrix object and scaling matrix: .

Note again, OpenGL's glScalef() performs post-multiplication: .

Matrix4 class also provides uniform scaling function as well.


// M1 = Ms * M1
Matrix4 m1;
m1.scale(1,2,3);    // non-uniform scale
m1.scale(4);        // uniform scale (same on all axis)

Matrix4::invert()

invert() function computes the inverse of the current matrix. This inverse matrix is typically used to transform the normal vectors from object space to eye space. Normals are transformed differently as vertices do. Normals are multiplied by the inverse of GL_MODELVIEW matrix to transform to eye space, . The detail is explained here.

If the matrix is only Euclidean transform (rotation and translation), or Affine transform (in addition, scale and shearing), then the computation of inverse matrix is much simpler. Matrix4::invert() will determine the appropriate inverse method for you, but you can explicitly call a specific inverse function; invertEuclidean(), invertAffine(), invertProjective() or invertGeneral(). Please look at the detail descriptions in Matrices.cpp file.


Matrix4 m1;
m1.invert();    // inverse matrix

Example: ModelView Matrix

Download the source and binary: matrix.zip (Updated: 2014-08-21)

This example shows how to integrate Matrix4 class with OpenGL. GL_MODELVIEW matrix combines the view matrix and model matrix, but we keep them separately and pass the product of 2 matrices to OpenGL's GL_MODELVIEW when it is required.


Matrix4 matModel, matView, matModelView;
glMatrixMode(GL_MODELVIEW);
...

// orbital camera (view)
matView.identity();                 // transform orders:
matView.rotate(-camAngleY, 0,1,0);  // 1: rotate on y-axis
matView.rotate(-camAngleX, 1,0,0);  // 2: rotate on x-axis
matView.translate(0, 0, -camDist);  // 3: translate along z

// model transform:
// rotate 45 on Y-axis then move 2 unit up
matModel.identity();
matModel.rotate(45, 0,1,0);         // 1st transform
matModel.translate(0, 2, 0);        // 2nd transform

// build modeview matrix: Mmv = Mv * Mm
matModelView = matView * matModel;

// pass it to opengl before draw
glLoadMatrixf(matModelView.get());

// draw
...

The equivalent OpenGL implementatations are following. The transform result is same as above.


//NOTE: the order of transforms are reversed
//      because OpenGL uses post-multiplication
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

// orbital camera (view)
glTranslatef(0, 0, -camDist);       // 3: translate along z
glRotatef(-camAngleX, 1,0,0);       // 2: rotate on x-axis
glRotatef(-camAngleY, 0,1,0);       // 1: rotate on y-axis

// model transform:
// rotate 45 on Y-axis then move 2 unit up
glTranslatef(0, 2, 0);              // 2nd transform
glRotatef(45, 0,1,0);               // 1st transform

// draw
...

The inverse of the modelview matrix is used for transforming the normals from object space to eye space. In programmable rendering pipeline, you may need to pass it to GLSL shader.


// build transform matrix for normals: (M^-1)^T
Matrix4 matNormal = matModelView;   // get modelview matrix
matNormal.invert();                 // get inverse for normal transform
matNormal.transpose();              // transpose matrix

Example: Projection Matrix

This example shows how to build the projection matrix, equivalent to glFrustum() and glOrtho(). Please see the source codes for more details.


// set projection matrix and pass to opengl
Matrix4 matProject = setFrustum(-1, 1, -1, 1, 1, 100);

glMatrixMode(GL_PROJECTION);
glLoadMatrixf(matProject.get());
...

///////////////////////////////////////////////////////////////////////////////
// glFrustum()
///////////////////////////////////////////////////////////////////////////////
Matrix4 setFrustum(float l, float r, float b, float t, float n, float f)
{
    Matrix4 mat;
    mat[0]  = 2 * n / (r - l);
    mat[5]  = 2 * n / (t - b);
    mat[8]  = (r + l) / (r - l);
    mat[9]  = (t + b) / (t - b);
    mat[10] = -(f + n) / (f - n);
    mat[11] = -1;
    mat[14] = -(2 * f * n) / (f - n);
    mat[15] = 0;
    return mat;
}

///////////////////////////////////////////////////////////////////////////////
// gluPerspective()
///////////////////////////////////////////////////////////////////////////////
Matrix4 setFrustum(float fovY, float aspect, float front, float back)
{
    float tangent = tanf(fovY/2 * DEG2RAD); // tangent of half fovY
    float height = front * tangent;         // half height of near plane
    float width = height * aspect;          // half width of near plane

    // params: left, right, bottom, top, near, far
    return setFrustum(-width, width, -height, height, front, back);
}

///////////////////////////////////////////////////////////////////////////////
// glOrtho()
///////////////////////////////////////////////////////////////////////////////
Matrix4 setOrthoFrustum(float l, float r, float b, float t, float n, float f)
{
    Matrix4 mat;
    mat[0]  = 2 / (r - l);
    mat[5]  = 2 / (t - b);
    mat[10] = -2 / (f - n);
    mat[12] = -(r + l) / (r - l);
    mat[13] = -(t + b) / (t - b);
    mat[14] = -(f + n) / (f - n);
    return mat;
}
...

←Back
Hide Comments
comments powered by Disqus