Book Image

3D Graphics with XNA Game Studio 4.0

By : Sean James
Book Image

3D Graphics with XNA Game Studio 4.0

By: Sean James

Overview of this book

<p>XNA is a very powerful API using which it's easy to make great games, especially when you have dazzling 3D effects. This book will put you on course to implement the same 3D graphics used in professional games to make your games shine, and get those gamers addicted! If you think 3D graphics is something that limits your games, then this book is for you.<br /><br />3D Graphics with XNA Game Studio 4.0 is a step by step companion to implement the effects used in professional 3D games in your XNA games. By the time you're done with this book your games would have eye-catching visuals and 3D effects. <br /><br />The one thing that can make or break a game is its appearance; players will mostly be attracted to a game if it looks good. With this book you will create your 3D objects and models and make them look more interesting by using shadowing and lighting techniques, and make them move in nasty ways using animation. Want to create realistic terrians for your games? Need some place for your 3D models to engage in battle? This book will enable you to do all that and more, by walking you through the implementation of numerous effects and graphics techniques used in professional games so that you can make them look great.</p>
Table of Contents (16 chapters)
3D Graphics with XNA Game Studio 4.0
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Drawing a model


Now that the model has been loaded, we are ready to draw it. We do this in the Draw() function of our game. Games redraw dozens of times per second, and each redraw is called a frame. We need to clear the screen before drawing a new frame, and we do so using XNA's GraphicsDevice.Clear() function. The single argument allows us to change the color to which the screen is cleared:

GraphicsDevice.Clear(Color.CornflowerBlue);

The first step of drawing the model itself is to calculate the view and projection matrices. To calculate the view matrix, we will use the CreateLookAt() static function of the Matrix class, which accepts as arguments a camera position, target, and up direction. The position is simply where in 3D space the camera should be placed, the target is the point the camera should be looking at, and the up vector is literally the direction that is "up" relative to the camera position. This code will go in the Draw() function after the GraphicsDevice is cleared:

Matrix view = Matrix.CreateLookAt(new Vector3(200, 300, 900),new Vector3(0, 50, 0),Vector3.Up);

There are some exceptions, but usually we calculate the projection matrix using the Matrix class' CreatePerspectiveFieldOfView() function, which accepts, in order, a field of view (in radians), the aspect ratio, and the near and far plane distances. These values define the shape of the view frustum as seen in the following figure. It is used to decide which objects and vertices are onscreen and which are not when drawing, and how to squish the scene down to fit onto the two-dimensional screen.

The near plane and far plane determine the distances at which objects will start and stop being drawn. Outside of the range between the two planes, objects will be clipped—meaning they will not be drawn. The field of view determines how "wide" the area seen by the camera is. Most first person shooters use an angle between 45 and 60 degrees for their field of view as anything beyond that range would start to distort the scene. A fish eye lens, on the other hand, would have a field of view closer to 180 degrees. This allows it to see more of the scene without moving, but it also distorts the scene at the edges. 45 degrees is a good starting point as it matches human vision closest without warping the image. The final value, the aspect ratio , is calculated by dividing the width of the screen by the height of the screen, and is used by the CreatePerspectiveFieldOfView() function to determine the "shape" of the screen in regards to its width and height. The GraphicsDevice has a precalculated aspect ratio value available that we can use when calculating the projection matrix:

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

The last matrix needed to draw the model is the world matrix. However, as discussed earlier, each mesh in the model has its own transformation relative to the model's overall transformation. This means that we will need to calculate a world matrix for each mesh. We'll start with the overall transformation and add the transformations from the modelTransformations array per mesh. Each mesh also has what is called an effect. We will look at effects in much more depth in the coming chapters, but for now, just remember that they are used to determine the appearance of a model. We will be using one of XNA's built-in effects (called BasicEffect) for now, so all we need to do is set its World, View, and Projection properties:

// Calculate the starting world matrix
Matrix baseWorld = Matrix.CreateScale(0.4f) * Matrix.CreateRotationY(MathHelper.ToRadians(180));

foreach (ModelMesh mesh in model.Meshes)
{
   // Calculate each mesh's world matrix
   Matrix localWorld = modelTransforms[mesh.ParentBone.Index]
        * baseWorld;

   foreach (ModelMeshPart part in mesh.MeshParts)
   {
      BasicEffect e = (BasicEffect)part.Effect;

      // Set the world, view, and projection matrices to the effect
      e.World = localWorld;
      e.View = view;
      e.Projection = projection;

      e.EnableDefaultLighting();
   }

   // Draw the mesh
   mesh.Draw();
}

XNA will already have added the last piece of code for you as well, but it is important to ensure that this code is still in place at the end of the Draw() function. This line of code simply calls the Draw() function of the base Game class, ensuring that the game runs correctly.

base.Draw(gameTime);

The complete code for the Game1 class is now as follows:

public class Game1 : Microsoft.Xna.Framework.Game
{
   GraphicsDeviceManager graphics;
   SpriteBatch spriteBatch;

   Model model;
   Matrix[] modelTransforms;

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

      graphics.PreferredBackBufferWidth = 1280;
      graphics.PreferredBackBufferHeight = 800;
   }

   // Called when the game should load its content
   protected override void LoadContent()
   {
      spriteBatch = new SpriteBatch(GraphicsDevice);

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

      modelTransforms = new Matrix[model.Bones.Count];
      model.CopyAbsoluteBoneTransformsTo(modelTransforms);
   }

   // Called when the game should update itself
   protected override void Update(GameTime gameTime)
   {
       base.Update(gameTime);
   }

   // Called when the game should draw itself
   protected override void Draw(GameTime gameTime)
   {
      GraphicsDevice.Clear(Color.CornflowerBlue);

      Matrix view = Matrix.CreateLookAt(new Vector3(200, 300, 900),new Vector3(0, 50, 0),Vector3.Up);

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

      // Calculate the starting world matrix
      Matrix baseWorld = Matrix.CreateScale(0.4f) * Matrix.CreateRotationY(MathHelper.ToRadians(180));

     foreach (ModelMesh mesh in model.Meshes)
     {
        // Calculate each mesh's world matrix
        Matrix localWorld = modelTransforms[mesh.ParentBone.Index]* baseWorld;

       foreach (ModelMeshPart part in mesh.MeshParts)
       {
          BasicEffect e = (BasicEffect)part.Effect;

          // Set the world, view, and projection 
          // matrices to the effect
          e.World = localWorld;
          e.View = view;
          e.Projection = projection;

          e.EnableDefaultLighting();
       }
          // Draw the mesh
         mesh.Draw();
      }

      base.Draw(gameTime);
   }
}

Run the game (Debug | Start Debugging, or F5) and you should see our spaceship in all its glory: