Rendering Patch Meshes

Simplifying meshes is a common practice, but what about the times when the meshes you've got are already too simple? You have enough triangle bandwidth to display even more vertices in your mesh. While it may not be common to want to increase the detail of a model, it is possible, and patch meshes are the way. Most modern 3D graphics modeling programs have a concept of a type of patch mesh, or some type of high order primitives such as NURBS or subdivided surfaces.

The objective of this book is not to explain the theory behind understanding patch meshes, so we will not get into that. What we want to do is to understand how to use them, and how they can increase a model's level of detail. To do this, we will create a small application that loads a model and allows us to subdivide the vertices to make a more complex model.

Once again, we will start with the file load mesh example from Chapter 5. We keep doing this because it sets up the majority of the "state" for us to get started. You will need the models from the included CD for this example as well. The code on the included CD uses two of the models that are included in the DirectX SDK (tiger.x and cube.x), plus a small sphere model you will find on the CD. Make sure you copy the texture for the tiger model (tiger.bmp) as well. You will need the following variables added:

 private float tessLevel = 1.0f; private const float tessIncrement = 1.0f; private string filename = @"..\..\sphere.x"; private Microsoft.DirectX.Direct3D.Font font = null; 

You will have our current tessellation level (which is initially set to 1.0f), as well as how much we will increment this level each time. Feel free to manipulate this constant, but for now, the default value will do nicely. We will also need to store the name of the current model we are loading, as well as a font to draw the text during rendering.

You will also want to change the SetupCamera method since the last one was set up for a very large model, while the ones being used for this example are quite a bit smaller. Update the method as follows:

 private void SetupCamera() {     device.Transform.Projection = Matrix.PerspectiveFovLH(         (float)Math.PI / 4, this.Width / this.Height, 1.0f, 100.0f);     device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, 5.0f),         new Vector3(), new Vector3(0,1,0));     device.Lights[0].Type = LightType.Directional;     device.Lights[0].Diffuse = Color.DarkKhaki;     device.Lights[0].Direction = new Vector3(0, 0, -1);     device.Lights[0].Commit();     device.Lights[0].Enabled = true; } 

This simply sets the camera and our light up. It's not very efficient to call this every single frame, but for clarity, we will do so for this example. Of course, we should be checking to see whether our graphics card supports directional lighting as well, but for this simple example, we will assume it exists as well.

