Back to Device States (XNA Game Studio 4.0 Programming)

Along with the depth buffer, you can also have a portion of that reserved for the stencil buffer.This is similar to the depth buffer in that it is evaluated on a per pixel basis, using a series of tests. Let’s look at emulating the cutout you did earlier, but by using the stencil buffer instead.

The Stencil Buffer

Create a new Game project and add the depthmodel.fbx to your Content project.Then, add the following variables to your project:

tmpD-266_thumb

The last two are the states you use for the stencil buffer—the first to create the cutout, and the second to use the cutout to render. However, by default, your application doesn’t even have a stencil buffer; it has only a depth buffer. Go to your game’s constructor and add the following line to the end:

tmpD-267_thumb


This tells the runtime that you prefer a depth buffer that includes eight bits reserved for the stencil buffer. This is actually the only stencil buffer available in the XNA runtime. With that, instantiate your variables in the LoadContent overload:

This tells the runtime that you prefer a depth buffer that includes eight bits reserved for the stencil buffer. This is actually the only stencil buffer available in the XNA runtime. With that, instantiate your variables in the LoadContent overload:

tmpD-268

You should recognize the earlier portion of the snippet, but notice something new after creating the two DepthStencilState objects. Before looking at the drawing code (which is quite simple), let’s take a look at what these properties are actually doing.

Unlike the depth buffer, the stencil buffer is slightly more complicated than a simple comparison of two values (although, in many cases, it is the same).The basic formula for computing a stencil value is the following:

(ReferenceStencil & StencilMask) Function (StencilBufferValue & StencilMask)

If your mask value is always all bits, then this is a comparison of the reference value to the buffer value. As you see previously, you use the ReferenceStencil property to dictate what the value is.You have the same compare functions that you had with depth buffers, but you have a completely new set of operations you can do if the stencil has passed, found in the StencilOperation enumeration.You can use Replace, to put the reference value into the buffer, and you can use Zero, to put a value of zero in the buffer.You can choose to Keep the current buffer value as it is (the default value of the operation).You can choose to Invert the value in the buffer, or you can Increment or Decrement it.The saturate versions of these methods simply clamp the value at the maximum or zero, respectively.

By default, the stencil buffer is turned off, and cleared to a value of zero. So, in order to create the cutout, first turn on the stencil buffer.Then, change the CompareFunction to Always.This means for every pixel that is drawn, you perform the operation in the StencilPass property, which you choose as Replace.This replaces the buffer value with the ReferenceStencil value, which you’ve placed as one now.

This means that when you render the models later, using this depth state, the stencil buffer initially is completely zeros, but for every pixel it renders, that pixel’s stencil buffer value is updated to one. Next, look at the state you use to render the cutouts.

Again turn on stenciling, but this time set the compare function to Equal. Because your ReferenceStencil value is zero, any pixel with a stencil value other than zero will fail, and not be drawn. If the stencil value is zero, you keep the value because you specified the Keep operation. This means that when you render the second overlay sprite, it does not render the pixels where the model used to be because they have a stencil value of one. Every other pixel has a stencil value of zero.

Now that you have a basic understanding of the stencil buffer and its operation, replace your Draw overload with the following:

tmpD-269_thumb

 

tmpD-270_thumb

With that, you now render cutouts of your models using the stencil buffer as in Figure 7.6. It is trivial to update the example you used previously for shadows to mirror this functionality using the stencil buffer.

Using the stencil buffer to form cutouts

Figure 7.6 Using the stencil buffer to form cutouts

RasterizerState

The RasterizerState object enables you to have a measure of control over this process in a variety of ways.To see the kinds of controls you have, create a new Game project and add the depthmodel.fbx file to your Content project.Then, declare your instance variables for the game:

tmpD-272_thumb

The RasterizerState class has a few static members that you can use, but you also use two extra states in this example. Create the following objects in your LoadContent method:

tmpD-273_thumb

After the matrices and the model are created and the lighting initialized, create your two needed state objects.The first changes the fill mode to WireFrame.The only other option for this property is Solid, which is the default.When you’re rending an object with WireFrame enabled, you see only the triangles rendered, but not the pixels inside of them. For the next option, turn on the ScissorTestEnable (the default is false).This is discussed in a moment when you see what it does! Now replace your Draw method with the following:

tmpD-274_thumb

 

tmpD-275_thumb

Here, you draw the model four different times, each time with a different rasterization setting and in a different location. The first draw call uses the static RasterizerState.CullCounterClockwise member, which sets the CullMode property of the state to CullCounterClockwise.This also happens to be the default, so this first object appears exactly as it normally would if you hadn’t set this state. So what exactly does CullMode mean, and what does it do?

When the device renders a triangle, it has two sides, but for most 3D models, you can see only one side of a given triangle. So each triangle has a "front" face, the side you expect to see, and a "back" face, the side you probably won’t see.The CullMode property tells the device to rasterize only pixels for one of those faces.This is determined by the vertices of the triangles "winding order."Triangles have either a winding order of clockwise, or counterclockwise. CullCounterClockwise tells the device to cull (remove) faces with a winding order of counterclockwise. The default winding order for XNA applications is that the front-facing triangles are wound clockwise and back-facing triangles are wound-counterclockwise.

Next, draw the model to the right with the wireframe state. As you expect, the model is not drawn solid, but instead with the triangles being drawn, but not the pixels inside of it. This mode is called wireframe.

The bottom model is drawn with the opposite culling mode.While running the application, notice that it looks odd. This is because it’s rendering only the "wrong side" of each triangle, so you see what the model looks like almost inside out.You can also use the built-in static member RasterizerState.CullNone to render both sides of each triangle.

The last model (the one drawn at the top of the screen) simply turns on the scissor test. The scissor test tells the device to not render any pixels that are outside the ScissorRectangle property of the device (which by default is the same size as the currently applied render target or back buffer). In this example, you set the rectangle to be the entire width of the screen, but only the upper eighth portion, which causes the model to be cut in half.Why are the other three models still showing despite the fact you told the device to render only in the upper eighth of the screen? The other three models were rendered with a state that had the scissor test turned off.

When you run the example, you see each of the four rasterizer states, as shown in Figure 7.7.

Next post:

Previous post: