The 2024 Wheel Reinvention Jam just concluded. See the results.

basis vectors (rotation)

Hello. I want to my character object facing where I am looking in 3D space.(character is just a box). So I am using basis vectors(right, up, forward) to construct matrix and then multiply that with the model matrix. But everything works except when the character rotates it also stretches(like a water). What I did wrong?
Code for rotation object
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
   model = scale(model, hero->size);
    
    v3 up = v3(0.0f,1.0f,0.0f);
    v3 right = normalize(cross(gameState->cameraFront,up));
    
    mat4 M = 
    {
        {
            {right.x,up.x,gameState->cameraFront.x,0.0f},
            {right.y,up.y,gameState->cameraFront.y,0.0f},
            {right.z,up.z,gameState->cameraFront.z,0.0f},
            {0.0f,0.0f,0.0f,1.0f}
        }
    };
    model = M * model;
    
    
    model = translate(model, hero->p);
    
    passUniformMatrix(opengl.shaderProgram,model,false,"model");
    glDrawArrays(GL_TRIANGLES, 0, 36);

Code for camera transforming
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void 
cameraTransform(Camera *camera, Input *input, Game_state *gameState)
{
    beginUseProgram(opengl.shaderProgram);
    if(camera->firstMouseMove)
    {
        camera->lastMouseX = input->mouseX;
        camera->lastMouseY = input->mouseY;
        
        camera->firstMouseMove = false;
    }
    
    s32 dtMouseX = input->mouseX - camera->lastMouseX;
    s32 dtMouseY = camera->lastMouseY - input->mouseY;
    
    dtMouseX *= camera->mouseSensitivity;
    dtMouseY *= camera->mouseSensitivity;
    
    camera->yaw += dtMouseX;
    camera->pitch += dtMouseY;
    
    if(camera->pitch > 89.0f)
    {
        camera->pitch  = 89.0f;
    }
    if(camera->pitch < -89.0f)
    {
        camera->pitch = -89.0f;
    }
    
    gameState->cameraFront.x = cos(ToRadians(camera->yaw)) * cos(ToRadians(camera->pitch));
    gameState->cameraFront.y = sin(ToRadians(camera->pitch));
    gameState->cameraFront.z = sin(ToRadians(camera->yaw)) * cos(ToRadians(camera->pitch));
    
    gameState->cameraFront = normalize(gameState->cameraFront);
    
    mat4 viewMatrix = indentity();
    viewMatrix = lookAt(gameState->cameraP,
                        gameState->cameraP + gameState->cameraFront,
                        v3(0.0f,1.0f,0.0f));
    
    
    passUniformMatrix(opengl.shaderProgram,viewMatrix,false,"view");
    
    camera->lastMouseX = WINDOW_WIDTH / 2;
    camera->lastMouseY = WINDOW_HEIGHT/ 2;
    
    endUseProgram(opengl.shaderProgram);
}

Thanks in advance.

Edited by Roman on Reason: Initial post
Are cameraFront and up guaranteed to be perpendicular (in other words, is cameraFront parallel to the ground since up is strait up) ?

If not, you need to recompute up after computing right by doing another cross product between cameraFront and right (the 3 axis of the matrix need to be perpendicular otherwise the object will be deformed).

But I'm not "fluent" with matrix transformations so maybe it's not that.
Just wondering what the matrix multiplication function looks like?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
mat4 
operator*(mat4 a, mat4 b)
{
    mat4 result = {};
    
    for(s32 r = 0; r < 4; r++)
    {
        for(s32 c = 0; c < 4; c++)
        {
            for(s32 i = 0; i < 4; i++)
            {
                result.e[r][c] += a.e[r][i] * b.e[i][c];
            }
        }
    }
    return result;
}

Hi Roman,

I'm a bit rusty on matrix's, but one thing I did notice is the layout of the matrix - the basis vectors are laid out in column-major order that is

1
2
3
4
5
6
Matrix4 result = {{
            xAxis.x, yAxis.x, zAxis.x, 0,
            xAxis.y, yAxis.y, zAxis.y, 0,
            xAxis.z, yAxis.z, zAxis.z, 0,
            0, 0, 0, 1
        }};


instead of

1
2
3
4
5
6
Matrix4 result = {{
            xAxis.x, xAxis.y, xAxis.z, 0,
            yAxis.x, yAxis.y, yAxis.z, 0,
            zAxis.x, zAxis.y, zAxis.z, 0,
            0, 0, 0, 1
        }};


Not that that's a problem as your matrix multiplication function takes this into account, but OpenGL expects matrixes to be in the second format I believe. Not sure if this is the problem, but you could try transposing the final matrix before you send it to OpenGL.

Edited by Oliver Marsh on
Unless you are talking about ancient fixed-functionality-pipeline in OpenGL 1.x, then OpenGL (same as D3D) does not care about which layout your matrix uses. You can use either layout, all you need is just correctly write out math in your shader (m * v) vs (v * m) or use GLSL layout qualifiers - like layout(row_major) vs layout(column_major).

Edited by Mārtiņš Možeiko on
Oh yea, was thinking if you use the matrices as the same way as math P * V * M instead of the other way.
With matrices it works the same way (vector is just the 1x4 matrix). You do either P*V*M or you do M*V*P - result will be the same for both storage formats. It works because of following basic property of matrix multiplication: https://en.wikipedia.org/wiki/Matrix_multiplication#Transpose


Edited by Mārtiņš Možeiko on
Does that mean you don't have to worry about the ordering in the shader or
1
P*V*M*Vertex
will work with the row-major layout (M, then V, then P) & column-major will work with
1
M*V*P*Vertex
(P first, then V, then M)? That is a neat way of doing things.

Edited by Oliver Marsh on
Almost correct. Vertex also needs to be on correct side, just like other matrices.

You do "P * V * M * vertex" for column major.
Or "vertex * M * V * P" for row major.

But order of multiplication you do first does not really matter. As "((vertex * M) * V) * P" is same as "vertex * ((M * V) * P)" or even as "(vertex * M) * (V * P)".
https://en.wikipedia.org/wiki/Matrix_multiplication#Associativity

Edited by Mārtiņš Možeiko on
Thanks Martin 😊