A z-buffer, also known as a depth buffer, is a type of data buffer used in computer graphics to store the depth information of fragments. The values stored represent the distance to the camera, with 0 being the closest. The encoding scheme may be flipped with the highest number being the value closest to camera.
In a 3D-rendering pipeline, when an object is projected on the screen, the depth (z-value) of a generated fragment in the projected screen image is compared to the value already stored in the buffer ( depth test), and replaces it if the new value is closer. It works in tandem with the rasterizer, which computes the colored values. The fragment output by the rasterizer is saved if it is not overlapped by another fragment.
Z-buffering is a technique used in almost all contemporary computers, laptops, and mobile phones for generating 3D computer graphics. The primary use now is for video games, which require fast and accurate processing of 3D scenes.
Each time an object is rendered into the framebuffer the z-buffer is used to compare the z-values of the fragments with the z-value already in the z-buffer (i.e., check what is closer), if the new z-value is closer than the old value, the fragment is written into the framebuffer and this new closer value is written into the z-buffer. If the z-value is further away than the value in the z-buffer, the fragment is discarded. This is repeated for all objects and surfaces in the scene (often in parallel). In the end, the z-buffer will allow correct reproduction of the usual depth perception: a close object hides one further away. This is called z-culling.
The granularity of a z-buffer has a great influence on the scene quality: the traditional 16-bit z-buffer can result in artifacts (called "z-fighting" or stitching) when two objects are very close to each other. A more modern 24-bit or 32-bit z-buffer behaves much better, although the problem cannot be eliminated without additional algorithms. An 8-bit z-buffer is almost never used since it has too little precision.
Z-buffers are often implemented in hardware within consumer graphics cards. Z-buffering is also used (implemented as software as opposed to hardware) for producing computer-generated special effects for films.
At the start of a new scene, the z-buffer must be cleared to a defined value, usually 1.0, because this value is the upper limit (on a scale of 0 to 1) of depth, meaning that no object is present at this point through the viewing frustum.
The invention of the z-buffer concept is most often attributed to Edwin Catmull, although Wolfgang Straßer described this idea in his 1974 Ph.D. thesis months before Catmull's invention.
On more recent PC graphics cards (1999–2005), z-buffer management uses a significant chunk of the available memory bandwidth. Various methods have been employed to reduce the performance cost of z-buffering, such as lossless compression (computer resources to compress/decompress are cheaper than bandwidth) and ultra-fast hardware z-clear that makes obsolete the "one frame positive, one frame negative" trick (skipping inter-frame clear altogether using signed numbers to cleverly check depths).
Some games, notably several games later in the Nintendo 64's life cycle, decided to either minimize z-buffering (for example, rendering the background first without z-buffering and only using z-buffering for the foreground objects) or to omit it entirely, to reduce memory bandwidth requirements and memory requirements respectively. Super Smash Bros. and F-Zero X are two Nintendo 64 games that minimized z-buffering to increase Frame rate. Several Factor 5 games also minimized or omitted z-buffering. On the Nintendo 64 z-Buffering can consume up to 4x as much bandwidth as opposed to not using z-buffering.
on PC supported resolutions up to 800x600 on the original 4 MB 3dfx Voodoo due to not using z-buffering.
When using a z-buffer, a pixel can be culled (discarded) as soon as its depth is known, which makes it possible to skip the entire process of lighting and Texture mapping a pixel that would not be visible anyway. Also, time-consuming will generally not be executed for the culled pixels. This makes z-culling a good optimization candidate in situations where fillrate, lighting, texturing, or pixel shaders are the main bottlenecks.
While z-buffering allows the geometry to be unsorted, sorting polygons by increasing depth (thus using a reverse painter's algorithm) allows each screen pixel to be rendered fewer times. This can increase performance in fillrate-limited scenes with large amounts of overdraw, but if not combined with z-buffering it suffers from severe problems such as:
After a perspective transformation, the new value of , or , is defined by:
\frac{\textit{far} + \textit{near}}{\textit{far} - \textit{near}} + \frac{1}{z} \left(\frac{-2 \cdot \textit{far} \cdot \textit{near}}{\textit{far} - \textit{near}}\right)
After an orthographic projection, the new value of , or , is defined by:
2 \cdot \frac{\textit{far}-\textit{near}} - 1
where is the old value of in camera space, and is sometimes called or .
The resulting values of are normalized between the values of -1 and 1, where the plane is at -1 and the plane is at 1. Values outside of this range correspond to points which are not in the viewing frustum, and shouldn't be rendered.
\frac{\textit{far} + \textit{near}}{2 \cdot \left( \textit{far} - \textit{near} \right)} + \frac{1}{2} + \frac{1}{z} \left(\frac{-\textit{far} \cdot \textit{near}}{\textit{far} - \textit{near}}\right)
Simplifying:
\frac{\textit{far}}{\left( \textit{far} - \textit{near} \right)} + \frac{1}{z} \left(\frac{-\textit{far} \cdot \textit{near}}{\textit{far} - \textit{near}}\right)
Second, the above formula is multiplied by where d is the depth of the z-buffer (usually 16, 24 or 32 bits) and rounding the result to an integer:
\left(2^d - 1\right) \cdot \left(\frac{\textit{far}}{\left( \textit{far} - \textit{near} \right)} + \frac{1}{z} \left(\frac{-\textit{far} \cdot \textit{near}}{\textit{far} - \textit{near}}\right) \right)\right\rfloor
This formula can be inverted and derived in order to calculate the z-buffer resolution (the 'granularity' mentioned earlier). The inverse of the above :
\frac{-\textit{far} \cdot \textit{near}}{\frac{z'}{S}\left(\textit{far} - \textit{near}\right) - \textit{far}} = \frac{-S \cdot \textit{far} \cdot \textit{near}}{z'\left(\textit{far} - \textit{near}\right) - \textit{far} \cdot S}
where
The z-buffer resolution in terms of camera space would be the incremental value resulted from the smallest change in the integer stored in the z-buffer, which is +1 or -1. Therefore, this resolution can be calculated from the derivative of as a function of :
\frac{-1 \cdot -1 \cdot S \cdot \textit{far} \cdot \textit{near}} {\left( z'\left(\textit{far} - \textit{near}\right) - \textit{far} \cdot S \right)^2} \cdot \left(\textit{far} - \textit{near}\right)
Expressing it back in camera space terms, by substituting by the above :
\frac{dz}{dz'} &= \frac{-1 \cdot -1 \cdot S \cdot \textit{far} \cdot \textit{near} \cdot \left(\textit{far} - \textit{near}\right)} {\left(S \cdot \left(\frac{-\textit{far} \cdot \textit{near}}{z} + \textit{far}\right) - \textit{far} \cdot S \right)^2} \\ &= \frac{\left(\textit{far} - \textit{near}\right) \cdot z^2}{S \cdot \textit{far} \cdot \textit{near}} \\ &= \frac{z^2}{S \cdot \textit{near}} - \frac{z^2}{S \cdot \textit{far}} \approx \frac{z^2}{S \cdot \textit{near}}\end{align}
This shows that the values of are grouped much more densely near the plane, and much more sparsely farther away, resulting in better precision closer to the camera. The smaller is, the less precision there is far away—having the plane set too closely is a common cause of undesirable rendering artifacts in more distant objects.
To implement a z-buffer, the values of are linearly interpolated across screen space between the vertices of the current polygon, and these intermediate values are generally stored in the z-buffer in fixed point format.
Whether a z-buffer or w-buffer results in a better image depends on the application.
// First of all, initialize the depth of each pixel. d(i, j) = infinite // Max length// Initialize the color value for each pixel to the background color c(i, j) = background color
// For each polygon, do the following steps : for (each pixel in polygon's projection) { // Find depth i.e, z of polygon // at (x, y) corresponding to pixel (i, j) if (z < d(i, j)) { d(i, j) = z; c(i, j) = color; } }
|
|