4
votes

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.        ]
2
600/480 is always going to return a 1. Make it 600.0/480.0. That shouldn't be the reason for the blank screen though. - Colin Basnett
600/480 = 1.25 when I run it in python. - Francis
Have you checked the result of your perspective function? Does it return what you expect? Where did you get the code for the function? - Colin Basnett
I'm guessing that we are using different versions of python (I'm using 3.4) stackoverflow.com/questions/21316968/…. All of which is beside the point as it's not contributing to the problem. - Francis

2 Answers

4
votes

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.

1
votes

I have figured out the problem, posting for info in case anyone runs into similar problems in future.

While building up the model, view and projection matrices I introduced a mix of row major and column major matrices. These were introduced because numpy and OpenGL require the matrices in different formats. When operating alone these matrices worked because they could be transposed easily with numpy to produce the correct result.

The problem occurred when combining the matricies. The effect was the transformations were applied in an inconsistent and meaningless order and all points were drawn off screen. This hid errors in the perspective matrix and complicated debugging.

The solution is to ensure all matrices are consistent in the way they store data (either all row major or all column major) and the transposition happens once prior to sending to OpenGL.