Search code examples
c++openglmatrixglslshader

Calculating normalized device coordinates on shader shows different results then if calculated on cpu


I have created an Orthographic projection camera for use in my simple opengl 2d renderer. Currently I have the problem that the calculated normalized device coordinates are wrong when I calculate them on the shader, but when I calculate them on the cpu I get the desired results.

I have created an Orthographic Projection matrix using the following formula:

2 / (right - left), 0, 0, -((right + left) / (right - left)),
0, 2 / (top - bottom), 0, -((top + bottom) / (top - bottom)),
0, 0, -2 / (zFar - zNear), -((zFar + zNear) / (zFar - zNear)),
0, 0, 0, 1

Where right = 1280, left = 0, top = 0, bottom = 720, zFar = 1.0 and zNear = -1.0.

So if I would create a rectangle using the following vertex positions:

float vertices[5 * 4] = {
    //vertex pos                tex pos
    0.0f,    720.0f, 0.0f,      0.0f, 0.0f, //bottom left
    1280.0f, 720.0f, 0.0f,      1.0f, 0.0f, //bottom right
    1280.0f, 0.0f,   0.0f,      1.0f, 1.0f, //top right
    0.0f,    0.0f,   0.0f,      0.0f, 1.0f  // top left
};

It should result in a rectangle filling the entire screen.

To calculate the normalized device coordinates I use the following formula:

ViewProjectionMatrix * Transform * Position

In the vertex shader it looks like this:

layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_TexCoord;

uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
uniform vec4 position;
out vec2 v_TexCoord;

void main()
{
    v_TexCoord =  a_TexCoord;
    gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
}

u_ViewProjection is the above mentioned ViewProjection matrix, u_Transform is a transform matrix and is in this case a simple Identity matrix and a_Position is the vertex position.

Using the above values it results in the following render: Image of render showing triangle

Now if I do the calculation on the cpu using the following code:

Mat4x4f transform = Mat4x4f::translate(Mat4x4f(1.0f), Vector3f(0.0f, 0.0f, 0.0f)) * 
Mat4x4f::scale(Vector3f(1.0f, 1.0f, 1.0f));

// ViewProjectionMatrix * Transform * Position
Vector4f tl = camera.getViewProjectionMatrix() * transform * Vector4f(0.0f, 0.0f, 0.0f, 1.0f); 

the transform is the same transform passed to u_Transform and the same for camera.getViewProjectionMatrix that is passed to u_ViewProjection.

Here I get the desired result: x = -1.0, y = 1.0 which should be the top left corner of the screen.

So my question... Am I doing something wrong in the shader or could it be something else causing this?

Edit 1 As said in the accepted answer the Projection matrix needed to be transposed, I came to this matrix by using the following source: https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/orthographic-projection-matrix. Unfortunatly here they show an image implying it is colum major while it is actualy row major, see screen shot below: screenshot


Solution

  • You have to transpsoe the matrices, because OpenGL matrices are stored in Column-major order.

    See The OpenGL Shading Language 4.6, 5.4.2 Vector and Matrix Constructors, page 108:

    To initialize a matrix by specifying vectors or scalars, the components are assigned to the matrix elements in column-major order:

    mat4(float, float, float, float,  // first column
         float, float, float, float,  // second column
         float, float, float, float,  // third column
         float, float, float, float); // fourth column
    

    The memory image of a 4*4 OpenGL matrix

      c0  c1  c2  c3            c0  c1  c2  c3
    [ Xx  Yx  Zx  Tx ]        [  0   4   8  12 ]
    [ Xy  Yy  Zy  Ty ]        [  1   5   9  13 ]
    [ Xz  Yz  Zz  Tz ]        [  2   6  10  14 ]
    [  0   0   0   1 ]        [  3   7  11  15 ]
    

    looks like this:

    [ Xx, Xy, Xz, 0, Yx, Yy, Yz, 0, Zx, Zy, Zz, 0, Tx, Ty, Tz, 1 ]
    

    So the view matrix has to be

    2 / (right - left), 0,                  0,                   0,
    0,                  2 / (top - bottom), 0,                   0,
    0,                  0,                  -2 / (zFar - zNear), 0,
    -((right + left) / (right - left)), -((top + bottom) / (top - bottom)), -((zFar + zNear) / (zFar - zNear)), 1
    

    The other option would be to reverse the order of multiplication in the vertex shader:

    gl_Position = vec4(a_Position, 1.0) * u_Transform * u_ViewProjection;
    

    See GLSL Programming/Vector and Matrix Operations:

    If a vector is multiplied to a matrix from the left, the result corresponds to multiplying a row vector from the left to the matrix. This corresponds to multiplying a column vector to the transposed matrix from the right: