Search code examples
c++opengl-esopengl-es-2.0coordinate-transformation

What is wrong with my matrix stack implementation (OpenGL ES 2.0)?


I am porting my OpenGL 1.1 application to OpenGL ES 2.0 and am writing a wrapper to implement the OpenGL 1.1 functions. My code seems to work fine until I start calling glPushMatrix() and glPopMatrix(). I think my understanding of how these should be implemented is incorrect.

Do I compute the final rotate/translate/scale before pushing it back on the stack? Should I keep only one modelview matrix (instead of separating it into three)? Are the transforms applied in the correct order?

Here is the code for my tranformation matrices

static std::vector<GLfloat> vertices;
static std::vector<std::vector<GLfloat>> rotationMatrixStack;
static std::vector<std::vector<GLfloat>> scalingMatrixStack;

static std::vector<GLfloat> rotationMatrix =
{
    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
};

static std::vector<GLfloat> scalingMatrix =
{
    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
};

static std::vector<GLfloat> translationMatrix =
{
    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
};

static std::vector<GLfloat> orthographicMatrix =
{
    .0025f, 0.0f, 0.0f, -1.0f,
    0.0f, .0025f, 0.0f, -1.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

void glTranslatef (GLfloat x, GLfloat y, GLfloat z)
{
    float translation[] = 
    {
        1.0f, 0.0f, 0.0f,   x,
        0.0f, 1.0f, 0.0f,   y,
        0.0f, 0.0f, 1.0f,   z,
        0.0f, 0.0f, 0.0f, 1.0f
    };

    multiplyMatrix(translation , &translationMatrix[0], &translationMatrix[0]);
}
void glScalef (GLfloat x, GLfloat y, GLfloat z)
{
    float scaling[] = 
    {
           x, 0.0f, 0.0f, 0.0f,
        0.0f,    y, 0.0f, 0.0f,
        0.0f, 0.0f,    z, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f
    };

    multiplyMatrix(scaling , &scalingMatrix[0], &scalingMatrix[0]);
}
void glRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
{
    glTranslatef(-x, -y, -z);
    GLfloat radians = angle * M_PI/180;
    float zRotation[] = 
    {
        cos(radians), -sin(radians), 0.0f, 0.0f,
        sin(radians),  cos(radians), 0.0f, 0.0f,
            0.0f,          0.0f, 1.0f, 0.0f,
            0.0f,          0.0f, 0.0f, 1.0f
    };

    multiplyMatrix(zRotation , &rotationMatrix[0], &rotationMatrix[0]);
    glTranslatef(x,y,z);
}

void glLoadIdentity (void)
{
    rotationMatrix, scalingMatrix, translationMatrix = 
    {
        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
    };
}

void multiplyMatrix(float* a, float* b, float* product)
{
    int a_heigth = 4;
    int a_width = 4;
    int b_heigth = 4;
    int b_width = 4;
    int product_heigth = a_heigth;
    int product_width = b_width;

    float intermediateMatrix[product_heigth * product_width] = {0};
    for (int product_row = 0; product_row < product_heigth; product_row++)
    {
        for (int product_column = 0; product_column < product_width; product_column++)
        {
            float value = 0;
            //std::cout << "r[" << (product_row*product_width) + product_column << "] = ";
            for (int multiplication_index = 0; multiplication_index < a_width ; multiplication_index++)
            {
                value += a[(product_row * a_width) + multiplication_index] * b[product_column + (b_heigth * multiplication_index)];
                //std::cout << "( a[" << (product_row * a_width) + multiplication_index << "] * b[" << product_column + (b_heigth * multiplication_index) << "] ) + ";
            }
            //std::cout << std::endl;
            intermediateMatrix[(product_row*product_width) + product_column] = value;
        }
    }

    for (int i = 0; i < product_heigth * product_width; i++)
    {
        product[i] = intermediateMatrix[i];
    }
}

Here is the code for the matrix stack

static std::vector<std::vector<GLfloat>> translationMatrixStack;
void glPushMatrix()
{
    rotationMatrixStack.push_back(rotationMatrix);
    scalingMatrixStack.push_back(scalingMatrix);
    translationMatrixStack.push_back(translationMatrix);
}

void glPopMatrix()
{
    rotationMatrix = rotationMatrixStack.back();
    scalingMatrix = scalingMatrixStack.back();
    translationMatrix = translationMatrixStack.back();

    rotationMatrixStack.pop_back();
    scalingMatrixStack.pop_back();
    translationMatrix.pop_back();
}

And here is the vertex shader code

attribute highp vec4    myVertex;
uniform mediump mat4    orthographicMatrix;
uniform mediump mat4    translationMatrix;
uniform mediump mat4    scalingMatrix;
uniform mediump mat4    rotationMatrix;
void main(void)
{
    gl_Position =   orthographicMatrix * translationMatrix * scalingMatrix * rotationMatrix * ( myVertex) ;
}";

Solution

  • You do not have a separate matrix stack for rotation, translation and scaling. In OpenGL there is one matrix stack for each matrix mode (See glMatrixMode). The matrix modes are GL_MODELVIEW, GL_PROJECTION, and GL_TEXTURE.


    See the documentation of glTranslate:

    glTranslate produces a translation by x y z . The current matrix (see glMatrixMode) is multiplied by this translation matrix, with the product replacing the current matrix.

    the documentation of glRotate:

    glRotate produces a rotation of angle degrees around the vector x y z . The current matrix (see glMatrixMode) is multiplied by a rotation matrix with the product replacing the current matrix.

    and the documentation of glScale:

    glScaleproduces a nonuniform scaling along the x, y, and z axes. The three parameters indicate the desired scale factor along each of the three axes. The current matrix (see glMatrixMode) is multiplied by this scale matrix.


    This means you need one matrix stack, and all operations operate on the same matrix stack.

    Note, a matrix multiplication C = A * B works like this:

    Matrix4x4 A, B, C;
    
    // C = A * B
    for ( int k = 0; k < 4; ++ k )
        for ( int j = 0; j < 4; ++ j )
            C[k][j] = A[0][l] * B[k][0] + A[1][j] * B[k][1] + A[2][j] * B[k][2] +  A[3][j] * B[k][3];
    


    A 4*4 matrix looks like this:

      c0  c1  c2  c3            c0  c1  c2  c3
    [ Xx  Yx  Zx  Tx ]        [  0   4   8  12 ]     
    [ Xy  Yy  Zy  Ty ]        [  1   5   9  13 ]     
    [ Xz  Yz  Zz  Tz ]        [  2   6  10  14 ]     
    [  0   0   0   1 ]        [  3   7  11  15 ] 
    

    And the memory image of a 4*4 matrix looks like this:

    [ Xx, Xy, Xz, 0, Yx, Yy, Yz, 0, Zx, Zy, Zz, 0, Tx, Ty, Tz, 1 ]
    


    This means you have to adapt your matrix operations:

    static std::vector<std::vector<GLfloat>> modelViewMatrixStack;
    
    static std::vector<GLfloat> modelViewMatrix{
        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 };
    
    void multiplyMatrix( float A[], float B[], float P[] )
    {
        float C[16];
        for ( int k = 0; k < 4; ++ k ) {
            for ( int l = 0; l < 4; ++ l ) {
                C[k*4+j] =
                    A[0*4+j] * B[k*4+0] +
                    A[1*4+j] * B[k*4+1] +
                    A[2*4+j] * B[k*4+2] +
                    A[3*4+j] * B[k*4+3];
            }
        }
        std::copy(C, C+16, P);
    }
    

    void glTranslatef( GLfloat x, GLfloat y, GLfloat z )
    {
        float translation[]{
            1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            x,    y,    z,    1.0f };
    
        multiplyMatrix(&modelViewMatrix[0], translation, &modelViewMatrix[0]);
    }
    
    void glScalef( GLfloat x, GLfloat y, GLfloat z )
    {
        float scaling[]{
            x,    0.0f, 0.0f, 0.0f,
            0.0f, y,    0.0f, 0.0f,
            0.0f, 0.0f, z,    0.0f,
            0.0f, 0.0f, 0.0f, 1.0f };
    
        multiplyMatrix(&modelViewMatrix[0], scaling, &modelViewMatrix[0]);
    }
    
    void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )
    {
        float radians = angle * M_PI/180;
        float c = cos(radians);
        float s = sin(radians);
    
        float rotation[16]{
           x*x*(1.0f-c)+c,   x*y*(1.0f-c)-z*s, x*z*(1.0f-c)+y*s, 0.0f,
           y*x*(1.0f-c)+z*s, y*y*(1.0f-c)+c,   y*z*(1.0f-c)-x*s, 0.0f,
           z*x*(1.0f-c)-y*s  z*y*(1.0f-c)+x*s, z*z*(1.0f-c)+c,   0.0f,
           0.0f,             0.0f,             0.0f,             1.0f };
    
        multiplyMatrix(&rotationMatrix[0], rotation, &rotationMatrix[0]);
    }  
    


    See further: