Rendering to Surfaces

Have you ever been playing a game, such as a racing game, and seen how you can turn on a "rear-view" mirror? Possibly you've played one of the kart racing games where there is a billboard showing the race in the background? These effects are normally achieved by rendering the same scene (most times with a different camera) into a texture. Actually, despite sounding somewhat difficult, this is in reality quite simple to do. You can once again take the example for loading a mesh from a file from Chapter 5, "Rendering with Meshes," and update it for this example.

We will naturally need to declare a new texture that we will be rendering to, among other things, so add the following declarations:

 private Texture renderTexture = null; private Surface renderSurface = null; private RenderToSurface rts = null; private const int RenderSurfaceSize = 128; 

Here we have declared the texture we will be rendering to, the actual surface we will be rendering to (which we will actually just get from the texture), and the helper class for rendering to a surface. We've also declared the size of the surfaces we'll be creating. In the InitializeGraphics method, we will want to hook the device reset event to give us a place to create our textures and surfaces, so replace the device constructor call with this:

 device = new Device(0, DeviceType.Hardware, this,       CreateFlags.SoftwareVertexProcessing, presentParams); device.DeviceReset +=new EventHandler(OnDeviceReset); OnDeviceReset(device, null); 

Now, add the actual event handler found in Listing 10.5.

Listing 10.5 Setting Default Options on Your Device
 private void OnDeviceReset(object sender, EventArgs e) {     Device dev = (Device)sender;     if (dev.DeviceCaps.VertexProcessingCaps.SupportsDirectionalLights)     {         uint maxLights = (uint)dev.DeviceCaps.MaxActiveLights;         if (maxLights > 0)         {             dev.Lights[0].Type = LightType.Directional;             dev.Lights[0].Diffuse = Color.White;             dev.Lights[0].Direction = new Vector3(0, -1, -1);             dev.Lights[0].Commit();             dev.Lights[0].Enabled = true;         }         if (maxLights > 1)         {             dev.Lights[1].Type = LightType.Directional;             dev.Lights[1].Diffuse = Color.White;             dev.Lights[1].Direction = new Vector3(0, -1, 1);             dev.Lights[1].Commit();             dev.Lights[1].Enabled = true;         }     }     rts = new RenderToSurface(dev, RenderSurfaceSize, RenderSurfaceSize,         Format.X8R8G8B8, true, DepthFormat.D16);     renderTexture = new Texture(dev, RenderSurfaceSize, RenderSurfaceSize, 1,         Usage.RenderTarget, Format.X8R8G8B8, Pool.Default);     renderSurface = renderTexture.GetSurfaceLevel(0); } 

Here, we will actually do our checks as we're supposed to. We will first detect whether the device supports directional lights. If it does, we will begin turning on our lights, assuming it supports enough active lights. We will have two directional lights, one shining toward the front of our model, the other shining toward the back.

After our lights have been created and turned on, we will create our helper class using the size of our constant. You'll notice that many of the parameters for this constructor match items you'd find in the device's presentation parameters. This example is just using well-known members, but it may be useful to actually use the same values from your present parameters structure.

Lastly, we create our texture. Notice that we've specified the usage to be a render target, since we plan on rendering to this texture soon. We also use the same format here as we did for our helper class. All render target textures must be in the default memory pool as we've done here. We also get the actual surface from the texture and store that as well.

Since we've got our lights set up in the device reset event handler, you should go ahead and remove them from the SetupCamera method already existing in this example. Now you will need to add a new method to render into our surface. You should use the method found in Listing 10.6.

Listing 10.6 Rendering to a Surface
 private void RenderIntoSurface() {     // Render to this surface     Viewport view = new Viewport();     view.Width = RenderSurfaceSize;     view.Height = RenderSurfaceSize;     view.MaxZ = 1.0f;     rts.BeginScene(renderSurface, view);     device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f,         0);     device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4,         this.Width / this.Height, 1.0f, 10000.0f);     device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, -580.0f),         new Vector3(), new Vector3(0, 1,0));     DrawMesh(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f,         angle / (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f);     DrawMesh(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f,         angle / (float)Math.PI * 4.0f, 150.0f, -100.0f, 175.0f);     rts.EndScene(Filter.None); } 

This looks quite a bit like our normal rendering method. We have a begin scene call, an end scene call, we set our camera transforms, and we draw our meshes. This looks similar because it is similar. When we render to a texture, we really do need to render the items of the scene we want to have shown in the texture. In our case, you'll notice our camera is just about the same, except it is on the other side of the model, so we should be seeing "behind" the model in our rendered texture. You'll also notice that we're rendering two models in this scene. We do this to give off the "fake" effect that there are two models in the world, and in the default view you can only see the one since the second is behind the camera. However, in the secondary view, with the camera facing the other way, you can see both models.

You'll notice that the BeginScene method takes the surface that we will be rendering to as an argument. Since we are using the surface we've retrieved from our texture, any updates to this surface will be reflected in our texture. The EndScene method allows you to pass in a mipmap filter that you wish to apply to the surface. Rather than worrying about the device capabilities, we will use no filtering for this example. One last thing to notice is that we've changed the clear color for our textured scene. We do this to show the obvious difference between the "real" scene, and the "other" scene.

Naturally, we will need to modify our rendering method slightly as well. First, we will want to render the scene to our texture before we even start rendering it for our main window. Add a call to our method as the very first item in your OnPaint method:

 RenderIntoSurface(); 

Finally, we will want to actually put the texture on the screen somehow. There is another helper class called the Sprite that we will discuss in a later chapter, but we will use it here since it allows an easy mechanism to draw a texture in screen coordinates. Add the following section of code before your end scene method:

 using (Sprite s = new Sprite(device)) {     s.Begin(SpriteFlags.None);     s.Draw(renderTexture, new Rectangle(0, 0, RenderSurfaceSize,          RenderSurfaceSize),  new Vector3(0, 0, 0), new Vector3(0, 0, 1.0f),         Color.White);     s.End(); } 

This code simply renders the entire texture's surface onto the screen in the upper left corner. Running the application now should show you something similar to what you see in Figure 10.3.

Figure 10.3. Rendering to a surface.

graphics/10fig03.jpg



Managed DirectX 9 Graphics and Game Programming, Kick Start
Managed DirectX 9 Kick Start: Graphics and Game Programming
ISBN: B003D7JUW6
EAN: N/A
Year: 2002
Pages: 180
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net