Chapter 15. Meshes

The title of this chapter is a bit misleading because it implies that just one subject is covered: meshes. However, there are many concepts we must learn in order to load and render a mesh, from fundamental rendering with a camera and lighting to calculating transformations. These concepts are challenging to grasp at first, so this chapter takes it slow, building a simple example that demonstrates how to render a mesh with lighting and transforms. We will skirt the theory and use the rendering classes provided in the XNA Framework without deriving any of the concepts on our own. For a reference, refer to Chapter 7, “Graphics,” which includes detailed information on the Graphics namespace.

These are the topics in this chapter:

  • Mesh file formats

  • Loading a mesh

  • Rendering a mesh

  • Mesh Demo project

Working with Meshes

I have never been crazy about the name “mesh.” This is the technical term for a 3D model, but it’s too imprecise of a term to describe all that a mesh represents. In short, a mesh is an optimized list of vertices that make up a shape. A mesh in its truest definition does not include textures or materials, just a list of vertices.

All the most relevant mesh formats in use today, however, include these as part of the mesh definition, stored in one or another of the mesh file formats. A mesh in XNA is handled by a class called Model, which is a bit ironic. The Model class can handle any kind of mesh, from a simple static mesh to a fully animated skeletal model containing separate mesh groups.

Mesh File Formats

The most common mesh formats are Maya and 3DS Max, but most game studios use their own proprietary mesh format optimized for their game engine. Because XNA was built around Direct3D, one built-in mesh format is X (sometimes referred to as XOF). Most modeling software packages like Maya and Max do not support X without a custom exporter. The second mesh file that XNA supports is FBX. This is an older format used originally in a software package called FiLMBOX, now owned by Autodesk.

Loading a Mesh

We will use the namespace Microsoft.Xna.Framework.Graphics, which was covered in detail in Chapter 7. I’ll forego any further reference information here and refer you to that chapter for the list of classes and methods within Graphics and get right down to business. Within the Graphics namespace is a class called Model that we will use for loading and rendering a mesh file. The Model class is fairly well self-contained, containing four key components: Bones, Meshes, Root, and Tag. Bones is filled with ModelBone objects, used to render a skeletal mesh. Meshes is filled with ModelMesh objects, used for normal rendering.

Before we can load a model file with the content manager, the model file must be added to the project. This is done in the usual way, by right-clicking the content project and selecting Add, selecting Existing Item, and then choosing the mesh file. If a texture is required by the mesh (and most meshes do have textures), then be sure to add the texture to the content project as well.

First, declare a Model variable:

Model model;

Then, from inside the LoadContent event method, we load the Model:

model = Content.Load<Model>("filename");

Rendering a Mesh

There is often more code involved in creating the camera than in rendering a mesh, but in the case of XNA, rendering the mesh is a bit involved. Let’s start with the camera. A basic rendered scene needs three matrices:

  • Projection matrix

  • View matrix

  • World matrix

Projection Matrix

The projection matrix, often called “perspective,” defines the perspective or field of view. We can use a method in the Matrix class called CreatePerspectiveFieldOfView() to create the projection matrix:

Matrix projection = Matrix.CreatePerspectiveFieldOfView(
    MathHelper.ToRadians(45.0f),
    graphics.GraphicsDevice.Viewport.AspectRatio,
    0.1f, 10000.0f);

This method requires some explanation. The first parameter is the field of view, which is the amount of the scene you wish to see, sort of like the type of lens attached to the camera. Next is the aspect ratio, which should be based on the resolution of the window, calculated by dividing the width by the height. The last two parameters define the near plane and far plane for the purpose of clipping.

View Matrix

The view matrix literally is the viewport created by the camera. It is what you “see” on the screen, based on the projection matrix (the rasterization properties), the camera’s position, and the camera’s target, or where the camera is pointed. It defines the camera’s location and orientation. The position will usually move around quite a bit in a game. For instance, in a car-racing game, the camera will be the driver’s view out the window of the car or in chase of the car for third-person view.

Vector3 position = new Vector3(0.0f, 10.0f, 10.0f);
Vector3 lookat = new Vector3(0.0f, 3.0f, 0.0f),
Matrix view = Matrix.CreateLookAt( position, lookat, Vector3.Up);

World Matrix

The world matrix defines the transforms of the current object to be rendered. The world matrix needs to take into account the rotation, scaling (if any), and translation of a model so that it is oriented correctly in the scene. To create the world matrix, the three transformation matrices (rotation, scaling, and translation) are multiplied together into a new combined matrix, which is the world. Because of the way matrix multiplication affects the elements of a matrix, we must perform multiplication in this specific order:

  1. Rotation

  2. Scaling

  3. Translation

If you mix up the ordering of the matrix multiplication (when combining them all into the world matrix), then an object will show up in very strange places!

Matrix world = matRotation * matScaling * matTranslation;

Drawing a Model

A model contains transforms for the bones (if any) as well as meshes that are rendered together for each type of material and/or texture used by the mesh. To render a whole model, we have to render each of the meshes in the Model.Meshes collection, using an effect to render each one. Here is a reusable function that will accomplish the task. This method uses global matrices to simplify the code a bit, but a more reusable version would allow you to pass the matrices to the method to render a model without using the global Matrix variables.

