The layout of the modelview matrix

Simple question for you guys to answer, I suppose:-
I’m a bit confused about the ordering of the modelview matrix. I’ve been assuming it’s in this form:-

XAxis YAxis ZAxis Translation
x [0] x [4] x [8] x [12]
y [1] y [5] y [9] y [13]
z [2] z [6] z [10] z [14]
w [3] w [7] w [11] w [15]

And to transform a vertex by this matrix, I would do this:-

eyevert.x = objvert DOT mat.XAxis
eyevert.y = objvert DOT mat.YAxis
eyevert.z = objvert DOT mat.ZAxis

eyevert.x += mat.Translation.x
eyevert.y += mat.Translation.y
eyevert.z += mat.Translation.z

But this doesn’t appear to correspond to what opengl is doing.
This puzzles me, because in a vertex program transforming a vertex is basically what I’ve done above, something like this:-

DP4 o.x, c[0], v[0]
DP4 o.y, c[1], v[0]
DP4 o.z, c[2], v[0]
DP4 o.w, c[3], v[0]

So, it would appear that when the matrix gets to the vertex program its axis are sequential.
Can anyone spot my misunderstanding of what’s going on under the hood?

[This message has been edited by inept (edited 08-23-2002).]

2 Words… Linear Algebra

Number of columns in first matrix must equal the number of rows in the second matrix. Size of final matrix determined by the number of rows in first matrix and number of columns in the second matrix.

4x4 Matrix * 4x1 Matrix = 4x1 Matrix

First Row of 4x4 Matrix DOT4 First Column of 4x1 Matrix = 1st entry in 4x1 matrix

Second Row of 4x4 Matrix DOT4 First Column of 4x1 Matrix = 2nd entry in 4x1 matrix

Third Row of 4x4 Matrix DOT4 First Column of 4x1 Matrix = 3rd entry in 4x1 matrix

Fourth Row of 4x4 Matrix DOT4 First Column of 4x1 Matrix = 4th entry in 4x1 matrix

The fourth component is part of what is called the homogenous coordinate. Normally, in your vector supplied to OpenGL, this is assumed to be 1.0.

Instead of the columns being your axis, think of the entire rows themselves as your axis. When doing the 4 component dot products, if the last part of of the vector (which by defination is a 4x1 or 1x4 depending on which way you look at) is a 1 (your homogenous coordinate), then what is in the last column of the 4x4 matrix is added (translated) after the other manipulations (rotation and scaling stored in the upper-left 3x3 of the 4x4 matrix). So 1 times the values in the last column of the 4x4 matrix is the last column of the 4x4 matrix, so that is what is simply added in the final part of the dot product.

What you are doing is multiplying your vector by the transpose of the upper-left 3x3 of the 4x4 matrix and adding the translation (you are assuming that the last component of each vector is always 1). Don’t transpose that 3x3 (go sideways instead of up and down) and you’d be doing exactly what OpenGL does (minus the homogenous component).

Edit ->
Oh, and you are using the transpose of your modelview matrix, matrices are normally handled in row-major, OpenGL handles things in column major. Slight technical difficulty.
<- Edit

Dan

[This message has been edited by Dan82181 (edited 08-23-2002).]

Thanks for your reply, Dan.
I already understand that, but it’s nice to have it clarified.
No, really what I’m saying is, is this correct (considering the order in which a 1 dimensional array is mapped over the matrix):-

struct vec3
{
float x,y,z;

float Dot(vec3 v) {return (xv.x + yv.y + z*v.z);}
};

struct mat4
{
vec3 Xaxis; float transX;
vec3 Yaxis; float transY;
vec3 Zaxis; float transZ;
vec4 vectorwhichIdontunderstand;
};

Could I not then do this:-

vec3 objvert;
vec3 eyevert;
mat4 matrix;

eyevert.x = objvert.Dot(matrix.XAxis) + matrix.transX;
eyevert.y = objvert.Dot(matrix.YAxis) + matrix.transY;
eyevert.z = objvert.Dot(matrix.ZAxis) + matrix.transZ;

?
It seems it doesn’t give the same results as OpenGL, you see. It seems that OpenGL uses the transpose of what I believe, but in a vertex program it doesn’t appear to be using the transpose.

[This message has been edited by inept (edited 08-23-2002).]

Ok, I’ve answered my own question by looking at the mesa source code…

