Device States (XNA Game Studio 4.0 Programming) Part 2

Premultiplied Alpha

Another change in this release of Game Studio involves the default mechanism used to perform alpha blending. In previous versions,”normal” interpolated blending was performed for alpha blending, which in the formula you’ve been using now was defined as:

tmpD-234_thumb_thumb

This was the standard way to declare transparency, but in Game Studio 4.0, the default changed to premultiplied alpha. This mode assumes that the color’s RGB values are multiplied by the alpha value before the blend takes place (hence, the term premultiplied), and the formula for this alpha blending is the following:

tmpD-235_thumb[2]

This is similar to the original one, but subtly different enough to matter. Game Studio’s defaults changed to use premultiplied alpha because (while it is not perfect) in the majority of cases, it is the blending mode that most developers actually want in the first place. Plus, it is easy to switch back to the old default way of blending.


What does it mean that Game Studio uses premultiplied alpha as the default? Look at the properties of the AlphaSprite.png that you added to your content project. Under the texture content processor, notice a property for PremultipliedAlpha that is set to true.

This means that during build time when the content pipeline is processing this texture, for each pixel that it processes it premultiplies the alpha in this formula:

tmpD-236_thumb[2]

Add the following code to your project to see how the alpha computation changes:

tmpD-237_thumb[2]

If you render this now, the source image has a black background where the alpha channel is when drawn opaque, with a transparent background and the image showing up alpha blended below it. Now, go to the properties of AlphaSprite.Png and change the PremultipliedAlpha value to false. Run the application, and notice that the top and bottom images look the same, and rather than a black background, it has a white background with no alpha blending at all.

I’m sure you can guess why. The blend operation expects the alpha value to be premul-tiplied, and it is not. The background turned white because without the premultiplication multiplying the 0 alpha value with the white RGB value, returning a 0 RGB value of black, it maintains the white color. However, add the following code to the project:

tmpD-238_thumb[2]

Because the image isn’t premultiplied anymore, this shows the image normally alpha blended. Lastly, switch the image back to PremultipliedAlpha true. Notice that now both premultiplied and non-premultiplied seem to render alpha blended, and appear to look identical.This is because this particular image only has an alpha value of completely on or completely off.There are no partial alpha values, so the two blend operations come up with the same values.This wouldn’t be the case otherwise.

Whew, that was a long discussion about blending.There are a number of built-in blend states for the more commonly used operations as well that are static objects off of the BlendState itself.These include Opaque, Alphablend, Additive, and Nonpremultiplied. After all that, running the app gives you quite a few examples of varying blend modes, much like you see in Figure 7.1.

DepthStencilState

Next up on the list is the depth stencil state.This state has all of the settings and properties required to control the depth and stencil buffers. The previous example is a little dry with just blending the colors, so start a new game project now. Add the depthmodel.fbx model to your content project, and add a few variables to the game, too:

tmpD-239_thumb[2]

 

An example of blending states

Figure 7.1 An example of blending states

Figure 7.1 An example of blending statesLoad the model and create the matrices, which you can do in your LoadContent overload:

tmpD-241_thumb[2]

Here, set up a basic camera, load the model, and turn on the lights on the model (because it would look boring and flat otherwise).To draw the model, replace your Draw overload with the following:

tmpD-242_thumb[2]

 

tmpD-243_thumb[2]

This code renders the model to the left of the screen with it slowly rotating around. Notice that the model is a donut-shaped object with four spheres spinning around with it along its outside border.You also should see that at the beginning of the method, you set the DepthStencilState property on the device, although you just set it to Default. In this particular case, it doesn’t matter if you don’t make this call.When you draw this model again to the right, it will matter.

What exactly is a depth buffer? Well, it’s a buffer to hold depth, of course. If you render a scene with the depth buffer enabled (which is the case by default), for every pixel that is rendered, the depth of that pixel is also stored.You can think of the depth as the distance from the camera. Something near the camera has a depth close to 0.0f, and objects far away have a depth close to 1.0f (the depth runs from 0.0f to 1.0f).

Each time a new pixel is rendered, the system checks whether a depth is written for the position already. If it is, it compares the depth of the current pixel with the one that is already stored, and depending on the function of the depth buffer (which is discussed later), it decides what to do with it. If, for example, the function is CompareFunction. LessEqual, it looks at the current pixel’s depth. If it is less than or equal to the stored pixel’s depth, it writes the new pixel to the buffer; otherwise, it discards it. This enables you to render scenes in 3D, with objects appearing behind (and occluded) by other objects.

To give you an idea of why having the depth buffer is so valuable in a 3D application, add the following code at the end of your Draw overload:

tmpD-244_thumb[2]

This turns the depth buffer completely off, and then draws the same model again to the right side of the screen, much like you see in Figure 7.2.

Notice that the model on the right looks odd.You can see the small spheres whether they’re in front of the torus or not.You can even see some of the inside of the torus through the other portions of itself.With no depth buffer, the last pixel drawn at a particular depth wins.

In the case of this model, it is composed of five different meshes (the torus, and each sphere is its own mesh).The torus is drawn first, and the spheres next, so the spheres always appear on top of the torus, as they’re drawn later. Depending on how the torus itself is drawn (and which angle it is at), certain pixels in the center of the torus might be drawn after pixels that would normally block it, so you can also get an odd visual with the center.

