Sunstone dev log 16: depth in depth


I fixed the remaining z-fighting using logarithmic depth. It’s a very small change, so I spent a day and a half understanding how it actually works. The next most obvious visual artifact is moiré patterns on the textures at a distance, which I think I can fix with anti-aliasing.
Depth buffer resolution analysis
After perspective transformation , a vertex looks something like this (assuming view space’s +Z axis points into the screen):
where is the distance to the near plane, and is the distance to the far plane.
Perspective division brings the final Z coordinate to
So is the value stored in a depth buffer for a perspective-transformed vertex with view space depth , assuming (otherwise the vertex is clipped).
Let’s plot this relationship for for . This and are the settings for the camera in the Bistro scene I loaded in my last Sunstone dev log.

Er, that’s just a horizontal line? Let’s zoom in to :

Okay, it has some shape now. Let’s enhance:

You can finally see the x-intercept, where .
On the far right of the graph, we can see that .
VK_FORMAT_D16_UNORM uses 16 bits for storage, meaning there are depth values,
where minimum depth is represented by 0 and maximum depth is represented by 65535.
That means are used to represent the depth for ,
with the remaining values representing depths for .
Invert to get
and we can find the after which there are only 100 16-bit depth values remaining:
or 10 values remaining:
If we’re measuring distances in metres, this means there are only 100 depth “buckets” for points between 65cm and 1km away, and 90 of them are used to bucket points from 65cm to 6.5m away. By the same logic, points from 6.5m away to 7.2m () all share the same depth value, as do points from 7.2m to 8.1m (). Finally, points further than 61.5m () are all considered to have maximum depth.
Logarithmic depth is the classic solution. It’s super simple to implement, but before I do I want to spend some time understanding exactly what’s going on.
I want to start with the idea of “resolution”. The article linked above gives a table that maps distance from the camera (in metres) to “resolution” (also in metres). My guess is that it’s answering the question of, at a given distance, how much change in distance is required to observe a change in depth . The particular that’s we’re interested in depends on the number of bits in a depth value. A 16-bit depth buffer has possible depth values, which means we can only store outputs of in multiples of :
- represents
- represents
- represents
- represents
I want to know, at a certain distance , the that would cause the depth buffer value to increase by 1. In other words, which gives ?
All these differences are making me think of derivatives. We know that the derivative of is roughly the ratio of to :
So it should be the case that
And for
The derivative of is
So we have
For and , this gives
| z | Δz |
|---|---|
| 0.001 | 1.5259e-8 |
| 0.01 | 1.5259e-6 |
| 0.1 | 1.5259e-4 |
| 1 | 1.5259e-2 |
| 10 | 1.5259 |
| 100 | 152.59 |
| 1000 | 15259 |
So at 10m away, objects less than 1.5m apart will appear to have the same depth value. Here’s the full plot:

My interpretation of this curve is that the ability to discriminate between distances degrades much faster than the distance increases. We can tell the difference between and (because , so they’re assigned different depth values), but we can’t tell the difference between and (because , so they’re assigned the same depth value).
The logarithmic depth solution says that we should be able to discriminate proportional distances, no matter how far away they are. So if we can discriminate and , then we should also be able to discriminate and , and , etc. In other words, . We already have , therefore we need . This is where the logarithm comes in: the derivative of is proportional to .
This gives us a system of equations we can solve:
Integrate :
Solve for :
Solve for :
Substitute:
The formula for in terms of this is:
Giving us
| z | Δz |
|---|---|
| 0.001 | 2.1081e-7 |
| 0.01 | 2.1081e-6 |
| 0.1 | 2.1081e-5 |
| 1 | 2.1081e-4 |
| 10 | 2.1081e-3 |
| 100 | 2.1081e-2 |
| 1000 | 0.21081 |
with plot

This gives us sub-millimetre depth discrimination at 1m away. Surely that’s more than enough?
Next steps
- Review/clean up model loading code
- Shade with physical units
- Turn on MSAA
References
Blinn, J. F., & Newell, M. E. (1978, August). Clipping using homogeneous coordinates. In Proceedings of the 5th annual conference on Computer graphics and interactive techniques (pp. 245-251).
The Math behind (most) 3D games - Perspective Projection - YouTube