Instead of using a single LoadMesh call (like we've done before) to get our mesh ready, replace the call to that method with the following few calls:

 // Create our patch mesh CreatePatchMesh(filename, tessLevel); // Create our font font = new Microsoft.DirectX.Direct3D.Font(device, new System.Drawing.Font     ("Arial", 14.0f, FontStyle.Bold | FontStyle.Italic)); // Default to wireframe mode first device.RenderState.FillMode = FillMode.WireFrame; 

Obviously here we will be creating our patch mesh, along with our font. We also switch our fill mode to wire-frame by default, since it is much easier to see the new triangles appearing in wire-frame mode. However, we don't have the method to create our patch mesh defined yet, so we should add that now. See Listing 9.5:

Listing 9.5 Creating a Patch Mesh
 private void CreatePatchMesh(string file, float tessLevel) {     if (tessLevel < 1.0f) // Nothing to do         return;     if (mesh != null)         mesh.Dispose();     using (Mesh tempmesh = LoadMesh(file))     {         using (PatchMesh patch = PatchMesh.CreateNPatchMesh(tempmesh))         {             // Calculate the new number of faces/vertices             int numberFaces = (int)(tempmesh.NumberFaces                 * Math.Pow(tessLevel, 3));             int numberVerts = (int)(tempmesh.NumberVertices                 * Math.Pow(tessLevel, 3));             mesh = new Mesh(numberFaces, numberVerts, MeshFlags.Managed                 | MeshFlags.Use32Bit, tempmesh.VertexFormat, device);             // Tessellate the patched mesh             patch.Tessellate(tessLevel, mesh);         }     } } 

If the tessellation level we are currently using is less than 1.0f (the default), there is nothing to do in this method, so we simply return immediately. Otherwise, we get ready to create our new tessellated mesh. Since we will be replacing the existing mesh if it exists, we will first dispose of that one. What's this though? Our current LoadMesh method doesn't return any values; we should update that as shown in Listing 9.6:

Listing 9.6 Loading a Mesh with Normal Data
 private Mesh LoadMesh(string file) {     ExtendedMaterial[] mtrl;     // Load our mesh     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);             }         }     }     if ((mesh.VertexFormat & VertexFormats.Normal) != VertexFormats.Normal)     {         // We must have normals for our patch meshes         Mesh tempMesh = mesh.Clone(mesh.Options.Value,             mesh.VertexFormat | VertexFormats.Normal, device);         tempMesh.ComputeNormals();         mesh.Dispose();         mesh = tempMesh;     }     return mesh; } 

There isn't anything here that we haven't seen before. We create our mesh (via a local variable). If there are no normals in our mesh, we clone the mesh and compute the normals, since they are required for the tessellation of our patch mesh. Finally, we return the created mesh (with the normals).

THE USING STATEMENT

You'll notice that for both the returned mesh and the patch mesh we create in this method, we use the "using" keyword. In case you are unaware, this causes the object in the using clause to be automatically disposed when it leaves the scope of the clause.

We then create a new patch mesh object based on the newly returned mesh. Since we will use the patch mesh object to tessellate into a new mesh, we need to know the number of new vertices and faces this mesh will need to have. After we've calculated that and created the new mesh, we finally tessellate our patch mesh into this newly created mesh. You'll notice that we are using the MeshFlags.Use32Bit flag as well. Since it's entirely possible we could get a very large mesh returned back after tessellation, we want to make sure we can support the largest meshes we can.

In reality, we are ready to run this application now. It will be pretty boring, though. First, let's add our text to the rendered scene. After the call to draw our mesh, add the following:

 font.DrawText(null, string.Format     ("Number Vertices: {0}\r\nNumber Faces: {1}",     mesh.NumberVertices, mesh.NumberFaces),     new Rectangle(10,10,0,0),     DrawTextFormat.NoClip, Color.Black); 

Finally, all we need now is a way to change models and increase (or decrease) the amount of tessellation. We will use the override in Listing 9.7:

Listing 9.7 KeyPress Event Handler
 protected override void OnKeyPress(KeyPressEventArgs e) {     if (e.KeyChar == '+')     {         tessLevel += tessIncrement;         CreatePatchMesh(filename, tessLevel);     }     if (e.KeyChar == '-')     {         tessLevel -= tessIncrement;         CreatePatchMesh(filename, tessLevel);     }     if (e.KeyChar == 'c')     {         filename = @"..\..\cube.x";         tessLevel = 1.0f;         CreatePatchMesh(filename, tessLevel);     }     if (e.KeyChar == 'o')     {         filename = @"..\..\sphere.x";         tessLevel = 1.0f;         CreatePatchMesh(filename, tessLevel);     }     if (e.KeyChar == 't')     {         filename = @"..\..\tiger.x";         tessLevel = 1.0f;         CreatePatchMesh(filename, tessLevel);     }     if (e.KeyChar == 'w')         device.RenderState.FillMode = FillMode.WireFrame;     if (e.KeyChar == 's')         device.RenderState.FillMode = FillMode.Solid; } 

We will use the old mainstays of "w" and "s" to switch between wire-frame and solid fill modes, respectively. We will also use the "+" and " " keys to increase and decrease the tessellation level, much like we did for our progressive mesh example. We finally use the "c" key to switch to a cube model, the "t" key to switch to the tiger model, and the "o" key to switch back to the sphere model. Try running the example now and switching between the various models and tessellation levels.



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