Sunday, May 1, 2011

How depth and stencil testing work, the DepthStencilState object

You probably have noticed that when more than one object is drawn in the same place on the GPU the closer object is the one we will see on screen. That makes sense right? You can't see objects that are behind other objects (I'm talking about opaque objects).  This is the result of the depth test. The GPU looks at the different pixels and compares their respective depth values, the smaller (closer) ones get drawn while the others are discarded. We can change this behavior and set up another one, the stencil test, which is completely configurable.

The depth test is very simple, it just compares the value found in the depth buffer with the depth value of the current pixel, if it passes it is drawn else it is discarded. The depth buffer is a texture buffer that has the same size as your game windows. It is bound to the target (the buffer with colors in it) and stores the depth values of pixels. These depth values range from 0 to 1 with 0 being the closest possible pixel (near plane) and 1 the farthest possible pixel (far plane). Each pixel (when it is rendered) also has an associated depth value (by default it is computed for us but we can change it in the pixel shader if we really want). When we clear the depth buffer we set everything to 1, this is the furthest possible and ensures than anything in the view space can be drawn. The settings in the DepthStencilState we have for the depth test are:
  • DepthBufferEnable
  • DepthBufferFunction
  • DepthBufferWriteEnable
The way they control the depth buffer is very simple but very powerfull: DepthBufferEnable is a boolean that tells us if depth testing (AKA depth buffering) is enabled, is true then the check is perfomed else everything is rendered in the order that the GPU receives the data. DepthBufferFunction is a member of the CompareFunction enumeration (the names are VERY self explanatory) and tells us how the values are compared (in currentDepth VS depthBuffer order), these functions are actually the usual comparison operators and a few extra one like Always. The last one, DepthBufferWriteEnable is a boolean that tells us if we can write to the depth buffer. For example we should not write to the depth buffer while rendering a transparent objects as we may have an object behind it  rendered at a later time.

The stencil operation is a little different. It compares a reference value with the value in the stencil buffer. The stencil buffer holds integers and is modified by stencil operations. What happens is that the GPU checks reference VS value_in_stencil_buffer order and decides if it failed or succeed, we also set operations for these cases so the GPU knows what to do. The things we can set for stenciling are:
  • StencilEnable
  • StencilFunction
  • ReferenceStencil
  • StencilPass
  • StencilFail
  • StencilDepthBufferFail
  • TwoSidedStencilMode
  • CounterClockwiseStencilFunction
  • CounterClockwiseStencilPass
  • CounterClockwiseStencilFail
  • CounterClockwiseStencilDepthBufferFail
  • StencilMask
  • StencilWriteMask
Huh, a lot of the right? Well they're pretty simple. The first one, StencilEnable is a boolean that tells us if the stencil test is on or off. The StencilFunction is another member of the CompareFunction enumeration, this is the compare operator used in the stencil test. The ReferenceStencil is the value we are going to be comparing against. StencilPass is the operation to perform if the stencil and depth test pass. StencilFail is the operation to perform if the depth test passed but the stencil test failed. StencilDepthBufferFail is the operation to perform if the depth test failed but the stencil test passed. As you can see this gives us a lot of control over how we modify the stencil buffer, but all of these are for front facing polygons (polygons oriented towards us). We usually only draw these polygons, but sometimes we render all polygons and it may be of interest to control the stencil test for these too. To do this we have to set TwoSidedStencilMode to true, as this member is a boolean. The members starting with CounterClockwise are the stencil settings for back facing polygons, same as before. The StencilMask and StencilWriteMask are 2 masks used for reading and writing respectively stencil data. These 2 masks are 32 bit integers used to determine which bits are going to be used in the stencil test. Because we are AND-ing them to the values, if a 1 is set then we are going to use that bit in the test, if a 0 is set it means the GPU will ignore that bit.

I will now tell you what each item from the CompareFunction enumeration will return, considering A as the reference stencil (current pixel depth for depth test) and B as value in stencil buffer (value in depth buffer for depth test). With these in mind, the members of the CompareFunction enumeration are:
  • Always - TRUE
  • Equal  - A == B
  • Greater - A>B
  • GreaterEqual - A>=B
  • Less - A<B
  • LessEqual - A<=B
  • Never - FALSE
  • NotEqual - A!=B
Now the StencilOperation items:
  • Decrement - decreases the stencil buffer by 1, if the result is less than 0 it wraps to the maximum value
  • DecrementSaturation - decrease by 1, no wrap
  • Increment - increases by 1, if it goes over the max it wraps to 0
  • IncrementSaturation - increase by 1, no wrap
  • Invert - bitwise negation
  • Keep - does not change the value
  • Replace - replaces the value in the stencil buffer with the reference value
  • Zero - sets the stencil buffer to 0 at that point

No comments:

Post a Comment