transformationgl-matrix

Transformation order reversed in glMatrix?


enter image description here

In both scenes the transformations have to be swapped in glMatrix.

i.e. to achieve 1) in glMatrix:

mat4.translate(modelViewMatrix, modelViewMatrix, [0.6, 0.0, 0.0]);
mat4.rotateZ(modelViewMatrix, modelViewMatrix, degToRad(45));

Why is the transformation order reversed?


Solution

  • That's not only the case for WebGL, but for OpenGL in general. And indeed, it may be confusing: The order in which the transformations are applied is the opposite of the order in which they appear in the source code.

    A simplified/shortened "pseudocode" version of the code that you provided is the following:

    M = identity();
    M = M * T; // Where T = Translation
    M = M * R; // Where R = Rotation
    

    An even shorter form of writing this would be

    M = T * R;
    

    Now imagine that you transform a vertex with this matrix - this can be written as

    transformedVertex = M * vertex
    

    Recalling that M = T * R, this is the same as

    transformedVertex = T * R * vertex
    

    You could also write it as

    transformedVertex = T * (R * vertex)
    

    or, to make it even more obvious:

    rotatedVertex = R * vertex
    transformedVertex = T * rotatedVertex
    

    So the vertex is rotated first. (And then, the rotated vertex is translated)


    Of course, you basically can turn things around. The usual way of multiplying matrices in OpenGL is the "post-multiplication" or "right-multiplication", in the form

    newMatrix = oldMatrix * additionalTransformation
    

    (like you have done it in your code). The alternative would be to write

    newMatrix = additionalTransformation * oldMatrix
    

    This is sometimes called "pre-multiplication" or "left-multiplication". So you could also write

    M = identity();
    M = T * M; // Where T = Translation
    M = R * M; // Where R = Rotation
    

    so that in the end,

    M = R * T
    

    In this case, the translation appears before the rotation in the source code, and the translation would also be applied before the rotation.

    But in the context of OpenGL, this is rather unusual. (And mixing both ways would be very confusing - I would not recommend this).


    A side note: All this may have made a bit more sense at the time when glPushMatrix and glPopMatrix have still been part of the OpenGL API. The way of thinking about this resembles the traversal of a scene graph. You first apply the "global" transformations, and then the "local" ones.


    Update:

    In response to the comments: I'll try to write a few words that may justify certain concepts. Summarizing this here is a bit difficult. I'll try to simplify it, and omit some details that likely are beyond the scope of a single answer here. Some of these things mentioned here refer to how things have been done in earlier versions of OpenGL and are solved differently nowadays - although many of the concepts are still the same!

    It is not uncommon to represent 3D scenes in form of a scene graph. This is a hierarchically structured representation of the scene, ususally in form of a tree:

                 root
                /    \
           nodeA      nodeB
          /  \           \
    nodeA0    nodeA1    nodeB0
    object    object    object
    

    The nodes contain transformation matrices (e.g. rotation or translation). The 3D objects are attached to these nodes. During rendering, this graph is traversed: Each node is visited, and its object will be rendered. This is done recursively, starting at the root, and visiting all children, down to the leaves. For example, the renderer may visit the above nodes in the following order:

    root 
      nodeA
        nodeA0
        nodeA1
      nodeB
        nodeB0
    

    During this traversal, the renderer maintains a "matrix stack". In earlier OpenGL versions, there have been dedicated methods for maintaining this stack. For example, glPushMatrix to push a copy of the current "top" matrix on the stack, and glPopMatrix to remove the topmost matrix from the stack. Or glMultMatrix to multiply the current "top" matrix of the stack with another one.

    When an object was rendered, it was always rendered with the matrix that was on the top of this stack. (There have been no shaders and mat4 uniforms back then...)

    So the renderer could render the scene graph with a simple recursive method like this (pseudocode) :

    void render(Node node) {
    
        glPushMatrix();
        glMultMatrix(node.matrix);
    
        renderObject(node.object);
    
        foreach (child in node.children) {
            render(child);
        }
    
        glPopMatrix();
    }
    

    By "enclosing" the rendering into a glPushMatrix/glPopMatrix pair, the renderer could always maintain the right current matrix for the node that it was visiting. Now, the renderer visited these nodes, and maintained the matrix stack:

    Node:           Matrix Stack:
    -----------------------------
    root            identity 
      nodeA         identity * nodeA.matrix 
        nodeA0      identity * nodeA.matrix * nodeA0.matrix
        nodeA1      identity * nodeA.matrix * nodeA1.matrix
      nodeB         identity * nodeB.matrix
        nodeB0      identity * nodeB.matrix * nodeB0.matrix
    

    One can see that the matrix that is used for rendering an object in a node is given by the product of all matrices along the path from the root to the respective node.

    The possible performance benefits and elegance of these concepts may become more obvious when considering a "large" scene graph:

    root
      nodeA
        nodeB
          nodeC
            nodeD0
            nodeD1
            nodeD2
            ...
            nodeD1000
    

    One could compute the product

    nodeA.matrix * nodeB.matrix * nodeC.matrix
    

    once, and then multiply the matrices of the nodeD0 ... nodeD1000 always with this matrix. Conversely, if one wanted to turn the multiplication around, one would have to compute

    nodeD0.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
    nodeD1.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
    ...
    nodeD1000.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
    

    wasting lots of resources for matrix multiplications. (These redundant computations could then have been avoided with other methods, but these would not nearly have been so elegant and easy).