Rendering a scene with and without depth

Figure 7.2 Rendering a scene with and without depth

Before moving on to the specific properties of the DepthStencilState object, let’s take a look at the last static member.You used Default (which turns the depth buffer on) and None (which turns it off).The last one is DepthRead, which reads from the depth buffer, but doesn’t write to it.What does that exactly mean?

It means that it does the same operation as the default depth buffer stateā€”it rejects pixels that don’t pass the depth test, but when it finds a new pixel that does pass the depth test, it doesn’t write the pixel’s depth to the buffer. For example, add the following variable to your game:

tmpD-246_thumb[2]

Next, make one change to your current Draw overload, namely deleting the line that sets the graphics device to DepthStencilState.None before adding this code to the end of the method:

tmpD-247_thumb[2]

Notice that you’re calling a version of Clear that you haven’t seen before.You’re clearing again because you want to erase the two models you just drew, because for this example, they were to write only depth into the depth buffer. However, you cannot use the prototype of Clear that you normally use, because it also clears the depth buffer. So instead, tell Clear to clear only the ClearOptions.Target (the color buffer) to the normal CornflowerBlue, and the rest of the parameters are ignored because they depend on other ClearOptions that you didn’t specify.You can use ClearOptions.DepthBuffer to clear the depth and ClearOptions.Stencil to clear the stencil, with the latter two properties as the values to clear each buffer to respectively.

Draw the model again in the center of the screen (this is where you see the effect of DepthRead in a few moments).

Next, you use one of the more complex spriteBatch.Begin overloads. Aside from the DepthStencilState.Default, the rest of the parameters are the defaults for sprite batches though, so you can ignore those for now. Render the texture to cover the entire screen at a layer depth of 1.0f (the last parameter). Specify the Default depth state because sprite batch (in its default) turns the depth off. Because you turned it on for this sprite, rendering the texture tests its depth (1.0f) against all the pixels in the depth buffer.Any pixels where the models were drawn have a lesser depth, so the pixels in the texture are discarded and not drawn.As you can see in Figure 7.3, this is exactly what happens because you see a gray screen with the outline of the model’s circling there with your extra model floating in between them.

Cutouts using depth

Figure 7.3 Cutouts using depth

Notice that the middle (colored) model looks perfectly normal in the scene. It gets occluded by the cutouts sometimes (when they are behind it), but it shows up in front of the cutouts when it is closer to the camera. This is because it is writing its depth into the depth buffer.Add the following line of code directly after the second Clear call:

tmpD-249_thumb[2]

This is a different output than before. Because it is not writing its depth to the scene, the only places you actually see the rendered model are the portions inside the cutouts where it passed the depth test. So why does this happen?

Let’s trace the lifetime of a pixel through this scene. Imagine the pixel is one in which the colored model is drawn through the cutout and intersecting the left model. First, the scene is cleared; the pixel has the default color and a depth value of 1.0f. Next, the left model is drawn, so now the pixel has the left model color in it, and its depth, you can call it 0.1f to have a nice round number.The right model is drawn next, but it doesn’t affect the pixel, so now the clear call happens again. This time, the color is reset back to default, but the depth isn’t changed, so it’s still 0.1f. Now, the colored model is drawn, it has a depth of 0.09f and passes the pixel test, so the color is now updated, but you aren’t writing depth, so it is still 0.1f. Finally, the grey texture is drawn, and its depth of 1.0f doesn’t pass the depth test, so the colored model’s pixel stays there.

If you are looking at a pixel outside of the cutout, though, something else happens. First, the left and right model don’t intersect the pixel (otherwise, it would be in a cutout), so this pixel is cleared, has a depth of 1.0f, and then cleared again without modifying the depth. Next, the colored model is drawn; it passes the depth test, updates the pixel color, but hasn’t modified the depth, which is still 1.0f.When the sprite comes to render, its value of 1.0f passes the depth test, it overwrites the pixel color with its grey, and you get the scene you see when running this example.

There are only three properties on the DepthStencilState object that deal directly with the depth buffer, and they’re all basically covered by the static members you’ve used already. However, because you aren’t setting them directly, let’s take a look at what they are.

First, you have the DepthBufferEnable property, which enables (or disables) the depth buffer completely. The default value for this is true.You should note that everything in this state object is only meaningful if you actually have a depth buffer. If you don’t need a depth buffer (for example, a purely 2D game with no layered sprites), you can simply turn the depth buffer off for the entire game by changing your PreferredDepthStencil Format type to DepthFormat.None (the default is DepthFormat.Depth24) in your constructor:

tmpD-250_thumb[2]

Next is DepthBufferWriteEnable, which is by default true.This sets whether the system will take pixels that pass the depth test and write them to the depth buffer.You’ve seen the behavior when the write is false in the previous example.

The last depth buffer specific property is DepthBufferFunction, which has a default of CompareFunction.LessEqual.This enables you to dictate how the depth buffer decides whether to allow the pixel or not. By default, the depth buffer is cleared to a value of 1.0f

(this is the parameter of the Clear function called depth). Because this is the farthest away point in the depth buffer, anything within the viewing frustum passes the depth test at first. However, you can change this behavior with the various values of the CompareFunction enumeration.

The rest of the properties deal with the stencil portion of the depth buffer, but first, let’s take a bit of time to talk about render targets.

Next post:

Previous post: