🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Object rotation

Started by
21 comments, last by JoeJ 3 years, 7 months ago

taby said:
So OpenGL is column-major, right?

I'm always uncertain about what column-major vs. major-column really means.

We likely use the same convention, where the position is stored in elements [12,13,14] of a float[16] memory layout.
This has the advantage that position and basis vectors are compatible with vec3 or 4 structs.

But we seemingly disagree on ‘rows’ and ‘columns’. To me, a row is horizontal and stores basis vectors / position, and a column is vertical with strides in memory.
I noticed you have always called this ‘columns’, but i'm unsure if naming with this can be a matter of different conventions too.

However - it's just words. The larger problem is that this causes confusion also when programming, so i can't get rid of trial and error with such things. (Example: Rendering red/green/blue thingy also works with the transpose of a 3x3 matrix)

Advertisement

Apparently, OpenGL is column-major, and Direct3D is row-major. The matrices are transposes of each other.

Here is an example of a column-major perspective projection matrix. I used this in an iPhone game and some Windows utilities.

void get_projection_matrix(float fovy_degrees, float aspect, float znear, float zfar, float (&in_a)[16])
{
	const float pi = 4.0f*atanf(1.0);

	// Convert fovy to radians, then divide by 2
	float f = 1.0f / tan(fovy_degrees/360.0f*pi);

	in_a[0] = f/aspect; in_a[4] = 0; in_a[8] = 0;                              in_a[12] = 0;
	in_a[1] = 0;        in_a[5] = f; in_a[9] = 0;                              in_a[13] = 0;
	in_a[2] = 0;        in_a[6] = 0; in_a[10] = (zfar + znear)/(znear - zfar); in_a[14] = (2.0f*zfar*znear)/(znear - zfar);
	in_a[3] = 0;        in_a[7] = 0; in_a[11] = -1;                            in_a[15] = 0;
}

Here is the same matrix, but row-major (only the diagonal entries are the same). I've never used this before, because I've not used D3D since D3D9, where I could get by with D3DXMatrixLookAtLH and D3DXMatrixPerspectiveFovLH.

void get_projection_matrix(float fovy_degrees, float aspect, float znear, float zfar, float (&in_a)[16])
{
	const float pi = 4.0f*atanf(1.0);

	// Convert fovy to radians, then divide by 2
	float f = 1.0f / tan(fovy_degrees/360.0f*pi);

	in_a[0] = f/aspect; in_a[1] = 0; in_a[2] = 0;                              in_a[3] = 0;
	in_a[4] = 0;        in_a[5] = f; in_a[6] = 0;                              in_a[7] = 0;
	in_a[8] = 0;        in_a[9] = 0; in_a[10] = (zfar + znear)/(znear - zfar); in_a[11] = (2.0f*zfar*znear)/(znear - zfar);
	in_a[12] = 0;       in_a[13] = 0; in_a[14] = -1;                           in_a[15] = 0;
}

And so, in OpenGL, m[0][1] is the first column, and second row. In D3D it's the first row, and second column.

This why I assume that m[0] is the first column in OpenGL.

Have you ever felt like a caveman who has been shown fire by ancient astronauts? LOL

-LOL-

Ok well, let's try to understand what you have , but let's be like the caveman, as if we didn't know 3D then -lol-

Here comes the SPACE astronaut with a lot of SPACE theory:

Vertex v is usually specified in the LOCAL space of an object O such that:

v' = M * v

This is noted here as a right-to-left multiplication of a Homogeneous Position Vector v by a Transformation Matrix M which is object O's LOCAL space transform, in other word:

