Using Meshes to Render Complex Models

Rendering teapots can be quite exciting, but it's not often you make a game with nothing but teapots. Most meshes are created by artists using a modeling application. If your modeling application supports exporting to the X file format, you're in luck!

There are a few types of data stored in a normal x file that can be loaded while creating a mesh. There is naturally the vertex and index data that will be required to render the model. Each of the subsets of the mesh will have a material associated with it. Each material set can contain texture information as well. You can also get the High Level Shader Language (HLSL) file that should be used with this mesh while loading the file. HLSL is an advanced topic that will be discussed in depth later.

CONVERTING COMMON MODELING FORMATS TO THE X FILE FORMAT

The DirectX SDK (included on the CD) includes several conversion utilities for the most popular modeling applications out there. Using these conversion tools allows you to easily use your high-quality models in your applications.

Much like the static methods on the Mesh object that allowed us to create our generic "simple" primitive types, there are two main static methods on the mesh object that can be used to load external models. These two methods are Mesh.FromFile and Mesh.FromStream. The methods are essentially identical, with the stream method having more overloads for dealing with the size of the stream. The root overload for each method is as follows:

 public static Microsoft.DirectX.Direct3D.Mesh FromFile ( System.String filename ,     Microsoft.DirectX.Direct3D.MeshFlags options ,     Microsoft.DirectX.Direct3D.Device device ,     Microsoft.DirectX.Direct3D.GraphicsStream adjacency ,     out Microsoft.DirectX.Direct3D.ExtendedMaterial[] materials ,     Microsoft.DirectX.Direct3D.EffectInstance effects ) public static Microsoft.DirectX.Direct3D.Mesh FromStream ( System.IO.Stream stream ,     System.Int32 readBytes ,     Microsoft.DirectX.Direct3D.MeshFlags options ,     Microsoft.DirectX.Direct3D.Device device ,     Microsoft.DirectX.Direct3D.GraphicsStream adjacency ,     out Microsoft.DirectX.Direct3D.ExtendedMaterial[] materials ,     Microsoft.DirectX.Direct3D.EffectInstance effects ) 

The first parameter(s) are the source of the data we will be using to load this mesh. In the FromFile case, this is a string that is the filename of the mesh we wish to load. In the stream case, this is the stream, and the number of bytes we wish to read for the data. If you wish to read the entire stream, simply use the overload that does not include the readBytes member.

The MeshFlags parameter controls where and how the data is loaded. This parameter may be a bitwise combination of the values found in Table 5.1.

Table 5.1. Mesh Flags Enumeration Values

PARAMETER

VALUE

MeshFlags.DoNotClip

Use the Usage.DoNotClip flag for vertex and index buffers.

MeshFlags.Dynamic

Equivalent to using both IbDynamic and VbDynamic.

MeshFlags.IbDynamic

Use Usage.Dynamic for index buffers.

MeshFlags.IbManaged

Use the Pool.Managed memory store for index buffers.

MeshFlags.IbSoftwareProcessing

Use the Usage.SoftwareProcessing flag for index buffers.

MeshFlags.IbSystemMem

Use the Pool.SystemMemory memory pool for index buffers.

MeshFlags.IbWriteOnly

Use the Usage.WriteOnly flag for index buffers.

MeshFlags.VbDynamic

Use Usage.Dynamic for vertex buffers.

MeshFlags.VbManaged

Use the Pool.Managed memory store for vertex buffers.

MeshFlags.VbSoftwareProcessing

Use the Usage.SoftwareProcessing flag for vertex buffers.

MeshFlags.VbSystemMem

Use the Pool.SystemMemory memory pool for vertex buffers.

MeshFlags.VbWriteOnly

Use the Usage.WriteOnly flag for vertex buffers.

MeshFlags.Managed

Equivalent to using both IbManaged and VbManaged.

MeshFlags.Npatches

Use the Usage.NPatches flag for both index and vertex buffers. This is required if the mesh will be rendered using N-Patch enhancement.

MeshFlags.Points

Use the Usage.Points flag for both index and vertex buffers.

MeshFlags.RtPatches

Use the Usage.RtPatches flag for both index and vertex buffers.

MeshFlags.SoftwareProcessing

Equivalent to using both IbSoftwareProcessing and VbSoftwareProcessing.

MeshFlags.SystemMemory

Equivalent to using both IbSystemMem and VbSystemMem.

MeshFlags.Use32Bit

Use 32-bit indices for the index buffer. While possible, normally not recommended.

MeshFlags.UseHardwareOnly

Use hardware processing only.

The next parameter is the device we will be using to render this mesh. Since resources must be associated with a device, this is a required parameter.

The adjacency parameter is an "out" parameter, meaning it will be allocated and passed to you after the function has finished working. This will return the adjacency information for the mesh, stored as three integer values for each face of the mesh, that will specify the three neighbors.

