Search code examples
pythonopenglmatrixperspectivecamera

OpenGL Perspective Matrix in Python


I am trying to build a perspective transformation matrix in python for use with pyOpenGL. My view and model transformations are working but when I apply my projection transformation I get a blank screen (should be seeing a triangle at the origin viewed from (0,0,+1)).

I have looked over the maths and as far as I can tell the transformation should work, so I need a second pair of eyes to help find the problem.

def perspective(field_of_view_y, aspect, z_near, z_far):

    fov_radians = math.radians(field_of_view_y)
    f = math.tan(fov_radians/2)

    a_11 = 1/(f*aspect)
    a_22 = 1/f
    a_33 = (z_near + z_far)/(z_near - z_far)
    a_34 = -2*z_near*z_far/(z_near - z_far)

    # a_33 = -(z_far + z_near)/(z_far - z_near)
    # a_34 = 2*z_far*z_near/(z_far - z_near)

    perspective_matrix = numpy.matrix([
        [a_11, 0, 0, 0],       
        [0, a_22, 0, 0],       
        [0, 0, a_33, a_34],    
        [0, 0, -1, 0]          
    ]).T 

    return perspective_matrix

projection_matrix = perspective(45, 600/480, 0.1, 100)
mvp_matrix = projection_matrix * view_matrix * model_matrix

I am transposing the matrix because I'm fairly sure numpy stores the matrix transposed to how OpenGL needs it. I have tried sending the matrix without transposing it and it had no (visible) affect on the output.

And here is the vertex shader:

#version 330 core

layout(location = 0) in vec3 position;
uniform mat4 MVP;

void main()
{
    vec4 p = vec4(position, 1.0);
    gl_Position = MVP * p;
}

Can someone identify what the possible issues with my transformation could be?

EDIT: I've taken the output matrix and worked through the calculation by hand. After applying the perspective divide all the points on the edge of the frustum appear along the NDC box, with z at the near and far points being transformed to -1, +1 respectively (+/- minor accuracy due to rounding error). To me this suggests my maths is right and the problem is elsewhere. This is the output matrix:

[ 1.93137085  0.          0.          0.        ]
[ 0.          2.41421356  0.          0.        ]
[ 0.          0.         -1.002002   -1.        ]
[ 0.          0.          0.2002002   0.        ]

Solution

  • Since you said you are working from glm::perspective, let's analyze your code compared to it. There is a critical incongruency:

    glm::perspective

        assert(aspect != valType(0));
        assert(zFar != zNear);
    
    #ifdef GLM_FORCE_RADIANS
        valType const rad = fovy;
    #else
        valType const rad = glm::radians(fovy);
    #endif
    
        valType tanHalfFovy = tan(rad / valType(2));
        detail::tmat4x4<valType> Result(valType(0));
        Result[0][0] = valType(1) / (aspect * tanHalfFovy);
        Result[1][1] = valType(1) / (tanHalfFovy);
        Result[2][2] = - (zFar + zNear) / (zFar - zNear);
        Result[2][3] = - valType(1);
        Result[3][2] = - (valType(2) * zFar * zNear) / (zFar - zNear);
        return Result;
    

    Notice the following line:

    Result[2][2] = - (zFar + zNear) / (zFar - zNear);
    

    Compare it to your equivalent:

    a_33 = (z_near + z_far)/(z_near - z_far)
    

    Note that there is a negative sign (-) in front of the entire statement. Your version does not have this.