Chapter 4: Basic 3D Objects


This chapter concentrates on the portions of the game engine that provide the setting for the game. Every game is set in a particular environment, which could be the dark and winding corridors of a dungeon , the great outdoors, the depths of space, and so on. The genre of the game as well as any story line usually decides what this setting will be. The sample game we are developing over the course of this book will be an outdoor driving game. To set the scene for this game, we will want to provide the feeling of being outdoors: the illusion of seeing to the horizon, rolling landscape, static objects that dot the land, and some environmental effects.

Seeing to the Horizon: The Skybox

In an outdoor game, we should include a sense of being able to see for miles in all directions. Since the hardware does not exist to render three-dimensional terrain at these distances, a shortcut was developed to give the illusion of seeing to the horizon. This technique relies on a skybox or a sky dome, depending on the implementation. A skybox is a textured cube that is displayed around the viewpoint. All of the textures on the cube are displayed on the inside facing the viewpoint.

The top of the cube has a texture of the sky we would see if we were looking straight up. The bottom of the cube is a texture of the ground. The textures on each of the sides represent the view looking north, south, east, and west. These textures are created so that they meet with no visible seams to produce a sense of immersion within a scene. The tools for creating these textures are discussed in Chapter 11.

To create the illusion that the skybox is at a great distance from the viewer, we disable the Z-buffering for the skybox. Since the rendering of the skybox does not write to the Z-buffer, everything rendered after it is drawn on top of the skybox. We also want to prevent the player from getting closer to any of the images that make up the cube, so the cube is always centered on the current viewpoint.

What Makes Up a Skybox?

Now that we have established the basics of what makes up a skybox, it is time to look at an implementation and discuss the details. We will start with the vertex structure for storing the points used to construct the cube for the skybox. Managed DirectX offers two different ways to define our vertex formats. The first method is reminiscent of the method used in the previous versions of DirectX. We define a structure for our vertex data and use the VertexFormat enumerations to inform the device as to what the structure looks like. Managed DirectX provides a second method by predefining a number of vertex formats that are commonly used. These structures appear in the CustomVertex namespace. All we need is the vector to define the point, normal vector, and the texture coordinates that dictate how the texture map is drawn. The CustomVertex PositionNormalTextured structure fits the bill.

Now that we have a structure defining a point for the corner of the cube, it is time to move up to a class defining a cube face. A cube face is one side of the skybox consisting of the four points that define the corners of the face and the texture that is drawn on the face. The other information that the class will hold includes the name of the file containing the texture, the vertex buffer used for rendering, and a flag indicating that the class has been properly initialized and may be rendered. The final attribute in the class is a read-only accessor to allow other classes to check the face s validity. Listing 4 “1 shows these attributes as they appear in the C# source code.

