pouët.net

Matrix conventions / multiplication order

category: code [glöplog]
Hi, I read ryg's very nice blog post about matrix conventions over at
http://fgiesen.wordpress.com/2012/02/12/row-major-vs-column-major-row-vectors-vs-column-vectors/

Some of the facts mentioned there include:
- GL fixed function is column-major and uses column vectors multiplied from the right.
- D3D fixed function was row-major and used row vectors multiplied from left.
- Matrix multiplication is not commutative, i.e. A*B != B*A.

My question is:

Doesn't this in fact imply that overloading “operator *” in matrix classes to perform matrix multiplication should be generally avoided? Because I think this way your codebase will “lock in” to a specific multiplication order, as writing A*B has a different meaning in OpenGL vs. D3D.
I am not sure this can be solved by a simple transpose in the rendering backend code: What about matrices that resulted out of complex transformations with many multiplications?

Isn't it better to keep the required multiplication order for the currently active platform in a globally accessible flag that you then passthru to your multiplyMatrix() function (LEFT_MULTIPLY for row vectors to be multiplied from the left (D3D), RIGHT_MULTIPLY for column vectors to be multiplied from the right (GL)), or something like that?

Furthermore, it seems to me that implementing a custom matrix stack (similar to OpenGL) can also help to eliminate the mentioned issues: The order of "push", "pop", "Load/MultMatrix", etc. calls is agnostic of the actual matrix multiplication order (i think), so special case code only has to live inside the "MultMatrix" implementation.

Any thoughts?
added on the 2013-09-04 11:22:11 by arm1n arm1n
generally you pick either system and use that consistently throughout your codebase

then, if somewhere down the line conversion is required (e.g. in a renderer backend), you solve it there in the platform specific implementation
added on the 2013-09-04 11:35:16 by superplek superplek
But then if my codebase computed e.g. M = (((Trans*Rot)*Scale)*Bla1)*... (opengl-compatible order) how would I be able to convert the resulting M that gets passed to my D3D renderer backend?
I.e. how do I yield the required M_d3d = (((...*Bla1)*Scale)*Rot)*Trans) from the given M?
added on the 2013-09-04 11:46:01 by arm1n arm1n
argh, M_d3d = ...*(Bla1*(Scale*(Rot*Trans))) I mean
added on the 2013-09-04 11:48:32 by arm1n arm1n
i'm bad at programming, but isn't it by transposing the result matrix?
added on the 2013-09-04 12:02:58 by nystep nystep
If I am not mistaken, to convert between an openGL and a D3D matrix, you need to transpose it, i.e. Rot_GL = transpose(Rot_D3D).
Now you are lucky that
transpose(A) * transpose(B) = transpose(B * A)
So if you do M_d3d = transpose(M_GL) you will get the same as transposing the individual matrices and multiplying them in the reverse order.

Or in other words: What nystep said.
added on the 2013-09-04 12:04:40 by chock chock
basically if you change from row to column major you transpose the matrices if my undercaffeinated brain gets that right.

then this rule applies: (ab)^t = (b^t)(a^t). so you can simply implement your own stuff and then transpose if needed afterwards, for instance in the specific shader or in memory, whatever you prefer.
added on the 2013-09-04 12:05:44 by skomp skomp
chock: i only had to think longer :D
added on the 2013-09-04 12:06:11 by skomp skomp
GL and D3D matrices have the same _memory layout_, no need to transpose them!

Quoting from ryg's article:
Quote:
If you look at an individual matrix (say translation by (tx, ty, tz)), the D3D and GL versions look exactly the same in memory. However, they do not behave the same way: in OpenGL, the matrix that represents “first A then B” is B*A; in D3D, it is A*B.
added on the 2013-09-04 12:37:20 by arm1n arm1n
indeed, a transpose will do the trick (*). unless you're working with third party libraries that also use an order different than yours you'll generally only have this going on in the platform renderer.

(* - another issue to look out for is projection matrices when using a different handed graphics api)
added on the 2013-09-04 12:37:45 by superplek superplek
i'd advise adhering to b*a btw, since everyone does that.
added on the 2013-09-04 12:39:38 by superplek superplek
so the transpose reverses the previous concatenation order?

because i state again: there is no difference in memory layout, so no transposing required regarding layout
added on the 2013-09-04 12:46:11 by arm1n arm1n
Quote:
generally you pick either system and use that consistently throughout your codebase


No no no, that's all wrong. Generally you make a mess out of it and then throw random transposes and inverses around until it seems to work and finally throw the result onto maintenance programmer.
added on the 2013-09-04 12:54:17 by 216 216
:D

spike:

When memory layout is identical (i.e. row-major as Ryg describes it) it becomes a matter of interpretation (or implementation if you will).

