By Benjamin Wrensch
Summary: Cheatsheet for getting Reverse Z integrated into your codebase quickly. Reverse Z can be used to better utilize the available precision of the depth buffer and thus to aid larger rendering distances without running into Z-fighting issues quickly.
Just recently I've started looking into ways to optimize the depth buffer precision for large draw distances and one specific approach caught my eye again. A technique that is now commonly referred to as Reverse Z. While it comes with just a few minor changes, the results can be quite considerable.
That considerable everyone should just go ahead and use it.
The general idea itself is actually very simple: instead of mapping the interval between the near and far plane $[z_n, z_f]$ to $[0, 1]$, a special projection matrix is constructed in a way that it is being mapped to $[1, 0]$ instead.
Why this increases the depth buffer precision is not directly obvious, but I will also not go into detail here. I've added some references to articles on this topic at the end of this post.
Simply put: this approach works very well in combination with floating-point depth formats (16- or ideally 32-bit) and it utilizes the high precision in the numerical area around zero for distant objects. Using it in conjunction with normalized integer formats will not yield the expected precision improvements.
Sadly the overall knowledge about this approach is still a bit scattered through the Net and I finally thought it's a good idea to put the most useful information in a single place. Thus I am going to focus on the following things:
... and here we go!
Let's start out with the well known perspective projection matrices for left- and right-handed coordinate systems:
We can remap the depth range from $[0, 1]$ to $[1, 0]$ by applying a simple transformation matrix
to both projection matrices respectively, yielding the final Reverse Z projection matrices:
Using the described projection matrices, while setting the near plane distance to $z_n=15$ and the far plane distance to $z_f=1000$, we are able to plot the following results:
It's also possible to completely remove the need for a fictional far plane and reduce potential rounding and truncation errors during projection and matrix concatenations to almost zero. To achieve this, we can simply assume that the far plane is infinitely far away, yielding:
Setting the near plane distance to $z_n=15$, plotting the results reveals the following:
Note how the function is not starting to touch the x-axis when reaching the previous far plane distance.
An infinite far plane might not be the perfect fit for everyone. It has to be correctly handled throughout the whole codebase, requiring a bit of extra care and potentially a few hacks in certain scenarios.
Swapping the projection matrix is the easy part and there is still a little bit of work left to do (depending on the codebase you are working with). You have to...
Multiplying with the inverse of the current projection matrix is a common approach to move the normalized post-projection coordinates back to view space.
But: in a lot of scenarios, it is already sufficient to apply this transformation to the $z$ component only and to skip the complexity of a complete matrix multiplication. This approach is often called linearizing the native depth values. We can deduce the transformation of the $z$ component by applying the inverse of the projection matrix, dividing by the $w$ component and simplifying the term of the $z$ component only:
Using this approach it is possible to derive the depth linearization functions fitting to each of the projection matrices depicted above:
The left-handed counterparts are simply the negation of the right-handed functions.
As a small side note, when using those functions in shaders: make sure to prepare as much as possible on the CPU side and to pass the prepared constants as uniform or constant buffer values. Taking the Reverse Z linearization function as an example:
... and that's all there is to it. To sum it up, the following plot shows the linearization functions with the near plane distance set to $z_n=15$ and the far plane distance set to $z_f=1000$:
Reverse Z
Projection Matrices