As with all games, Z-buffering plays an important part when rendering the pixels to the screen. During the development of Britonia I have often run into a problems related to the Z-Buffer, so I thought it would be nice to share what I have learned about them and how to avoid these issues.
What are Depth Buffers?
Depth buffers (commonly referred to as Z- or W- buffers) are a means to check which pixels being rendered will occlude one another. This is achieved by creating a separate surface the same size as the back(colour-) buffer where the depth of each rendered pixel is stored. Then before any other colours competing for the same pixel are rendered, they must first pass the depth function test using the same position within the depth buffer. Typically this check is passed if the new depth is less than or equal to the current depth in the depth buffer, although it is possible to change the pass conditions.
Enabling and setting the depth function with the following:
// enable depth buffer checks GraphicsDevice.RenderState.DepthBufferEnable = true; // specify the pass criteria for pixels GraphicsDevice.RenderState.DepthBufferFunction = CompareFunction.LessEqual; // allow writing to the depth buffer GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
Z-buffering is when the z component of a transformed pixel is stored as the depth. This differs from W-buffering, which takes the homogeneous W-component as the depth value. Because most of today’s hardware has support for Z-buffering it is the more commonly of the two.
Depth buffer Formats and Resolution:
The precision that you will get from the depth buffer depends greatly on the format of the surface you choose to use as well as the near and far clip plane distances. Depth buffers are non linear, and the depth stored in the depth buffer is relative to the z coordinate. The maths can be a little confusing but put basically, you get more precision closer to the near plane as you do the further away you get. There is a Z-buffer calculator on this site here so you can test it out for yourself.
In that article he talks about the resolution of z, so instead of covering the same equation, I thought I’d do a little picture representation of the resolution of Z:
The graph above shows a 16-bit depth buffer. The closest depth (1.0f) has a value of 0, while the furthest depth (1000.0f) has a depth value of 65536 in the buffer. As you can see on the left of the x-axis although the increments in distance are only relatively small (1.1, 1.2 etc.), we actually have a huge difference in the depth values assigned to these positions, whilst towards the furthest distances, the differences in depth value is very small.
Hopefully you can see form this graph the importance of selecting as high a value as possible for the zNear distance. This is a pretty common mistake when starting out with directx/xna whereby the programmer defines a point extremely close to the camera for the zNear like 0.1f, but you can see from above that this wastes a lot of depth needlessly – especially considering that not many objects will actually be that close to the camera.
Similarly, you should also pick sensible values for the zFar distance.
Of course when rendering planets, the distances involved mean you would very quickly have problems considering the depths distances involved if you were to use constant values as the near and far plane distances. As you can imagine, this isn’t that difficult to overcome, but I wanted to mention it so you have a reference.
For each individual planet in Britonia, I dynamically adjust the near and far clip plane distances based on the closest point on the planet sphere and the furthest point. Even using 16-bit depth buffers and realistic planet sizes there are no visible artifacts (anymore).
Creating, Caching and Restoring the DepthBuffers
Creating a depth buffer is very straight forward. In XNA the depth buffer is created and on the graphics device like so:
Graphics.GraphicsDevice.DepthStencilBuffer = new DepthStencilBuffer(Graphics.GraphicsDevice, width, height, DepthFormat.Depth16);
It is worth noting that if you wish to use a stencil buffer when rendering your scene, then this shares the same surface as the depthbuffer, and you will have to sacrifice some bits per pixel for this stencil buffer. As example would be:
Graphics.GraphicsDevice.DepthStencilBuffer = new DepthStencilBuffer(Graphics.GraphicsDevice, width, height, DepthFormat.Depth15Stencil1);
The last thing I’d like to mention is the ability to store the depth buffer for your scenes, which can be restored and used again later. Again this is quite easy to do:
// Cache the current depth buffer DepthStencilBuffer old = GraphicsDevice.DepthStencilBuffer; // Set our custom depth buffer GraphicsDevice.DepthStencilBuffer = shadowDepthBuffer;
As an example of how this may be benefical: In britonia I store the depth buffer for each planet during the planet rendering phase. This is then later restored when rendering the ground objects on the closest planet. This is required because of the swapping of depth buffers with multiple planets as mentioned above.
I hope this article was at least partitially helpful.
If you feel like reading up some more, you can find the MSDN article here.