Simply put:

- GL and D3D implement the matrix mul. the other way around, so wherever this is of concern, correct it (GLSL, fixed pipe stuff). In practice you'll find that you often create full transform matrices yourself and upload them to shader constants: so then nothing's different really.

- Projection is obviously different for each, but that's sorta besides the point here.

- Pick one way of doing things in your own code, and stick with it. Adjust in places where needed, and at the last moment (platform dep. code). Or go with what 216 just said, since that is usually what happens and allows for hours of fun!

Matrix transpose has a few uses (e.g. transpose 3x3 part to invert rotate/scale). It's also used to convert the memory layout.

Also: (AB)t == BtAt
So that does not mean that (AB)t == BA
(afair, i haven't done much geometry/graphics stuff the past 2 years)
added on the 2013-09-04 13:10:59 by superplek superplek
Quote:

Also: (AB)t == BtAt
So that does not mean that (AB)t == BA
(afair, i haven't done much geometry/graphics stuff the past 2 years)


but you are still right.

for the matrix mul row vs col major and memory layout means that they just switch the inner and outer loops of the multiplication? man, weirdos...
added on the 2013-09-04 13:30:08 by skomp skomp
ok, i think i figured it out:

we want to build a transformation matrix that uniformly scales by 2 first an then translates by (5,5,5):

OGL: M_gl = T*S

|1 0 0 5| |2 0 0 0|
|0 1 0 5| |0 2 0 0| =
|0 0 1 5| |0 0 2 0|
|0 0 0 1| |0 0 0 1|

|2 0 0 5|
|0 2 0 5|
|0 0 2 5|
|0 0 0 1|

in memory (column major storage): {{2,0,0,0},{0,2,0,0},{0,0,2,0},{5,5,5,1}}

D3D: M_d3d = S*T

|2 0 0 0| |1 0 0 0|
|0 2 0 0| |0 1 0 0| =
|0 0 2 0| |0 0 1 0|
|0 0 0 1| |5 5 5 1|

|2 0 0 0|
|0 2 0 0|
|0 0 2 0|
|5 5 5 1|

in memory (row major storage): {{2,0,0,0},{0,2,0,0},{0,0,2,0},{5,5,5,1}}

So the answer indeed is to stick with one of the two multiplication order conventions and the resulting matrix then can be used with either D3D or GL (*) - i.e. no transposing is needed!
(* with the exception of projection matrices as plek points out (because D3D uses a [0..1] clipping range for z, while GL uses [-1..1]). and i also think view/lookat matrices are API/handedness dependend..)
added on the 2013-09-04 13:36:42 by arm1n arm1n
You're right, when you only scale, you don't need to transpose.. :) (runs to optimize his engine code once again!)
added on the 2013-09-04 13:38:59 by nystep nystep
so you're saying i picked a bad case to prove that transposing is not needed... uff, ok, i'll double check..
added on the 2013-09-04 13:49:24 by arm1n arm1n
Quote:

No no no, that's all wrong. Generally you make a mess out of it and then throw random transposes and inverses around until it seems to work and finally throw the result onto maintenance programmer.


Or you can use a library like glm and stop wasting time with that... unless you're making an intro, of course.
added on the 2013-09-04 14:05:43 by flure flure
flure: i want to see you taking the projection matrix from glm and stick it straight inside D3D and it works fine.. :)
added on the 2013-09-04 14:43:32 by arm1n arm1n
awful example spike. what's that matrix doing? isn't the 5,5,5 setup to modify the w component? kinda unimportant. you coulda based it on translation. and there is that for soft simd implementations the column major layout is probably the better choice for d3d. and mangle the shaders to do the math that way. there's more differences between gl and d3d so the render back end gotta solve the matrix orientation magic anyway.
added on the 2013-09-04 14:57:29 by yumeji yumeji
also: stop overthinking this
just beware you're not writing kludgy logic everywhere (the endless transposing/inverting 216 mentioned) and you'll be fine
research as needed
added on the 2013-09-04 15:19:22 by superplek superplek
plek: you're probably right regarding overthinking. but on the other hand i'd like to really understand what's going on. after all ryg wrote the article, because people don't know what they are doing and there is a lot of confusing/wrong info on the web.

logged out: the 5,5,5 _is_ a translation. talking about confused people..
added on the 2013-09-04 15:41:21 by arm1n arm1n
i just read that article and not only is it spot on, it also answers all questions that were asked here ;)
added on the 2013-09-04 16:08:31 by superplek superplek
@spike really? mmh. *shrugs* yeah. anyway... it's confusing. double standards suck major ass. :D
added on the 2013-09-04 16:30:02 by yumeji yumeji

login