M transforms v (which is also in O's LOCAL space) to v' which is in GLOBAL space.

If this GLOBAL space has for origin (0,0,0) then v' has ended up in WORLD space.

M is defined as: M = T * R * S,

where:

  • S is the scale matrix that O has been scaled by to become the size that it is in GLOBAL space not LOCAL (in other words, this GLOBAL space scaling contributes to O's LOCAL space )
  • R is the orientation or rotation that O has gone through in GLOBAL space (not LOCAL) (in other words, this GLOBAL space orientation/rotate contributes to O's LOCAL space)
  • T is the translation that O was moved by to be where it is in GLOBAL space (not LOCAL) (in other words, this GLOBAL space translation has contributed to O's LOCAL space origin)

therefore by substitution, we can write on the cave wall:

v' = T * R * S * v  

this is a LOCAL-TO-GLOBAL space transform of v to v'

and if the scale is always going to be 1 on all axis, this can simplified to:

v' = T * R * v

I'm going to assume now that S is 1, that means M must contain some rotational and translation values, right:

M = 
[	r1	r4	r7	tx	]
[	r2	r5	r8	ty	]
[	r3	r6	r9	tz	]
[	0	0	0	1	]
	in column-major  

where (r1,r2,r3) represent X-axis rotational coefficients and thus define the LOCAL space X-axis (not GLOBAL)

where (r4,r5,r6) represent Y-axis rotational coefficients and thus define the LOCAL space Y-axis (not GLOBAL)

where (r7,r8,r9) represent Z-axis rotational coefficients and thus define the LOCAL space Z-axis (not GLOBAL)

where (tx,ty,tz) represent translation coefficients and thus define the LOCAL space origin (not the GLOBAL space origin)

Right, because we now have a orthonormal definition of space, any 3D object that has 3 orthonormal basis vectors, which have their origin at point T, can use this M notation;

for example,

the astronaut didn't come empty-handed, he brought a SONY xdcam model pxw-x70 camera snuggled at the right lobe of his helmet at a point P. This way, everytime dude moves his head to take a picture of the frog-like half-naked pre-cambrian ancestral, his helmet's chip re-adjusts the camera's orientation defined by (Forward, Up, Left or Right) vectors;

at the delight of NASA folks out here, the helmet's chip uses those 3 unit orthogonal vectors at point P to form the camera's LOCAL space (aka VIEW space or EYE space, but not the GLOBAL space);

and here is how those NASA folks defined the camera's VIEW space computed by the chip in a language of their own:

camera's Forward F vector to be aligned with +Z axis (r7,r8,r9), insert F into 3rd column;

camera's Up U vector to be aligned with +Y axis (r4,r5,r6), insert U into 2nd column;

camera's Left L vector will be aligned with -X axis (r1,r2,r3), insert L into 1st column;

and translation P set as origin of VIEW space, insert into 4th column;

return error;

crash if u want;

in other words:

View =  
[	-Lx		Ux		Fx		Px	]
[	-Ly		Uy		Fy		Py	]
[	-Lz		Uz		Fz		Pz	]
[	0		0		0		1	]

with this functionality coded in, any vertex v'' (which is in WORLD space, this is a space where the GLOBAL space is at (0,0,0)) multiplied by this View matrix will be transformed to v''' which will be in CAMERA space or VIEW space. Job done.

Then everything we said thus far can be written as:

v_global = M_local * v_local (1)
v_world = Mworld * v_global (2)

(1) into (2)
v_world = M_world * M_local * v_local (3)
v_view = M_view * v_world (4)

(3) into (4)
v_view = M_view * M_world * M_local * v_local (5)

and (5) is none other then the familiar:

v_view = VIEW * WORLD * MODEL * v; // v is v_local

this v_view vertex in VIEW space can then be projected to be this vertex into PROJECTION or CLIPPED space:

v_proj = PROJECTION * VIEW * WORLD * MODEL * v;

and this v_proj in PROJECTION or CLIPPED space can be normalized for viewport rendering like so:

v_normalized = NORMALIZE * PROJECTION * VIEW * WORLD * MODEL * v;

v_normalized is the vertex that we see onscreen. End of story;

Now, why did we have this story?

Well, if u look at what Joej did, which u also changed later:

JoeJ said:
Matrix4x3 mat; 
mat[0] = normalize(geodesic_dir); // tangent 
mat[1] = normalize(temp_dir); // make up vector (...be orthogonal to .., ..we would need to orthonormalize...) 
mat[2] = cross(mat[0], mat[1]); 
mat[3] = temp_dir * displacement;

you both built LOCAL space transforms of your chess pawns ?

taby said:
What's not obvious to me is why it works. LOL. Anyone care to decipher the magic code?

… and this story deciphers it, it all started in a cave ?

have fun ?

taby said:
And so, in OpenGL, m[0][1] is the first column, and second row. In D3D it's the first row, and second column. This why I assume that m[0] is the first column in OpenGL.

hmmm… there is no hope my confusion ever lifts : )

I would still say m[0][1] is first row and second column always. No matter if interpreted from GL or D3D, terms remain the same?

@ddlox What's your opinion on this semantics issue?

Thanks for that explanation ddlox!

Did I already show you this? The animation is a bit jerky, because my computer is not very powerful. LOL

Hi again JoeJ. Here is the simplest code to show that m[0] is the first column:

#include <glm/vec3.hpp> // glm::vec3
#include <glm/vec4.hpp> // glm::vec4
#include <glm/mat4x4.hpp> // glm::mat4
#include <glm/gtc/matrix_access.hpp> // glm::column
using namespace glm;

#include <iostream>
using namespace std;


int main(void)
{
	mat4 m;
	m[0] = vec4(
		1, 
		2, 
		3, 
		4);

	vec4 c = column(m, 0);

	// This prints 1 2 3 4, not 1 0 0 0
	cout << c.x << endl << c.y << endl << c.z << endl << c.w << endl;

	return 0;
}

taby said:
Here is the simplest code to show that m[0] is the first column:

I'm shocked.

It means i got this wrong for decades and i never noticed. :O

Note to myself:

Row is column, and column is row!

Row is column, and column is row!

Row is column, and column is row!

Row is column, and column is row!

Row is column, and column is row!

: ) Thanks for to patience to convince me - i guess this will help to minimize some confusion in the future… :D

JoeJ said:
What's your opinion on this semantics issue?

in GLM's doc it states “Notice that all matrix types are column-major rather than row-major. ” :

so when you write:
glm::mat4 m;
m[1] = ... it means you are accessing the entire 2nd column
m[2][3] = ... it means you are accessing the element at the 3rd colum and the 4th row 

This is true whether you are using GLM in OGL or DirectX
Both OGL and DX support column-major matrices in their shaders, so it's not a problem to pass this matrix as is to your shader

GLSL shader:

...
mat4 m;
vec4 my_pos;
...
vec4 pos = mul(m, my_pos);

HLSL shader:

...
matrix m; 			// or float4x3 or 4x4, whatever...
float4 my_pos;
...
float4 pos = mul(m, my_pos);

but if u insist on using row-major mats in GLM then u need this storage extension:

https://glm.g-truc.net/0.9.4/api/a00183.html

it will allow u to build matrices in one or the other format;

taby said:
Did I already show you this? The animation is a bit jerky, because my computer is not very powerful. LOL

haha! very nice , they look like they're going to vote ?

it’s kind of like how I had no idea that the model matrix forms a basis and a translation. I had no idea, and now the possibilities are practically endless. ?

Like, I am humbled by the simplicity and power of linear algebra, so thanks again for showing me the way!

So we both learned some thing ; )

taby said:
The animation is a bit jerky, because my computer is not very powerful. LOL

Probably not the main reason, but the last two days i had good opportunity to look at glm performance.
It's MPM fluid sim, which is all about speed because particles are many and timestep is tiny.
And i was using glm at first, and now have replaced it with what i usually use: Sonys SSE math lib that comes with Bullet Physics. That's old and i have never updated it since > 10 years.

MPM uses mostly vec3 and mat3, and to update 1 million particles i got (VS2019, Ryzen2700):

glm: 3.75 sec. (Seems the MSVC compiler dies from templates?)

Sony, not using SIMD: 1.36 sec. (No templates or C++11. Seems the compiler is surprisingly good at auto vectorization.)

Sony, SSE intrinsics: 1.23 sec.

I think we can subtract 0.4 seconds for sparse grid stuff which isn't affected much, so the real math difference is larger than 3.
I'd love to check this also with Clang, but i guess it's hard to make this work properly with VS.

This topic is closed to new replies.

Advertisement