The extended material parameter is also an out parameter that will return an array of information about the different subsets in the mesh. The ExtendedMaterial class holds both the normal Direct3D material, as well as a string that can be used to load a texture. This string is normally the filename or resource name of the texture; however, since the loading of the texture is done by the application, this can be any user-provided string data.

READING ADJACENCY INFORMATION FROM THE RETURNED GRAPHICSSTREAM

The adjacency information returned to you from the mesh creation routines will come from a GraphicsStream class. You can get a local copy of the adjacency data with this small snippet of code:

 int[] adjency = adjBuffer.Read(typeof(int), mesh.NumberFaces * 3); 

This will create an array of three integers per face storing the adjacency information that is easier to access than the native GraphicsStream class.

Finally, we have the EffectInstance parameter, which describes the HLSL shader file and values that will be used for this mesh. There are actually multiple overloads for each of these methods that take varying combinations of these parameters. Always pick the one that has only the information you want and need.

Now that seems like quite a bit to chew on just to load and render a mesh, but in reality it's not that bad. It looks slightly intimidating at first, but once you actually see the code, it really isn't all that bad. Let's start writing some of that code now.

First, we'll need to make sure we have member variables that will store our materials and textures for the different subsets of the mesh. Add the following variables after your mesh declaration:

 private Material[] meshMaterials; private Texture[] meshTextures; 

Since there can possibly be many different subsets in this mesh, you will need to store an array of both textures and materials, one for each subset. Now let's actually take a look at some code to load a mesh; create a function called "LoadMesh" that looks like Listing 5.1:

Listing 5.1 Loading a Mesh from a File
 private void LoadMesh(string file) {     ExtendedMaterial[] mtrl;     // Load our mesh     mesh = Mesh.FromFile(file, MeshFlags.Managed, device, out mtrl);     // If we have any materials, store them     if ((mtrl != null) && (mtrl.Length > 0))     {         meshMaterials = new Material[mtrl.Length];         meshTextures = new Texture[mtrl.Length];         // Store each material and texture         for (int i = 0; i < mtrl.Length; i++)         {             meshMaterials[i] = mtrl[i].Material3D;             if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename !=                 string.Empty))             {                 // We have a texture, try to load it                 meshTextures[i] = TextureLoader.FromFile(device, @"..\..\" +                     mtrl[i].TextureFilename);             }         }     } } 

Okay, so it looks a little more scary than the simple things we've done before, but really it's not. It's actually quite simple. First, we declare our ExtendedMaterial array that will store the information about the subsets of this mesh. Then, we simply call the FromFile method to load our mesh. We don't necessarily care about the adjacency or HLSL parameters now, so we use the overload that doesn't have them.

After our mesh has been loaded, we want to store the material and texture information for the various subsets. After we make sure there really are different subsets, we finally allocate our local material and texture arrays using the number of subsets as our size. We then loop through each of our ExtendedMaterial array members, and store the material in our local copy. If there is texture information included in this material subset, we use the TextureLoader.FromFile function to create our texture. This function only takes two parameters, the device and the filename of the texture and is much faster than going through System.Drawing.Bitmap like we did before.

In order to draw this mesh, you will need to add the following method to your application:

 private void DrawMesh(float yaw, float pitch, float roll, float x, float y, float z) {     angle += 0.01f;     device.Transform.World = Matrix.RotationYawPitchRoll(yaw, pitch, roll) *          Matrix.Translation(x, y, z);     for (int i = 0; i < meshMaterials.Length; i++)     {         device.Material = meshMaterials[i];         device.SetTexture(0, meshTextures[i]);         mesh.DrawSubset(i);     } } 

If you notice, we've kept the same basic signature as we had with our DrawBox method. Then, in order to draw the mesh, you will loop through each of the materials and perform the following steps:

  1. Set the stored material as the material on the device.

  2. Set the texture on the device to the stored texture. Even if there is no stored texture, setting the texture to null is completely valid and correct in that case.

  3. Call DrawSubset passing in our subset id.

Perfect, now we've got all we really need to load a mesh and render it onscreen. The source code on the accompanying CD uses the model "tiny.x" that ships with the DirectX SDK as its test model for this application. In order to render this model, we added the following after device creation:

 // Load our mesh LoadMesh(@"..\..\tiny.x"); 

We then need to modify our camera information because the tiny model is anything but tiny. It is actually quite large, and we need to zoom our camera way out. Replace the view and projection transforms as follows:

 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)); 

As you can see, we increased the length of the far plane, and moved the position of the camera pretty far back. Now, the only thing left to do is actually call DrawMesh during your render loop:

 DrawMesh(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle /     (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f); 

You may also want to turn the lights back to white now. You don't want to see your mesh rendered with a blue tint to it. Running the application would give you results similar to Figure 5.4.

Figure 5.4. A mesh rendered from a file.

graphics/05fig04.gif

Now we're getting somewhere. This is beginning to look much nicer than our cubes spinning around.



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