2
$\begingroup$

Hi I have a slight confusion in using the opengl perspective matrix in vulkan. glm's perspective matrix works directly in vulkan just by multiplying the "[1,1 term by -1 but when I compared the formula for creation of perspective matrix for both the apis.

opengl one here:http://www.songho.ca/opengl/gl_projectionmatrix.html

vulkan here:https://www.youtube.com/watch?v=U0_ONQQ5ZNM( at 12:10)

and I see 2 differences: 1st at 1,1 2nd at 2,3: here is my doubt(opengl's is 2 times the one in vulkan.

So how does glm's matrix works for my vulkan program just by correcting [1,1 location? Please do correct me if I am making some mistake in understanding the differences in matrix.

openglMarix talking about:enter image description here

$\endgroup$
1
  • 3
    $\begingroup$ Could you excerpt the specific formulas you're talking about and put them in your question? There are multiple formulas in the page you linked and the youtube video, and I don't know which ones you mean exactly. That said, the difference in [2][3] sounds related to depth range. Vulkan uses a 0 to 1 final depth range and OpenGL is −1 to 1. $\endgroup$ Commented Jan 11, 2022 at 4:46

3 Answers 3

3
$\begingroup$

Note that there is a little extra info here to (hopefully) provide the info need to understand the answer at the end. Plus I don't know of any sources that gives all the solutions in one place....

Vulkan clip space has [1, -1] for the y axis where OpenGL has [-1, 1] so just multiplying the perspective matrix [1][1] value by -1 will "correct" the OpenGL matrix to work with Vulkan. All it really does is flip the y axis. (or you can also use gl_Postion.y *= -1)

As of Vulkan 1.1 negative values are allowed when setting up the viewport which gives a fairly natural solution to the same problem.

For the depth range: Vulkan has a [0, 1] range for the Z axis in NDC space where OpenGL has [-1, 1] range (by default). There are a couple ways to handle this. You can remap the OpenGL range to the Vulkan range fairly easily with:

gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0;

The other option is to modify the perspective matrix to match up with Vulkan. The Vulkan approach (which is also used in directx) maps to floating point numbers better since floating point numbers have more accuracy near zero. Also, some depth conversion code is simplified using a range of [0,1]. Finally, using a range [0,1] allows for "reverse Z" (improving floating point number distribution)

Here is some code for generating a Vulkan friendly perspective matrix. Note that this code looks a lot simpler then what GLM uses because it expects a symmetric frustum. This code is copied from the FGED website...mostly

mat4 MakeProjectionMatrix(float fovy_rads, float s, float near, float far)
{
    float g = 1.0f / tan(fovy_rads * 0.5);
    float k = far / (far - near);

    return mat4(g / s,  0.0f,   0.0f,   0.0f,
                 0.0f,  g,      0.0f,   0.0f,
                 0.0f,  0.0f,   k,      -near * k,
                 0.0f,  0.0f,   1.0f,   0.0f);
}

(This solution does not flip the y-axis.)

Finally: A perspective matrix setup for OpenGL will work in Vulkan but it has limitations. The y axis will be flipped and objects falling in the [0,1] range will be visible. But objects falling in the [-1,0) range will not. Since Vulkan has a depth range of [0,1] it is easy to get the "illusion" that it is fully functional since the camera will appear to map correctly to the full depth range.

$\endgroup$
3
$\begingroup$

(I don't have enough rep yet to add a comment)

@pmw1234's explanation and solution work well. However, instead of writing out the perspective matrix by hand to handle the [0,1] Z-range of vulkan clip space, I found it easier to simply define GLM_FORCE_DEPTH_ZERO_TO_ONE for my project, e.g. in CMake, adding the line:

target_compile_definitions(<target_name> PUBLIC GLM_FORCE_DEPTH_ZERO_TO_ONE)

In summary, I could define my camera's projection matrix as the following, given the viewport's width and height, the "zoom" value for the camera, and the near/far plane:

glm::mat4 proj = glm::perspective(glm::radians(Zoom), 
    static_cast<float>(ScreenWidth) / ScreenHeight, Near, Far);
proj[1][1] *= -1;
$\endgroup$
2
$\begingroup$

If you are using the GLM library, you can define GLM_FORCE_DEPTH_ZERO_TO_ONE, as deabird11 said.

But you can also call specific functions suited for the projection of each API.

  • For OpenGL call: glm::perspectiveRH_NO or glm::perspectiveNO, or simply glm::perspective
  • For Vulkan call: glm::perspectiveRH_ZO or glm::perspectiveZO

This could be useful if you are making an engine that supports both OpenGL and Vulkan (using a define would lock you to use one convention).

You still need to invert the Y: proj[1][1] *= -1; for Vulkan. After all, GLM is designed for OpenGL.

I recommend reading this article for a good explanation on the differences between OpenGL and Vulkan coordinate systems: https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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