c++openglglm-mathperspectivecamerahomogenous-transformation

How does near/far clipping planes in gluPerspective()/glm::perspective() work?


I have a question about how the standard perspective projection matrix works, the one you get from gluPerspective() or glm::perspective(). Specifically, I'm wondering about the near/far clipping planes.

I was under the impression that after transforming a point with the perspective matrix, objects on the near clip plane mapped to a Z value of -1, and objects near the far clip plane mapped to a Z value of 1.

For instance, lets say you have a camera located at the origin, looking in the direction of the positive Z axis, with near clip plane at 1.0 and far clip plane set to 2.0. I would then expect that a things having "world" Z coordinates between 1.0 and 2.0 would be transformed as having projected coordinates between -1.0 and 1.0.

In other words, i would expect this C++ code:

#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

using namespace glm;

void print_vec(vec4 pnt) {
    std::cout << "(" << pnt.x << ", " << pnt.y << ", " << pnt.z << ")" << "\n";
}

int main() {
    mat4x4 view = lookAt(
            vec3(0.0f, 0.0f, 0.0f),   // eye at origin
            vec3(0.0f, 0.0f, 1.0f),   // looking towards +Z
            vec3(0.0f, 1.0f, 0.0f));  // up is +Y

    mat4x4 projection = perspective(
            radians(90.0f),  // field of view
            1.0f,            // aspect ratio
            1.0f,            // near clipping plane
            2.0f);           // far clipping plane

    // transformation matrix for world coordinates
    mat4x4 vp = projection * view; 

    // points (0,0,1), (0,0,1.5) and (0,0,2) in homogeneous coordinates
    print_vec(vp * vec4(0.0f, 0.0f, 1.0f, 1.0f));
    print_vec(vp * vec4(0.0f, 0.0f, 1.5f, 1.0f));
    print_vec(vp * vec4(0.0f, 0.0f, 2.0f, 1.0f));
}

to print out this

(0, 0, -1)
(0, 0, 0)
(0, 0, 1)

But it doesn't. It prints out this:

(0, 0, -1)
(0, 0, 0.5)
(0, 0, 2)

And I don't understand why. The near clip plane projects like I would expect, but a point exactly midway between the planes are closer to the back when projected, and a point on the far clip plane is outside the [-1,1] range entirely.

I figure there's some basic math thing i'm just missing, so that's why I'm asking you fine fellows.

(incidentally, the thing I'm working on is actually written in Rust, not C++, but I switched to C++ for this test, just to be sure there wasn't a bug in the Rust implementation or something.)


Solution

  • But it doesn't. It prints out this:

    (0, 0, -1)
    (0, 0, 0.5)
    (0, 0, 2)
    

    That is wrong, because the result is not of type vec3, it is a Homogeneous coordinate of type vec4:

    (0, 0, -1, 1)
    (0, 0, 0.5, 1.5)
    (0, 0, 2, 2)
    

    You have to do a Perspective divide then you' ll get the expected result:

    (0/1,   0/1,   -1/1)
    (0/1.5, 0/1.5,  0.5/1.5)
    (0/2,   0/2,    2/2)