This page described how to load and render a 3D model. It is best if you have read about the XNA Matrices before reading this.
You can use the XNA content pipeline to make models available to us on the PC or Xbox via a simple name (set in the properties). this takes away a lot of the problem of handling data directories etc. XNA supports the Microsoft X file format as well as the Autodesk FBX format. There are two ways of making the model available in your project:
Adding the model - Method 1
You can drag the model on to the content area in the solution explorer of Visual Studio or select Add Item as you did with textures.
At this stage you should build your code and see if you get any warnings. There are a couple of possible problems:
Adding the model - Method 2
To prevent the problems described above you can alternatively create a link to the model only. This means it does not get inserted into the project but is added as a link to a local directory. This can avoid the issues of missing textures etc. and is the method recommended by Microsoft. To add as a link select add item from content and change the drop down button at the bottom to 'link'.
Loading it
XNA provides a helper class called Model. This class wraps all the model data (vertices, indices etc.) and provides functionality to allow the contained mesh to be drawn and also to get information about each mesh like the bounding sphere (useful for collision handling) and even the raw data should you wish. To load a model therefore you need to first declare a model type variable:
Model testModel;
Then load it in your LoadContent function. We use the same Content class Load call we used for textures but this time we specify the type of content to be a Model. The Load function uses Generics (like Templates in C++) that allows it to handle different types. You place the type between the <> symbols
testModel = Content.Load<Model>("house");
A 3D model is normally made up of a number of mesh e.g. a car may have a wheel mesh, body mesh, door mesh, bumper mesh. The reason for this is because each mesh may need drawing differently (uses a different material for example) and / or to support animation. I will talk more about animation later on.
Within the model the position of each mesh is defined relative to each other. For example if you had a model of a person the left arm may be defined as being x units to the left of the body and the right arm x units to the right of the body. You could in fact split the arm into upper arm and lower arm. In this case the upper arm is defined relative to the body but the lower arm is defined relative to the upper arm. The huge advantage of this tree like structure is that if you were to move or rotate the upper arm the lower arm would follow automatically.
Each mesh may be rotated or translated relative to another. Each model mesh itself stores this information in a matrix. This is what XNA refers to as a bone (other APIs often call it a frame). When we come to render all we need to do is to use the position of the model and render our tree of mesh with each being offset from its parent (each mesh has a parent bone that holds the transformation from the parent).
To summarise: each model is made up of a number of mesh (type: ModelMesh). Each mesh has a related matrix defining its relative translation and rotation from its parent mesh (bone). To draw the model we traverse through all the mesh and calculate that mesh's transformation matrix. Note that we also, as before, need to handle the situation where there is more than one effect per mesh. But first we need to prepare by copying all the bone transforms to a matrix array for use when rendering (best to do this just once rather than each time you draw the model). For this we use CopyAbsoluteBoneTransformsTo
Matrix[] transforms = new Matrix[testModel.Bones.Count];
testModel.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in testModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.Projection = projectionMatrix;
effect.View = viewMatrix;
effect.World = transforms[mesh.ParentBone.Index] * modelWorldMatrix;
}
mesh.Draw();
}
The important thing to note is the calculation of the world matrix that takes into account the world model matrix (where in the world the model is placed and rotated) and the bone matrix. See also the notes on Matrix.
You may find that when drawing your model it does not look right. It may look like the Z buffer is not working or that the textures are applied wrongly. This can be caused if you are also displaying 2D text or sprites. The SpriteBatch function changes the graphic states but does not, by default, set them back to what they were before. You can make it remember the state and set it back by using a different Begin overload for example:
spriteBatch.Begin(SpriteBlendMode.Alphablend,SpriteSortMode.Deferred,SaveStateMode.SaveState);
The above works but may cause some small slow down of code. The most efficient way would actually be to set just the required states when you render each model.
To animate our model manually we can simply alter one of the bone matrix. So if it was an upper arm we could rotate it. Note that each ModelMesh has a name so we can find which one to alter (as long as the artist has named them correctly). Ultimately though a model can contain one or more predefined sets of animation created by an artist e.g. walking animation or dying animation. These are handled quite differently involving key frames etc. and will be the subject of later notes.
Say we have a model with a bone with name "Neck" we would like to rotate it around the y axis so it moves from side to side. To do this we need to firstly remember the original transformation (store it on load) and then multiply it by a new transform each time.
In the LoadContent function:
originalBoneTransform = testModel.Bones["Neck"].Transform;
It is quite useful to use the gameTime object for this sent into a sin function e.g.:
In the Draw function:
testModel.Bones["Neck"].Transform = Matrix.CreateRotationY(-(float)Math.Sin((float)gameTime.TotalGameTime.TotalSeconds)) * originalBoneTransform;
testModel.CopyAbsoluteBoneTransformsTo(transforms);
// Draw as before
Note that the bones collection allows us to use the actual bone name to get the correct entry rather than having to work out the index directly - another cool feature of C# and XNA!