Listing 4.1: SkyFace Class Attribute Definition
start example
 public class SkyFace : IDisposable  {  #region Attributes     private CustomVertex.PositionNormalTextured[] m_Corners;     private VertexBuffer m_VB = null;   // Vertex buffer     private Texture       m_Texture; // Image for face     private bool          m_bValid = false;     private string        m_sName;     public bool Valid { get { return m_bValid; } }  #endregion 
end example
 

The constructor sets the points for the four corners and loads the texture for that face. The arguments for the constructor are the name of the file holding the structure and an enumerated value declaring which face of the cube to build. The constructor begins by saving the filename for future reference and creating an array of four vertices, one for each corner. The next three listings (Listing 4 “2a, shown here, and Listings 4 “2b and 4 “2c, shown a little later) cover the source code for the constructor.

Listing 4.2a: SkyFace Constructor Definition
start example
 public SkyFace(string sName, SkyBox.Face face)  {     m_sName = sName;     // Create the vertices for the box.     m_Corners = new CustomVertex.PositionNormalTextured[4]; 
end example
 
Listing 4.2b: SkyFace Constructor Definition
start example
 switch (face)  {  case SkyBox.Face.Top:     m_Corners[0].X =   100.0f;     m_Corners[0].Y = 100.0f;     m_Corners[0].Z =   100.0f;     m_Corners[0].Tu = 0.0f;     m_Corners[0].Tv = 1.0f;     m_Corners[0].Nx = 0.0f;     m_Corners[0].Ny =   1.0f;     m_Corners[0].Nz = 0.0f;     m_Corners[1].X = 100.0f;     m_Corners[1].Y = 100.0f;     m_Corners[1].Z =   100.0f;     m_Corners[1].Tu = 0.0f;     m_Corners[1].Tv = 0.0f;     m_Corners[1].Nx = 0.0f;     m_Corners[1].Ny =   1.0f;     m_Corners[1].Nz = 0.0f;     m_Corners[2].X =   100.0f;     m_Corners[2].Y = 100.0f;     m_Corners[2].Z = 100.0f;     m_Corners[2].Tu = 1.0f;     m_Corners[2].Tv = 1.0f;     m_Corners[2].Nx = 0.0f;     m_Corners[2].Ny =   1.0f;     m_Corners[2].Nz = 0.0f;     m_Corners[3].X = 100.0f;     m_Corners[3].Y = 100.0f;     m_Corners[3].Z = 100.0f;     m_Corners[3].Tu = 1.0f;     m_Corners[3].Tv = 0.0f;     m_Corners[3].Nx = 0.0f;     m_Corners[3].Ny =   1.0f;     m_Corners[3].Nz = 0.0f;  break; 
end example
 
Listing 4.2c: SkyFace Constructor Definition
start example
 // Load the texture for the face,     try     {        m_Texture = GraphicsUtility.CreateTexture(CGameEngine.Device3D, sName);        m_bValid = true;     }     catch     {        Console.AddLine("Unable to create skybox texture for " + sName);     }     // Create a quad for rendering the face.     m_VB = new VertexBuffer(typeof(CustomVertex.PositionNormalTextured), 4,            CGameEngine.Device3D, Usage.WriteOnly,            CustomVertex.PositionNormalTextured.Format, Pool.Default);     m_VB.Created += new System.EventHandler(this.CreateQuad);     CreateQuad(m_VB, null);  } 
end example
 

A switch statement is then used to establish which face to build based on the value passed into the constructor. Each face is 200 units on a side centered on zero. The texture coordinates are set up so that the face texture occupies the complete face. The normal vector at each corner is set to point toward the inside of the cube. The code snippet in Listing 4 “2b shows the initialization for the face at the top of the cube. The code for the other faces is nearly identical except for the coordinates used for the corner locations. To see the details for the other faces, check out SkyBox.cs in code downloadable from the Apress Web site.

Now that the data for the corner positions has been defined, it is time to load the texture to apply to the face, as shown in Listing 4 “2c. The call to create the texture from the image file is enclosed in a Try/Catch block. This protects against trying to load a file that doesn t exist. If the call fails, a message is posted to the console so that the developer is alerted to the problem. The CreateTexture method will create the texture using video memory if enough is available. Failing this, it will create the texture in System memory, which will allow the texture to be used at the cost of performance. If the call to CreateTexture succeeds, the valid flag is set to true so that the rendering code knows that the face is ready to be rendered. The final thing to do is the allocation and setup of the vertex buffer for the face. An event handler is used to place the data into the vertex buffer. This allows the system to automatically refill the vertex buffer if it needs to be re-created later.

Note

If the game application loses focus (i.e., another application gets input focus), the resources on the video card may be lost. When the game regains the focus, we will need to restore these resources. DirectX provides events and event handlers for this situation.

The CreateQuad message handler is used to populate the vertex buffer. This message handler is called anytime the vertex buffer needs to be re-created. This simple message handler only needs to copy the contents of the m_Corners array into the vertex buffer as shown in Listing 4 “3.

Listing 4.3: SkyFace CreateQuad Message Handler
start example
 public void CreateQuad(object sender, EventArgs e)  {      VertexBuffer vb = (VertexBuffer)sender;      // Copy tree mesh data into vertex buffer.      vb.SetData(m_Corners, 0, 0);  } 
end example
 

The drawing of a face of the SkyBox object is performed by the SkyFace Render method. Before we actually try to render anything, we need to make sure that the face is valid. Remember that back in the constructor we set the valid flag to true at the end of the construction steps. If an exception had been thrown at any point in the constructor, the setting of the flag to true would have been skipped . Assuming that the face is valid, the normal rendering steps are followed. The stream source, vertex format, and texture are set. The DrawPrimitives method is then called to render the face as a triangle strip of two triangles . The method is show in Listing 4 “4.

Listing 4.4: SkyFace Render Method
start example
 public void Render()  {      if (m_bValid)      {        Material mtrl = new Material();         mtrl.Ambient = Color.White;         mtrl.Diffuse = Color.White;         CGameEngine.Device3D.Material = mtrl;          CGameEngine.GetDevice().SetStreamSource(0, m_VB, 0);         CGameEngine.Device3D.VertexFormat =                         CustomVertex.PositionNormalTextured.Format;          // Set the texture.          CGameEngine.GetDevice().SetTexture(0, m_Texture);          // Render the face.          CGameEngine.GetDevice ().DrawPrimitives (PrimitiveType.TriangleStrip, 0, 2);      } } 
end example
 

The last method in the class is the Dispose method shown in Listing 4 “5. The assets of this class to dispose of consist of the texture and the vertex buffer.

Listing 4.5: SkyFace Dispose Method
start example
 public void Dispose()     {         m_Texture.Dispose() ;         if(m_VB != null)             m_VB.Dispose();     }  } 
end example
 
Note

Remember that textures, vertex buffers, and index buffers are allocated from memory on the video card. Failing to dispose of them as soon as you no longer need them could get you into a situation where you do not have enough memory left on the card to allocate new textures and buffers. You cannot wait for the .NET garbage collector to do this for you. It does not distinguish between system memory and video card memory and may not free the memory until the application exits.




Introduction to 3D Game Engine Design Using DirectX 9 and C#
Introduction to 3D Game Engine Design Using DirectX 9 and C#
ISBN: 1590590813
EAN: 2147483647
Year: 2005
Pages: 98

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