Render Targets (XNA Game Studio 4.0 Programming)

Up until this point, the rendering has been directly onto the screen.You perform a rendering operation, and it appears on the screen.You can go so far as to say that the display was your render target (as in the target of your rendering).

What if you need to render something that you don’t need to show on the screen? You would need a different type of render target, and as luck would have it, there is a RenderTarget2D class available for you!

A RenderTarget2D object is a special kind of texture (it inherits from Texture2D) that enables you to use it as a source for rendering, instead of the device.This enables you to use the render target on your device, and then later, use that rendered scene as a texture. To help visualize this concept, create a new Game project and add the depthmodel.fbx from the downloadable examples to the Content project.

Add the following variables to the project so you can draw the model later and use your render target:

tmpD-251_thumb

Like always, initialize these as well by adding the following code in your LoadContent overload:


tmpD-252_thumb

Load the model and update the lights as you’ve done many times before. Next is the render target creation. There are three different overloads for creating a render target, with the first as the simplest by taking in the graphics device, the width, and the height.You don’t use this one here because a render target created with this overload does not have an associated depth buffer.

The difference between a render target and a texture is render targets can have associated depth buffers. If you think about it, it seems obvious because it can be a target of your rendering, and rendering 3D scenes without a depth buffer can give you ugly scenes.

The overload for creation used here includes the three parameters, and a Boolean parameter to dictate whether the render target includes mipmaps (which are discussed later this topic), followed by the color format and depth format for the render target. Notice that this example uses a 256×256 render target. Render targets do not have to be the same size as your display or back buffer. This size was chosen because later in the example, it is rendered overlaid on the screen.

Understanding The RenderTargetUsage Options

The last overload includes the parameter to specify RenderTargetUsage. This enumeration has three values: PreserveContents, PlatformContents, and DiscardContents. The default value for new render targets is DiscardContents, which means whenever a render target is set onto the device its previous contents are destroyed. The framework attempts to help you realize this by clearing the buffer to a solid purple color, a familiar color when working with render targets. If you choose to use PreserveContents, then the data associated with the render target is maintained when you switch to and from the render target. Be warned though, that this can have a significant performance impact because, in many cases, it requires storing the data, and then copying it all back into the render target when you use it again. At a high level, the PlatformContents chooses between the other two depending on the platform, preserving the data on Windows in most cases, and discarding it on Windows Phone and Xbox 360.

Note

The RenderTargetUsage option can be specified for the device as well if you want your device to have preserve semantics.

To show the rendering into the render target, render the model multiple times, and add the following helper method to encapsulate the operation:

tmpD-253_thumb

This sets up a simple rotating world matrix, turns the depth buffer back to its defaults (you render via the default sprite batch later), and draws the model.This is not fancy, but let’s call it. Replace your Draw overload with the following:

tmpD-254_thumb

There’s nothing here that’s extremely new and exciting, unless of course you consider the different clear color to be new and exciting. Running the example now shows you the model spinning in the center of the screen.Well, the aspect ratio is different too, because you use this (in a moment) on a render target with a 1.0 aspect ratio (width and height are the same). Now, switch to your render target before you call clear using the following line:

tmpD-255_thumb

Note

You can set up to four render targets at a time for HiDef projects by using the SetRenderTargets method instead.

You probably shouldn’t run the example now, because you’ll just get an exception complaining that you can’t call Present when a render target is set.To unset the render target when you’re done, add the following to the end of your Draw method: GraphicsDevice.SetRenderTarget(null);

With your rendering now happening onto a separate render target, when you run the example you get a purple screen. As mentioned earlier, this is because your render target (and the device) each have a usage of DiscardContents.When a render target is set with this usage, its contents are cleared to a purple color. In this case, when you set the render target back to null (the device’s back buffer), its contents are cleared and you see purple. Add the following to the end of your Draw method:

tmpD-256_thumb

Notice that the view matrix has changed so you’re looking at the model from a different direction. However, when you run the example, the model spins in the middle of the scene, and there is only one copy of it. It is as if the first version you rendered vanished, which is somewhat true.

Remember earlier when the RenderTarget2D object was just a special kind of texture? That means you can use it exactly as you would a texture! So, add the following to the end of the Draw method in your example to show both views of the model rendering at the same time:

tmpD-257_thumb

There it is! Now you can see that the model you drew originally is now in the upper left corner of the screen as in Figure 7.4, and the second model is still spinning in the center. Also, notice that they’re viewed from different angles.

Seeing two views from a render target

Figure 7.4 Seeing two views from a render target

Note

Just like there is a RenderTarget2D, there is also a RenderTargetCube, which derives from TextureCube.

Faking a Shadow with a Depth Buffer and Render Targets

Why would you want to render something not on the screen? There are quite a few techniques you can use with this type of capability. Let’s take a look at one now, by using what you learned so far in this topic with the depth buffer and the render targets to fake a shadow on a scene. Create a new game project to get started.

The model you are using so far works well for casting shadows, so add depthmodel.fbx to your content project. For something to render the shadow onto, add dualtextureplane.fbx and ground.jpg to your content project. Open the Content

Processor property for dualtextureplane.fbx, and change the Default Effect property to DualTextureEffect, because you will render the shadows as a second texture on the plane. Add the following variables to your game:

tmpD-259_thumb

The render target object is used to store the shadow data (which manifests itself like the light map in the previous topic).You also need a small texture to form the cutout of your objects, along with the ground texture.You can initialize these in your LoadContent overload:

tmpD-260_thumb

Most of this should look familiar. Note the size of the render target.The smaller the size, the less memory it takes naturally, but you can also get some visual artifacts for having so few pixels to work with. So instead, a medium-sized render target is created. Lower the size from 512 to something much lower (say 64) after the example is complete, and notice how your shadow appears pixelated. Now, because you render the torus models a few times, add the following helper method to your class:

tmpD-261_thumb

This draw your two models side by side—one to the left and one to the right. Now, let’s take a few different techniques you’ve seen over the last couple topics and combine them into something awesome. Earlier in this topic, we discussed using the depth buffer and extra clearing to draw a cutout of an object.This is the basis of your shadows, so you can draw these onto your render target. Replace your Draw method with the following:

tmpD-262_thumb

After setting the render target to a custom one, clear the buffer as normal and call your RenderScene helper to draw your two models on the screen.Then, clear the color buffer again (but not the depth buffer) to a dark gray color.This is the color of your cutouts, because right after this you draw your simple sprite across the entire render target. If you remember from your LoadContent method, your sprite texture is a light gray, which means the render target after this is a light gray solid color with a couple of dark gray cutouts of your model.

Remember that a common usage of DualTextureEffect is to render lightmaps onto objects, and your render target now contains essentially just that. Add the following to the end of your Draw method to complete rendering of the scene:

tmpD-263_thumb

 

tmpD-264_thumb

With everything in place, it’s simply a matter of rendering your scene again, setting the textures on your DualTextureEffect instances on the mesh, and rendering the ground model (that uses those textures) slightly lower in the scene.Your objects now have realtime shadow’s as in Figure 7.5.

Using the depth buffer to form shadows

Figure 7.5 Using the depth buffer to form shadows

Next post:

Previous post: