Working with Shaders in XNA
Xna4.png

Introduction

In the previous tutorial, we created our very first shader in an effect file. In this tutorial, we will go through the steps of using a shader in an XNA game. This is actually not too hard to do.

Starting Code

Chances are, you have already got a game that you are working on that you can add this code to. However, I am providing some basic starter code, in case you are starting from scratch. Below is the code for my Game1 (the main game) class:

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 FirstShader
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
 
        Matrix world = Matrix.CreateTranslation(0, 0, 0);
        Matrix view = Matrix.CreateLookAt(new Vector3(0, 0, 10), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
        Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), 800f / 600f, 0.1f, 100f);
        float angle = 0;
        float distance = 10;
 
        Model model;
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
 
        protected override void Initialize()
        {
            base.Initialize();
        }
 
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            model = Content.Load<Model>("Models/Helicopter");
        }
 
        protected override void UnloadContent()
        {
        }
 
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
 
            angle += 0.01f;
            view = Matrix.CreateLookAt(distance * new Vector3((float)Math.Sin(angle), 0, (float)Math.Cos(angle)), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
 
            base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
 
            DrawModel(model, world, view, projection);
 
            base.Draw(gameTime);
        }
 
        private void DrawModel(Model model, Matrix world, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;
                    effect.World = world * mesh.ParentBone.Transform;
                    effect.View = view;
                    effect.Projection = projection;
                }
                mesh.Draw();
            }
        }
    }
}

This code should all look fairly familiar to you, since everything here was discussed in the 3D tutorials. Notice that I'm accessing a specific model. You can change this to access your own model, or you can download the helicopter model from my 3D Model Library.

Loading the Shader

The first step is to create a variable to store our effect in. As you have probably come to expect by now, XNA has a built in type for storing effects called the Effect class. If you are using the starter code above, add the following code in as an instance variable, next to the line that says Model model;, otherwise, add it to your game wherever you need it:

Effect effect;

Next, in the LoadContent() method, add the following line of code:

effect = Content.Load<Effect>("Effects/Ambient");

If you placed your effect file in a different directory, or if you called it something other than "Ambient.fx", you will need to put in a different name. It's nice to see, though, that we can load in effects just like models, textures, and everything else.

Shader Parameters

In the starter code, and in other tutorials, we have done our rendering with code like the following (this is identical to the starter code):

        private void DrawModel(Model model, Matrix world, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;
                    effect.World = world * mesh.ParentBone.Transform;
                    effect.View = view;
                    effect.Projection = projection;
                }
                mesh.Draw();
            }
        }

Our code for rendering with an effect is not really all that different, but there are a few differences. Add the following method, to render a model with the effect. You can even replace the old rendering code if you aren't going to use it anymore.

        private void DrawModelWithEffect(Model model, Matrix world, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = effect;
                    effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform);
                    effect.Parameters["View"].SetValue(view);
                    effect.Parameters["Projection"].SetValue(projection);
                }
                mesh.Draw();
            }
        }

Notice, first of all, that the inner loop goes through each of the mesh parts, rather than the effects in the mesh. In the line part.Effect = effect; we set the part's current effect to the one we want to use, which in our case is our instance variable that we loaded in the previous section. The next three lines set parameters for the effect. This is where we set the variables that we defined in the effect file. Inside the square brackets, you put the name of the variable you want to set, and then set the value you want. Since we created a variable called "World" in our effect file, we set it here in our XNA game to be the value that we want. We do the same thing with the "View" and "Projection" variables.

One thing that we haven't done here, is set the ambient light properties. In our shader, we defined default values for these, so I'm not worrying about it too much right now. But you could easily set these variables as well, with statements like:

effect.Parameters["AmbientColor"].SetValue(Color.Green.ToVector4());
effect.Parameters["AmbientIntensity"].SetValue(0.5f);

Any variable that you have in your effect file can be changed in this same way.

The last thing that we need to do is to call this method, instead of the old DrawModel() method. If you are using the starter code, go back to your Draw() method and change the line that says:

DrawModel(model, world, view, projection);

to say:

DrawModelWithEffect(model, world, view, projection);

You should now be able to run your game and see your shader in action, which should look something like the image below:

screenshot1.png

The entire code for the completed project is below.

What's Next?

You now know how to write a simple shader and use it in an XNA game. From here on, it is just a matter of writing fancier shaders. Our next step is to create a shader that does diffuse lighting, which is the main component of lighting.


troubleshoot64.png Having problems with this tutorial? Try the troubleshooting page!