2
\$\begingroup\$

I am trying to display camera frustum in my OpenGL application. I calculate the vertices of the frustum pyramid using reverse projection from screen space to world space. The camera uses perspective projection, but what is rendered looks like an orthogonal projection.

From my understanding, if any point in world space coordinates can be transformed to a screen space coordinates using matrix multiplication as following

$$ v_{screen} = P \times V \times M \times v_{model} $$

where:

  • \$P\$ is camera projection matrix
  • \$V\$ is a camera view matrix
  • \$M\$ is a model transformation matrix
  • \$v_{screen}\$ is a 4-dimensional vector in screen space
  • \$v_{model}\$ is a 4-dimensional vector in model space

So in order to obtain camera frustum vertices in world space, I solve the above equation for \$M \times v_{model}\$, which is same as saying \$v_{world}\$, since model transformation here is none (identity matrix):

$$ v_{world} = PV^{-1} \times v_{screen} $$

And, as figured out on forums online, the resulting vertex, a 4-dimentional vector, would have the \$w\$ component, which, if omitted (as I did initially), would result in data loss and thus incorrect results being displayed.

So instead of naive

auto vertex = glm::vec3(glm::inverse(cameraProjection * cameraView) * glm::vec4(-1.0f, -1.0f, 0.0f, 1.0f));

I am now doing

auto p = glm::inverse(cameraProjection * cameraView) * glm::vec4(-1.0f, -1.0f, 0.0f, 1.0f);

auto vertex = glm::vec3(p) / p.w;

Just for a sake of context, the camera transformation matrices are defined as

const float fov = 45.0f;

glm::vec3 cameraPos = glm::vec3(0.0f, 1.0f, 3.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::vec3(1.0f, 0.0f, 0.0f);
glm::vec3 cameraForward = glm::normalize(glm::cross(cameraUp, cameraRight));

glm::mat4 initialCameraProjection = glm::perspective(glm::radians(fov), (float) window.getSize().x / (float) window.getSize().y, 0.1f, 100.0f);

glm::mat4 initialCameraView = glm::lookAt(
    cameraPos,
    cameraPos + cameraForward,
    cameraUp);

Then, using the list of vertices in screen space I map them onto vertices in world space using the inverse matrix approach from above:

std::array<glm::vec3, 8> _cameraFrustumCornerVertices{
    {
        { -1.0f, -1.0f, 1.0f }, { 1.0f, -1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f }, { -1.0f, 1.0f, 1.0f },
        { -1.0f, -1.0f, -1.0f }, { 1.0f, -1.0f, -1.0f }, { 1.0f, 1.0f, -1.0f }, { -1.0f, 1.0f, -1.0f },
    }
};

const auto proj = glm::inverse(initialCameraProjection * initialCameraView);

const auto proj = glm::inverse(initialCameraProjection * initialCameraView);
std::array<glm::vec3, 8> _frustumVertices;

std::transform(
    _cameraFrustumCornerVertices.begin(),
    _cameraFrustumCornerVertices.end(),
    _frustumVertices.begin(),
    [&](glm::vec3 p) {
        auto v = proj * glm::vec4(p, 1.0f);
        return glm::vec3(v) / v.w;
    }
);

And then display the pyramid in violet color:

incorrect camera frustum, screenshot 2

As you can see, the frustum looks nothing like a cut pyramid, but rather like an orthogonal projection.

Moreover, the view from the camera itself (using the initialCameraView and initialCameraProjection) does not align with what is rendered as a "frustum":

incorrect camera frustum, screenshot 1

I bet the issue is with my math, but I can't think of anything else that could cause these errors. Where is my mistake?

\$\endgroup\$
2
\$\begingroup\$

If I was not this blind, I would notice: the math is perfectly fine. I am transforming the data correctly. But I was not using the transformed data - I was rendering the original vertices in screen space instead.

enter image description here enter image description here enter image description here

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.