Reputation: 3426
I'm running into contradictions while attempting to build a projection matrix for Vulkan, and have yet to find an explanation for how the projection matrix should map Z from input vector to the output. Mapping x and y is straightforward. My understanding is that OpenGL Projection matrices should map the near frustum plane to -1, and far to +1. Vulkan to 0 and +1 respectively. The mapping should be logarithmic, allowing greater precision in the near field.
The examples below use near (n) = 1, far (f) = 100. Here's a plot of z mapping using a matrix I constructed to the Vulkan spec. It produces errors in rendering, but produces the correct result as I understand it:
lambda z: (f / (f-n) * z - f*n/(f-n)) / z
A plot of the most common OpenGL projection I've found online, which should map from -1 to +1:
lambda z: ((-f+n)/(f-n)*z - 2*f*n/(f-n))/-z
And here's one generated from a lib I use, for OpenGL (cgmath in Rust):
I'm unable to build a proper Vulkan projection matrix (Of which I've found none via Google) unless I understand what z should map to. I suspect this is due to an implicit correction done post projection matrix by the shader that actually maps to the ranges I listed, but if so, I don't know what range to feed into it via the proj mat.
Upvotes: 10
Views: 1351
Reputation: 45342
The mapping should be logarithmic, allowing greater precision in the near field.
Actually, if you don't do any tricks, the mapping will be hyperbolic, not logarithmic. The key point about the hyperbolic mapping is that you can actually interpolate it linear in screen space (which is a very nice property when you want to do some Z buffer optimizations like Hierarchical Z).
A plot of the most common OpenGL projection I've found online, which should map from -1 to +1:
lambda z: ((-f+n)/(f-n)*z - 2*f*n/(f-n))/-z
Nope. You have a sign error in the first term, it should be
(-(f+n)/(f-n)*z - 2*f*n/(f-n))/-z
Hence, your plot is just wrong. With the corrected formula, you would get a plot similiar to your cgmath
rust library.
But the important bit is something else: you are plotting the wrong thing!
Note the -z
on denominator in that formula? Classic GL convention has always been to use a right handed eye space, but left handed window space. As a result, a classic GL projection matrix projects along -z
direction. The parameters n
and f
are still given as distances along the viewing direction, though. This means, that the actual clip planes will be at z_eye = -n
and z_eye=-f
in eye space. What you plotted in your graph is the range behind the camera, and you will see the second branch of the hyperbole, the one which is usually clipped ayway, and which will map to outside of the [-1,1]
interval.
If you plot the mapping for n=5 and f=100, you would get:
Note that OpenGL's project into -z
direction is pure convention, it is not enforced by anything, so you can use +z
projection matrix as well.
I'm unable to build a proper Vulkan projection matrix (Of which I've found none via Google) unless I understand what z should map to. Here's a plot of z mapping using a matrix I constructed to the Vulkan spec. It produces errors in rendering, but produces the correct result as I understand it:
lambda z: (f / (f-n) * z - f*n/(f-n)) / z
Not sure what errors you're seeing with this, but the mapping is correct if you
+z
in eye space.Btw. vulkans [0,1]
clip convention also makes it possible to better use the precisions when using a floating point depth attachment: By reversing the mapping so that the near plane is mapped to 1, and the far plane mapped to 0, you can improve precision considerably. Have a look at this nvidia devblog article for more details.
Upvotes: 10