Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 736
dmr Avatar asked Jul 17 '16 20:07

dmr


People also ask

How do you reverse transformation matrix?

The inverse of transformation matrix [R|t] is [R^T | - R^T t].

What is the correct order to get the transformed vector?

The order of the composite transformation is first scale, then rotate, then translate.

What is transformation in OpenGL?

There are three basic kinds of Transformations in Computer Graphics: 1. Translation 2. Rotation 3. Scaling.

Can a 3x3 matrix be used to perform a 3D translation?

All linear transformations map the origin of the domain to the origin of the range. Therefore 3x3 matrices cannot perform translation on 3D vectors since the origin in one space cannot be mapped to anything but the origin on another using linear maps.


2 Answers

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).

like image 117
Marco13 Avatar answered Dec 17 '22 11:12

Marco13


I'm not so sure this glMatrix is backward.

For example watching these videos it seems like it's standard to do

m1 * m2 * m3 * vector

and given the order shown in the video that would correspond to

gl_Position = projection * view * world * position;

which exactly matches GL and GLSL.

It also matches glMatrix.

var m = mat4.create();
mat4.projection(m, fov, aspect, zNear, zFar);
mat4.multiply(m, m, view);
mat4.translate(m, m, [x, y, z]);
mat4.rotateY(m, m, someAngle);
mat4.scale(m, m, [sx, sy, sz]);

Exactly corresponds to

m = projection * 
    view * 
    translation * 
    rotation *
    scale;

Seems forward to me.

var vs = `
uniform mat4 u_worldViewProjection;

attribute vec4 position;
attribute vec2 texcoord;

varying vec2 v_texCoord;

void main() {
  v_texCoord = texcoord;
  gl_Position = u_worldViewProjection * position;
}
`;
var fs = `
precision mediump float;

varying vec2 v_texCoord;
uniform sampler2D u_diffuse;

void main() {
  gl_FragColor = texture2D(u_diffuse, v_texCoord);
}
`;

"use strict";
var gl = document.querySelector("canvas").getContext("webgl");
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);

var arrays = {
  position: [1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
  normal:   [1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1],
  texcoord: [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1],
  indices:  [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23],
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

var tex = twgl.createTexture(gl, {
  min: gl.NEAREST,
  mag: gl.NEAREST,
  src: [
    255, 0, 0, 255,
    192, 192, 192, 255,
    0, 0, 192, 255,
    255, 0, 255, 255,
  ],
    });

    var uniforms = {
    u_lightWorldPos: [1, 8, -10],
  u_lightColor: [1, 0.8, 0.8, 1],
  u_ambient: [0, 0, 0, 1],
  u_specular: [1, 1, 1, 1],
  u_shininess: 50,
  u_specularFactor: 1,
  u_diffuse: tex,
};

function render(time) {
  time *= 0.001;
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  var eye = [1, 4, -6];
  var target = [0, 0, 0];
  var up = [0, 1, 0];

  var view = mat4.create();
  var camera = mat4.create();
  
  // glMatrix's lookAt is arguably backward.
  // It's making an inverse lookAt which is far less useful.
  // There's one camera in the scene but hundreds of other
  // objects that might want to use a lookAt to you know, look at things.
  mat4.lookAt(view, eye, target, up);
  //mat4.lookAt(camera, eye, target, up);
  //mat4.invert(view, camera);
    
  var m = mat4.create();

  var fov = 30 * Math.PI / 180;
  var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  var zNear = 0.5;
  var zFar = 10;
  mat4.perspective(m, fov, aspect, zNear, zFar);
  mat4.multiply(m, m, view);
  mat4.translate(m, m, [1, 0, 0]);
  mat4.rotateY(m, m, time);
  mat4.scale(m, m, [1, 0.5, 0.7]);
  
  uniforms.u_worldViewProjection = m;
  
  gl.useProgram(programInfo.program);
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  twgl.setUniforms(programInfo, uniforms);
  twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display block; }
<script src="https://twgljs.org/dist/twgl-full.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
<canvas></canvas>
like image 26
gman Avatar answered Dec 17 '22 11:12

gman