Camera Types
Although any individual
game probably has only a single type of camera, there are a wide variety
that can be used—cameras that follow the players, cameras that never
move, and cameras that change depending on the circumstances...the
possibilities are endless.
Static Cameras
The
easiest camera to think about is a static camera. As the name implies, a
static camera doesn’t move. It sits there and just looks out.Let’s create a helper class that is your camera type.
Right-click your project, select Add->New Item, choose Game Component
(calling it CameraComponent.cs) and add the following variables to it:
protected Matrix view;
protected Matrix proj;
You need a way to get these values back from your component, so add two property getters for them now:
public Matrix View
{
get
{
return view;
}
}
public Matrix Projection
{
get
{
return proj;
}
}
You also need to initialize these to something reasonable, so in your Initialize overload, add the following:
view = Matrix.CreateLookAt(new Vector3(0, 0,-16),
Vector3.Zero, Vector3.Up);
proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
Game.GraphicsDevice.Viewport.AspectRatio, 1.0f, 100.0f);
With that, you’ve made yourself
a fixed static camera. Let’s modify the game to use this camera
instead. Add a new variable to your Game class to hold the camera:
Then, in your game’s Initialize overload, add this component to your game’s component list:
Components.Add(camera = new CameraComponent(this));
Then finally, switch your call to Draw on the model to use this camera component:
model.Draw(Matrix.CreateTranslation(pos), camera.View, camera.Projection);
If
you run the application now, you see a slightly different view because
your camera is in a different location. This camera can be extended to
support different types of cameras, which you should experiment with.
Models
What Is a Model?
A model
is essentially a collection of geometry that is rendered together to
form an object in the world.
If you use a Digital Content Creation (DCC) package, such as SoftImage Mod Tool (see Figure 1),
to create your objects, when you export those creations to a file (such
as an .fbx file or an .x file), you can add these to your content
projects to use the Model content importer. These are loaded into the Model class.
Models
The first method you can look at on the Model class is the Draw method. This method (as the name implies) draws the model using the
supplied transform with the provided view and projection matrices and
with the defaults for everything else. It is a quick and easy way to
show something onscreen as you’ve seen so far.
A Tag property can
also be used to store any type of data. Many content importers also
include extra information in this object for the game to use. Let’s take
a look at the Meshes property next.
Meshes
The Meshes property returns a collection of ModelMesh
objects. Although that is the
simplest form of a model, models can be extremely complex. Imagine a
model that represents a city. The city would have roads, buildings, and
signs. Each of these objects can be represented by a different model
mesh, whereas the combined set of meshes is the full model.
Each mesh also has a Draw
method that can be used to render the mesh separately, but notice that
this method has no parameters. This does the rendering of the mesh, but
it does so with the current settings of the effects rather than setting
the world, view, and projection matrices that the model’s Draw
method does. This is because there is extra information inherent to
each mesh that might change where it is in the world.
Another piece of information found on the mesh is the BoundingSphere
property, which describes a sphere that encompasses the entire mesh.
The bounding sphere object has a few fields and methods that are used
for a variety of reasons. The Center and Radius fields are used to describe the sphere, whereas there are a few helper methods such as Contains and Intersects used to detect objects in the sphere. You can also Transform the sphere using that helper method.
Each mesh can also have a Name (that can be set by your code or set during content importing and loading) and Tag similarly to the model itself. Aside from the bones (which are discussed in a moment), the last two properties on the mesh are Effects and MeshParts, which are interrelated. The Effects
collection is the collection of effects that is associated with each
mesh part and controls how the mesh parts are rendered. The number of
effects and mesh parts is identical, and you can update or modify the
effects for any of the mesh parts you’d like.
Each mesh part is the portion of the model that is actually rendered. Notice that one of the properties it has is the Effect that this part uses. Changing this property also affects the Effects
property on the mesh at the same index this mesh part exists at. The
rest of the properties contain the information about the geometry and
gives you the information you need to draw the mesh part with the DrawIndexedPrimitives method. You can see the IndexBuffer and VertexBuffer properties that hold the index and vertex data and the PrimitiveCount to dictate how many triangles to draw. When drawing data using this information, you can always use a primitive type PrimitiveType.TriangleList.
A few other pieces of
information on the mesh part can be used when setting the vertex buffer
and index buffer to the device, such as NumVertices (which is the number of vertices in the buffer), VertexOffset (which is the number of vertices to skip before getting to the vertices required for this mesh part), and StartIndexModel and ModelMesh before it, this also includes a Tag (which is the number of indices to skip before getting to the first index required by this mesh part). Like the property.
Normally each mesh part is
separated from the other portions of the mesh based on the material it
uses to render itself. You see the term material used often in DCC
packages and throughout literature on this subject.
For example, in the
imaginary city model, you can also imagine that the road portions of the
model are all one mesh. If you had three different kinds of roads, such
as a paved street, a gravel road, and a dirt road, you would
potentially use different textures and possibly even different effects
for those, so your roads mesh would have three different mesh parts: one
for the paved street, one for the gravel roads, and a final one for the
dirt roads.
See Figure 2 for an example of how models, meshes, and mesh parts are all interrelated.
Throughout the discussions of models, you didn’t about one particular type of object, so let’s take a look at it now.
Bones
The ModelMesh has a property ParentBone
that you skipped, and the model itself had quite a few methods and
properties that deal with bones. What exactly are bones (aside from
things like a femur or a song by Alice in Chains)? At a high level,
bones are what connect each mesh to every other mesh. See Figure 3 for an example of a simple model with three meshes.
Let’s assume you want to build a model of a person, and what is in Figure 3
is what you have so far. You have the sphere representing the head, a
cylinder representing the neck, and a cube representing the torso. Each
of these body parts can be considered a mesh in the larger full model,
and bones are what tie these together. Each bone tells you the
relationship between itself, its parent, and its children.
One of the meshes in the model is the root bone. This is the mesh that is the root of the other meshes and is accessed via the Root property on the model, which returns a ModelBone
object. These objects have a few different properties to help explain
the relationship of that particular bone to the others in the model.
First, it has the Children property that is a collection of ModelBone objects that define the children of this bone. You can also get the Parent (again, as a ModelBone) of this bone. Like the mesh, you can also get the Name of this bone, which can be set during content import and creation.
You also see the Index property, which is the index of this bone in the model’s Bones collection (which is the entire collection of bones for the model). Finally, you see the Transform property, which is the transform of this bone in relation to its parent. Let’s look at an example.
In the model in Figure 3,
you can imagine that the torso would be the root bone with a single
child being the cylinder forming the neck, which in turn, has a single
child as the sphere forming the head. If the model had arms and the
torso was the root, you could extrapolate out that it would have
multiple children for the neck, arms, and legs.
Now, assume that the torso has a transformation of Matrix.Identity.
It has no scale or rotation and it is located at the origin (0,0,0).
However, the neck is approximately three units above that, so the Transform property of the neck bone is Matrix.CreateTranslation(0,3,0). You can see that the head is even higher, yet approximately three units above its parent, the neck, so its Transform property would be Matrix.CreateTranslation(0,3,0), even though it is approximately six units above the torso! If your head was slightly turned, you could instead have its Transform property be Matrix.CreateRotationX(MathHelper.PiOver4) * Matrix.CreateTranslation(0,3,0).
The transform is always based on the parent bone and not the model
itself. This is especially useful for animated characters because like
in this example, you can easily have your head turn side to side by
simply changing the rotation transform on a single bone, rather than
repositioning the entire set of geometry.
There are also three methods on the Model class you can use to get or set these transforms. You can use the CopyBoneTransformsTo
method to create a copy of the transforms each bone has into a new
array of matrices (for example, what you might want to pass into an
effect). You can also use the similar (but opposite order) method of CopyBoneTransformsFrom to update all the bones with these new transforms. There is also another helper method called CopyAbsoluteBoneTransformsTo,
which like the nonabsolute one, copies all the transforms into an array
of matrices. The difference is this one combines each child’s transform
with all of its parent transforms.
What this means, in the previous example, by using CopyBoneTransformsTo you would have an array of three matrices: the first member having Matrix.Identity and the second two members having Matrix.CreateTranslation(0,3,0). However, if you use CopyAbsoluteBoneTransformsTo,
you would have the same array of three matrices with the first two
members remaining the same, but the third member would instead be Matrix.CreateTranslation(0,6,0) because it combines the transform with its parents transforms.
Rendering Models
Enough of all this
text—’let’s actually use the model class to draw some stuff onscreen!
Create a new Windows Game project and add any model you’d like from the
downloadable examples. Now, get ready to do some rendering. You need to
add a variable for your model, such as:
Next, update your LoadContent method to get the object instantiated:
model = Content.Load("YourModel");
foreach (ModelMesh mm in model.Meshes)
{
foreach (Effect e in mm.Effects)
{
IEffectLights iel = e as IEffectLights;
if (iel != null)
{
iel.EnableDefaultLighting();
}
}
}
Naturally, you need to
replace the YourModel with whatever model you picked. Although you
haven’t gotten to the effects yet, the next section of code enables
default lights for all portions of the model that have it.
Next, because you’ve picked
any random model you wanted, and models can be of a wide variety of
sizes, you need to add a helper method to get the size of the largest
mesh in the model, so you can scale it correctly. In a real game, you
never have to do this trickery because you control the scale and size of
the models, but here, it’s a quick operation to have a good guess. Add
this private function to your game class:
private float GetMaxMeshRadius(Model m)
{
float radius = 0.0f;
foreach (ModelMesh mm in m.Meshes)
{
if (mm.BoundingSphere.Radius > radius)
{
radius = mm.BoundingSphere.Radius;
}
}
return radius;
}
This goes through each mesh
and finds the one with the largest bounding radius and returns the
largest radius it finds. Now you can add another helper method to draw
the model, which you’ update a few times over the next few pages to see
the various ways of rendering the model. The first one is the easiest:
private void DrawModel(Model m, float radius, Matrix proj, Matrix view)
{
m.Draw(Matrix.CreateScale(1.0f / radius), view, proj);
}
The
only thing interesting you use for the model is the world matrix. You
create a scale that makes the model approximately 1.0f units. This
enables you to use a static camera regardless of model size and still
see it in its entirety.
Finally, you need to do the actual rendering. Replace your Draw method with the following:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
float radius = GetMaxMeshRadius(model);
Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
GraphicsDevice.Viewport.AspectRatio, 1.0f, 100.0f);
Matrix view = Matrix.CreateLookAt(new Vector3(2, 3, 4), Vector3.Zero, Vector3.Up);
DrawModel(model, radius, proj, view);
base.Draw(gameTime);
}
Notice here that you’re
positioning the camera slightly offset from the model and four units
away. This is possible only because of the scaling done in the DrawModel
call; otherwise, you have to base your camera size on the size of the
model. Nothing here is exactly new, though, and running the application
shows your model rendered on screen.
Next, let’s add the new method DrawModelViaMeshes to use the meshes instead of the single Draw method on model. Add the following method and update your DrawModel call to use this one instead:
private void DrawModelViaMeshes(Model m, float radius, Matrix proj, Matrix view)
{
Matrix world = Matrix.CreateScale(1.0f / radius);
foreach (ModelMesh mm in model.Meshes)
{
foreach (Effect e in mm.Effects)
{
IEffectMatrices iem = e as IEffectMatrices;
if (iem != null)
{
iem.World = GetParentTransform(m, mm.ParentBone) * world;
iem.Projection = proj;
iem.View = view;
}
}
mm.Draw();
}
}
This also needs a helper method to get the parent bone transform, so add this, too:
private Matrix GetParentTransform(Model m, ModelBone mb)
{
return (mb == m.Root) ? mb.Transform :
mb.Transform * GetParentTransform(m, mb.Parent);
}
So, what is going on here? You
set the initial world matrix you want to use as the basis for
everything, much like you did in the previous one-line call, but after
that, things seem to get much more complicated! In reality, this is
straightforward. For every mesh in your model, you go through the
effects and set the matrices each needs. The view and projection
matrices are the same for every effect (because you don’t want the
camera moving around based on which portion of the model you’ render);
however, the world matrix is vastly different.
Each mesh sets its world
matrix to the parent’s world matrix combined with the constant world
matrix for the model. Notice that the helper method to get the parent’s
world matrix is recursive. Because each bone’s transform is relative to
its parent’s transform, to get the full transform for any bone in the
model, you need to combine its transform with all of its parents, which
is what the helper method does. It stops when it gets to the root bone
because it has no parent and simply returns the root bone’s transform.
Note
In a real game, you do not
want to use this recursive function every single frame as you did here
to get the bone transforms. You would instead cache them (and use
perhaps the CopyAbsoluteBoneTransformsTo helper method), but it was done this way to illustrate the concept.
If you want even more control, however, you can render everything in the
model exclusively through the device methods without ever calling one
of the helper Draw methods. Add yet another helper method to draw the model via the vertex data itself and update your DrawModel call to use this one instead:
private void DrawModelViaVertexBuffer(Model m, float radius, Matrix proj, Matrix view)
{
Matrix world = Matrix.CreateScale(1.0f / radius);
foreach (ModelMesh mm in model.Meshes)
{
foreach(ModelMeshPart mmp in mm.MeshParts)
{
IEffectMatrices iem = mmp.Effect as IEffectMatrices;
if ( (mmp.Effect != null) && (iem != null) )
{
iem.World = GetParentTransform(m, mm.ParentBone) * world;
iem.Projection = proj;
iem.View = view;
GraphicsDevice.SetVertexBuffer(mmp.VertexBuffer, mmp.VertexOffset);
GraphicsDevice.Indices = mmp.IndexBuffer;
foreach (EffectPass ep in mmp.Effect.CurrentTechnique.Passes)
{
ep.Apply();
GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList, 0, 0,
mmp.NumVertices, mmp.StartIndex, mmp.PrimitiveCount);
}
}
}
}
}
Notice the similarities
between this one and the last one. You still need to enumerate through
each of the meshes; however, instead of calling the Draw
method on the meshes, enumerate through each of the mesh parts. You set
the world, view, and projection matrices as you did before, although
the actual rendering is done much differently.
You can see that the data needed to do the rendering is on the ModelMeshPart class. You need to set the vertex and index buffers to the device, which are on the part, and then the data needed to make the DrawIndexedPrimitives
call are also there. By default, models loaded via the built-in
importers from the content pipeline require the primitive type to be TriangleList, although creating your own importers can change this if you want to change it.