How can I find the pixel space coordinates of a 3D point – Part 3 – The Depth Buffer

In this opportunity, we are going to talk about the depth buffer. I recommend you to read Part 1 and Part 2 first.

I am a DirectX user, so all I will describe here is related to that API. OpenGL/Vulkan is similar, but I am not going to mention/explain them.

If you remember from Part 2, we had a point Pv (in view space) that we transformed to perspective projection space and then to Normalized Device Coordinates space (by perspective divide).

clip-space

And also, you should remember that our perspective projection matrix looked like the following

asd3

Why does Zndc lie in [0.0, 1.0]? Why is that information needed?

As we already mentioned in Part 2, DirectX’s Zndc maps to [0.0, 1.0] while OpenGL maps to [-1.0, 1.0] (and also we explained why it is better the DirectX approach). Many rendering algorithms use the z post-projection values to make depth comparisons. It is stored in a texture called depth buffer.

The depth buffer is a texture that contains depth information about a particular pixel. The possible depth values range from 0.0 to 1.0, where 0.0 denotes the closest an object in the view frustum can be to the viewer and 1.0 denotes the farthest an object in the view frustum can be from the viewer (this is true if you choose to match near z to 0.0 and far z to 1.0, we will talk about this later in this article (reversed-z)). If we have a back buffer (or frame buffer) of 1920 x 1080, then the depth buffer will have the same dimensions.

To understand why depth information is important, we are going to see an example. In the following image, you can see some objects partially obscure the objects behind them.

overlappingGeometry.png

To determine which fragments of an object are in front of another, a technique called depth buffering or Z-buffering is used. It works in the following way:

  1. Before any rendering takes place, the back buffer is cleared to a default color (black color, for example), and the depth buffer is cleared to a default value (usually, the farthest depth value a pixel can have)
  2. Geometry is sent to the GPU to be rendered. For each fragment it overlaps in the screen, a depth value is computed, and the depth test is performed. The depth test compares the depths of fragments competing to be written to a particular pixel location on the back buffer. The fragment with the depth value closest to the viewer wins, and that is the pixel that gets written to the back buffer. This makes sense because the fragment closest to the viewer obscures the fragments behind it.

You can see in the following images, a back buffer and its corresponding depth buffer.

backbufferanddepthbuffer

But, you can do the same by rendering geometry in order from farthest to nearest!

That is true, but that method has the problems of sorting large data set in back-to-front order and intersecting geometry. Also, graphics hardware gives us depth buffer for free.

What Z value is stored in depth buffer? Is it Zp, Zndc, Zv?

Zndc is stored in the depth buffer. We know that Zndc = Zp / Wp (where p is for perspective projection). If we multiply a point Pv by the perspective projection matrix at the beginning of this article, then

Zp= ( Zv * fZ / (fZ – nZ)) + (-fZ * nZ / (fZ – nZ))

Wp = Zv

If we have A = fZ / (fZ – nZ) and B = -fZ * nZ / (fZ – nZ), then

Zp = A * Zv + B

then

Zndc = A + B / Zv

As you can see, this is a linear mapping of 1 / Zv and fits naturally with the perspective projection (as we shown in the equations). Also, it is linear in screen space, and this is important because depth information is used in post-processing and to reconstruct position from depth in deferred shading techniques (Matt Pettineo explains this in detail in this article). Here you have a hlsl code to get Zv from Zndc:

hlsl

Why do we choose to do a z-buffering and not w-buffering?

In this article, you can read an excellent explanation. Some important things in the article are:

  • In the past, some hardware has supported W-buffer, but it is considered deprecated.
  • W is linear in view space, but it is not in screen space. Z is linear in screen space, but it is not linear in view space.
  • Z is cheaper to interpolate than W (Z does not have to be perspective corrected)

If we store depth in texture, what is the format of this texture?

In DirectX, you have DXGI_FORMATs to choose. They are the following:

  • DXGI_FORMAT_D32_FLOAT_S8X24_UINT: A 32-bit floating-point component, and two unsigned-integer components (with an additional 32 bits). This format supports 32-bit depth, 8-bit stencil, and 24 bits are unused
  • DXGI_FORMAT_D32_FLOAT: A single-component, 32-bit floating point format that supports 32 bits for depth.
  • DXGI_FORMAT_D24_UNORM_S8_UINT: A 32-bit z-buffer format that supports 24 bits for depth and 8 bits for the stencil.
  • DXGI_FORMAT_D16_UNORM: A single-component, 16-bit unsigned-normalized-integer format that supports 16 bits for depth.

How is depth value stored if we choose an unsigned-normalized-integer format?

In that case, the depth value will be (1 << N) * A + B / Zv

We mentioned that depth values are per pixel, then why should I choose a 32 bits format, if 16 bits format saves 50% of the storage? 

First of all, you should check your API format support and the hardware you are targeting. Then you need to take into account that you can end with precision issues with your depth buffer format. These precision issues will cause artifacts and flickering effects like the following:

Zv is proportional to the reciprocal of its depth buffer value, and its precision too. There is much more precision close to the eye and tiny precision off in the distance. Then some bits are wasted because they store unnecessary detail close to the near plane. You can see a Nathan Reed‘s image to understand how these values are distributed in 4 bits unsigned-normalized-integer depth buffer:

01-standard-with-lines

To understand the precision issues deeply you could have, and how to solve them, taking into account different configurations of bits and formats, you should check these excellent articles:

Depth Prevision Visualized – Nathan Reed

Learning to Love your Z-Buffer – Steve Baker

Maximizing Depth Buffer Range and Precision – Brano Kemen

Attack of The Depth Buffer – Matt Petineo

Tightening the Precision of Perspective Rendering – Paul Upchurch and Mathieu Desbrun

Some important points from them are:

  • Always put near plane as far from the eye as you can tolerate.
  • Use as many bits as possible for depth buffer.
  • Use floating point depth buffer with reversed-Z.
  • If you cannot use a floating point depth buffer, you should still use reversed-Z.
  • To minimize roundoff error
    • Use an infinite far plane.
    • Keep the perspective projection matrix separate from other matrices, and apply it in a separate operation in the vertex shader, rather than composing it in the view matrix.

An infinite far plane? Tell us more…

Remember from our perspective matrix from the beginning of this article, that we have

A = fZ / (fZ – nZ)

B = -fZ * nZ / (fZ – nZ)

If we decide to have an infinite far plane, then:

lim {fZ -> infinite} fZ / (fZ – nZ) = 1.0

lim {fZ -> infinite} -fZ * nZ / (fZ – nZ) = -nz

Then, our perspective projection matrix will be:

infinitematrix

Reversed-Z depth buffer? Tell us more…

In this case, you need to set the near plane distance to fZ and the far plane distance to nZ. You will end with the following values for A and B

A = nZ / (nZ – fZ)

B = -nZ * fZ / (nZ – fZ)

Then, our perspective projection matrix will be

reversez

and the following image shows a plot of a reversed-Z depth buffer

graph5.jpg

As you can seem it has a quasi-logarithmic distribution.

We are tired…

And this concludes these 3 articles about How can I find the pixel space coordinates of a 3D point?.

Hope you find it useful!

Advertisements

3 thoughts on “How can I find the pixel space coordinates of a 3D point – Part 3 – The Depth Buffer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s