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?
The inverse of transformation matrix [R|t] is [R^T | - R^T t].
The order of the composite transformation is first scale, then rotate, then translate.
There are three basic kinds of Transformations in Computer Graphics: 1. Translation 2. Rotation 3. Scaling.
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.
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.
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).
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With