STRIDE_LOOP {
const GLfloat ox = from[0], oy = from[1], oz = from[2];
to[i][0] = m0 * ox + m4 * oy + m8 * oz + m12;
to[i][1] = m1 * ox + m5 * oy + m9 * oz + m13;
to[i][2] = m2 * ox + m6 * oy + m10 * oz + m14;
to[i][3] = m3 * ox + m7 * oy + m11 * oz + m15;
}

Looks like you’re right Dan (which I suspected, but didn’t want to believe) that the modelview is the transpose of what is sensible.
This must mean that when you track the modelview matrix in a vertex program, the vertex program automatically transposes the matrix before mapping it to the (C)onstant registers, so that the DP4 vector function can be used directly.
Thanks for your help, anyway.

(edit)
I seem to remember seeing an extension for automatically transposing the matrices in OpenGL, is there any reason why I shouldn’t use this? It would make my own transform code more efficient (and more readable) if I could work this way, but would it impose a heavy performance price on OpenGL?

[This message has been edited by inept (edited 08-23-2002).]

Yes, there was an extension that automatically transposed matricies for you. I don’t remember which version it was, but I think it became part of the GL1.3 spec, but it should still be advertised as an extension.

Link to spec at SGI’s extension registry (thanks guys)
GL_ARB_transpose_matrix

Concerning whether or not to use it, depends on how good you are at grouping/watching brackets and how crafty you can get with matrix math. Case and point…

EXT_vertex_shader does not have the equivelent of a “vertex state program” like NV_vertex_program, so when I need 4 or 5 matrices multiplied together, they’ve gotta be done on the CPU. And full matrices have to be supplied in column major order as well to EXT_vertex_shader. So what I do is if I need to retrieve a matrix from OpenGL, I leave it in it’s transposed state. What I do is write down the matrix formula I need on paper, with grouping brackets and parenthesis and what not. I see what I have to start with (possibly some transposed matrices), and I see what I have to end up with (a transposed matrix). And I just find the best way to come up with the same result requiring the least number of transposes and inverses and stuff like that.

If you really need to have the matrix in row order (say for writing it out to a log file or something like that), I wouldn’t see how there would really be any performance decrease in simply transposing incoming and outgoing matricies to/from OpenGL using the extension. I could be wrong, but I always just went with what the big guys said and never argued or complained about it.

Dan

Ok, just to clarify something.
Using a row major format (opposite to OpenGLs column major format), my matrix will fit nicely as 4 sequential vectors like this:-

                  
Xaxis vector:  0  1  2  3
Yaxis vector:  4  5  6  7
Zaxis vector:  8  9 10 11
Waxis vector: 12 13 14 15

And this is how the matrices are mapped into the constant registers of a vertex program?
Otherwise, I can’t see how the DP4 opcode in a vertex program could work…

[This message has been edited by inept (edited 08-24-2002).]

inept,

The problem is not in how the data is actually stored in memory; the problem is in how you write it out in text :slight_smile: Your matrix is correct, because it puts position in the 12,13,14 elements. If you want to write the matrix the way you do (column major) then that’s fine. Make sure to multiply your column vectors from the right.

If you want, you can write the same matrix in row-major order, and multiply your row vectors from the left:

xaxis x[0] y[1] z[2] w[3]
yaxis x[4] y[5] z[6] w[7]
zaxis x[8] y[9] z[10] w[11]
xlatn x[12] y[13] z[14] w[15]

Note that this writing is the EXACT SAME LAYOUT as your matrix, so it’s only a notational (on-paper) convention change, rather than an actual (in-code) change.

Thanks for your reply, jwatte.
But no it isn’t the same, I have to code it differently.
In your example, the translation would have to be in [3],[7] and [11] for it to work like a vertex program. As Dan said, the dot product would work like this:
ex = (oxm[0])+(oym[1])+(ozm[2])+(owm[3])
ey = (oxm[4])+(oym[5])+(ozm[6])+(owm[7])
ez = (oxm[8])+(oym[9])+(ozm[10])+(owm[11])

So if we assume ow will always be 1, then the translation in m[3],m[7] and m[11] will just be added on to the vector, giving us the origin of the new coordinate system.
The question still remains, why can’t I define a 4x4 matrix as 4 rows of vector4 instances? With the translation of each axis in the w component of each vector? In the same way that a vertex program appears to do?

inept,

That’s what ARB_transpose_matrix does.

Ok, so I’m right in thinking that when you track a matrix for a vertex program, it ends up copying the columns of the matrix into the rows in the vertex programs (ie. the ©onstant vector registers)?