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:
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":
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?