4

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.        ]
Francis
  • 1,151
  • 1
  • 13
  • 29
  • `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 Feb 12 '16 at 18:20
  • 600/480 = 1.25 when I run it in python. – Francis Feb 12 '16 at 18:23
  • http://ideone.com/I9sfXf – Colin Basnett Feb 12 '16 at 18:26
  • 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 Feb 12 '16 at 18:30
  • I'm guessing that we are using different versions of python (I'm using 3.4) http://stackoverflow.com/questions/21316968/division-in-python-2-7-and-3-3. All of which is beside the point as it's not contributing to the problem. – Francis Feb 12 '16 at 18:35
  • It's been hacked together from a few places, mostly from dissecting the glm::matrix_transform code and also this site https://unspecified.wordpress.com/2012/06/21/calculating-the-gluperspective-matrix-and-other-opengl-matrix-maths/. I derived the maths for this matrix myself. Some of the signs are different to both examples but substituting in their code didn't work either. EDIT: and yes the matrix produced is what I expect (unless I'm looking at that wrong, it's been know to happen). – Francis Feb 12 '16 at 18:35
  • Found a couple issues. Writing out an answer. – Colin Basnett Feb 12 '16 at 18:54

2 Answers2

4

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.

Colin Basnett
  • 4,052
  • 2
  • 30
  • 49
  • I had noticed the difference between the two matrices and checked this but it wasn't the problem. Also after changing the sign to match my clip space coordinates move from (-1,+1) to (+3,+1). I think the problem lies elsewhere. – Francis Feb 12 '16 at 19:08
  • Ok, that was the problem, for some reason when I checked this earlier there was no visible affect. I'm guessing something else was wrong and I changed that while fiddling with my code (I still don't know what it was or how it fixed it). Fingers crossed I don't break it again (it is times like these I am thankful I have git). – Francis Feb 12 '16 at 19:53
  • 1
    That can't be the reason, since they are exactly the same. The minus is being applied to the denominator. – Tony Power Mar 13 '20 at 13:02
1

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.

Francis
  • 1,151
  • 1
  • 13
  • 29