private void DrawModel(Model m)
{
    Matrix[] transforms = new Matrix[m.Bones.Count];
    m.CopyAbsoluteBoneTransformsTo(transforms);
    foreach (ModelMesh mesh in m.Meshes)
    {
        foreach (BasicEffect effect in mesh.Effects)
        {
            effect.EnableDefaultLighting();
            effect.View = view;
            effect.Projection = projection;
            effect.World = gameWorldRotation *
                transforms[mesh.ParentBone.Index] *
                Matrix.CreateTranslation(Position);
        }
        mesh.Draw();
    }
}

Mesh Demo

The Mesh Demo project, shown in Listing 15.1, loads a skull mesh (sourced from the DirectX SDK) and is shown in Figure 15.1.

Rendering a mesh using the BasicEffect shader.

Figure 15.1. Rendering a mesh using the BasicEffect shader.

Example 15.1. Mesh Demo Project

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Graphics_Demo
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont font;
        KeyboardState keyState;
        GamePadState padState;

        Model model;
        Vector3 Position = Vector3.One;
        float Zoom = 10.0f;
        float RotationY = 0.0f;
        float RotationX = 0.0f;
        Matrix gameWorldRotation;
        Matrix projection, view;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();

            //get the aspect ratio from the Viewport
            float aspectRatio = graphics.GraphicsDevice.Viewport.
                AspectRatio;

            //create the projection matrix
        projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45.0f),
            aspectRatio, 0.1f, 10000.0f);

        //create the view matrix
        view = Matrix.CreateLookAt(new Vector3(0.0f, 10.0f, Zoom),
            new Vector3(0.0f, 3.0f, 0.0f), Vector3.Up);
    }


    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        font = Content.Load <SpriteFont>("font1");
        model = Content.Load <Model>("skullocc");
    }

    protected override void UnloadContent() {}

    protected override void Update(GameTime gameTime)
    {
        keyState = Keyboard.GetState();
        padState = GamePad.GetState(PlayerIndex.One);
        if (keyState.IsKeyDown(Keys.Escape) ||
            padState.Buttons.Back == ButtonState.Pressed)
            this.Exit();

        //move the model with left thumbstick
        Position.X += padState.ThumbSticks.Left.X * 1.01f;
        Position.Y += padState.ThumbSticks.Left.Y * 1.01f;

        //rotate and zoom with the right thumbstick
        Zoom += padState.ThumbSticks.Right.Y * 1.01f;
        RotationY += padState.ThumbSticks.Right.X;

        //recreate the view matrix due to the Zoom
        view = Matrix.CreateLookAt(new Vector3(0.0f, 10.0f, Zoom),
            new Vector3(0.0f, 3.0f, 0.0f), Vector3.Up);

        //recalculate the world matrix rotation
        gameWorldRotation = Matrix.CreateRotationX(
            MathHelper.ToRadians(RotationX)) *
            Matrix.CreateRotationY(MathHelper.ToRadians(RotationY));

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.DarkSlateBlue);

        //restore render states messed up by SpriteBatch
        GraphicsDevice.BlendState = BlendState.Opaque;
        GraphicsDevice.DepthStencilState = DepthStencilState.Default;

        //draw the model
        DrawModel(model);

        //sprite drawing code
        spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.
            AlphaBlend);
        int y = 20;
        print(20, y, "Mesh Demo"); y += 15;
        print(20, y, "Position: " + Position.ToString()); y += 15;
        print(20, y, "Zoom: " + Zoom.ToString()); y += 15;
        print(20, y, "Rotation: " + RotationX.ToString() + "," +
            RotationY.ToString()); y += 15;
        spriteBatch.End();

        base.Draw(gameTime);
    }

    private void DrawModel(Model m)
    {
        Matrix[] transforms = new Matrix[m.Bones.Count];
        m.CopyAbsoluteBoneTransformsTo(transforms);

        foreach (ModelMesh mesh in m.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
                {
                   effect.EnableDefaultLighting();
                   effect.View = view;
                   effect.Projection = projection;
                   effect.World = gameWorldRotation *
                       transforms[mesh.ParentBone.Index] *
                       Matrix.CreateTranslation(Position);
                }
                mesh.Draw();
            }
        }

        public void print(int x, int y, string text)
        {
            print(x, y, text, Color.White);
        }
        public void print(int x, int y, string text, Color color)
        {
            try {
                spriteBatch.DrawString(font, text, new Vector2(
                   (float)x, (float)y), color);
            }
            catch (Exception e) { }
        }
    }
}

Summary

That wraps up mesh loading and rendering in fairly short order. There are many more advanced ways that a mesh can be rendered—for example, by using custom shaders and even performing animation on a skinned mesh. These are extremely advanced topics that require add-ons from MSDN, which are unfortunately not built in to the XNA Framework. This book will limit our use of meshes to just fixed, non-animated, non-bone meshes. After you have learned all you can about mesh rendering with the simple technique shown here and moved on to the slightly more advanced method of rendering with a custom shader, you should be ready for mesh animation. When you’re ready, head over to http://create.msdn.com/en-US/education/catalog/sample/skinned_model for the example.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.116.159