Custom Content Processor and Normal Mapping

    To further improve our renderer, we can add two things:

  • add normal mapping on objects – this improves any kind of renderer
  • add specular maps – the way specular lighting is set now, we have the same specular intensity and power over a whole object. In order to have better looking objects, we can read the specular attributes from a texture, This way, some areas of the object can be shiny, while others matte.

    In order to add these two improvements, and to make adding new object to the game easier, prepared for deferred rendering, we will write a custom content processor for models. In this processor, we will load the model, and set its effect to RenderGBuffer.fx. If the model has a normal map and a specular map defined in its opaque data, then we will load those, otherwise, we will provide parameters, so the user of these processor can select what textures to be used for the normal and specular maps. This way, even if we didn’t build the model ourselves, we still have the possibility of assigning a specular map to it.

    Right click on the solution, and select Add->New Project, and from the template list, select Content Pipeline Extension Library (2.0), and name it Deferred Pipeline. A new project will be created, and a class for a content processor will be added. Go ahead an rename the file to DeferredRendererModel. This will be a model processor, so we modify the superclass to ModelProcessor. After modifying the signature of the Process function, our class will look like this:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using System.ComponentModel;
using System.IO;
using TInput = System.String;
using TOutput = System.String;
namespace DeferredPipeline
{
    [ContentProcessor(DisplayName = "Deferred Renderer Model")]
    public class DeferredRendererModel : ModelProcessor
    {
        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            return base.Process(input, context);
        }
    }
}

    This is the base of our processor, and now we will begin adding code to this class.

    Normal mapping needs normals, binormals and tangents. The default Model content processor has an attribute that lets us if we want the tangent frames generated. We will override this, and make it always return true. This way, we will always have tangent frames, which are needed by normal mapping. By setting the Browsable attribute to false, this parameter will no longer appear between the properties of the Content Processor.

[Browsable(false)]
public override bool GenerateTangentFrames
{
    get { return true; }
    set { }
}

    For the vertex shader input, we will only need 4 channels: texture, normal, binormal and tangent. Any other channel is useless for us, so, in order to minimize the quantity of data stored in the vertex buffer, we will remove any other vertex channel. This is done by overriding ProcessVertexChannel.

static IList<string> acceptableVertexChannelNames =
    new string[]
    {
        VertexChannelNames.TextureCoordinate(0),
        VertexChannelNames.Normal(0),
        VertexChannelNames.Binormal(0),
        VertexChannelNames.Tangent(0),
    };
protected override void ProcessVertexChannel(GeometryContent geometry,
                                            int vertexChannelIndex, ContentProcessorContext context)
{
    String vertexChannelName =
        geometry.Vertices.Channels[vertexChannelIndex].Name;
    // if this vertex channel has an acceptable names, process it as normal.
    if (acceptableVertexChannelNames.Contains(vertexChannelName))
    {
        base.ProcessVertexChannel(geometry, vertexChannelIndex, context);
    }
    // otherwise, remove it from the vertex channels; it's just extra data
    // we don't need.
    else
    {
        geometry.Vertices.Channels.Remove(vertexChannelName);
    }
}

    In the Process function, we will first verify if the input is valid, then get the path to the model’s directory, and save it for future reference, and then call a function that will look for the textures we want in the models content.

String directory;
public override ModelContent Process(NodeContent input, ContentProcessorContext context)
{
    if (input == null)
    {
        throw new ArgumentNullException("input");
    }
    directory = Path.GetDirectoryName(input.Identity.SourceFilename);
    LookUpTextures(input);
    return base.Process(input, context);
}

    Normal Map

    Next, we will implement the LookUpTextures function. This function takes each mesh from a model, and looks inside it, to see if we have any normal map and specular maps defined. We’ll first deal with the normal maps. In a model, each texture (which may be a normal map, specular map, texture map, etc) is assigned to a texture key. For the color textures, this is most usually called Texture. For the normal map, we don’t have a standard notation. For example, the lizard model in the Normal Mapping Sample uses the NormalMap texture key to refer to the normal map, while the models in the ShipGameStarterKit use the Bump0 texture key. If you are like me, and are not so good with art, you’ll probably want to use models from different sources, so each model might have the key for the normal map named different, or might not have it at all. To work around this, we will add some properties to the Content Processor. Note that Content Processor properties are only available in XNA GS 2.0, and are a very nice feature added to the Content Pipeline.

    We will have several properties. The NormalMapTexture can hold a path to the texture we wish to use as a normal map. NormalMapKey will indicate the name of the texture key to look for in the model, as discussed above. To avoid any problem that might come in the way of a non-artist programmer getting normal mapping on a model, we will use the following logic to determine the normal map.

  1. If the NormalMapTexture property is set, we use that normal map for all meshes in the model. This overrides anything else. A case when this is not desired is when a model contains several meshes, which use different textures.
  2. If NormalMapTexture is not set, we look into the opaque data of the model, and search for a texture with the key equal to NormalMapKey. NormalMapKey will have the default value NormalMap, but the user will be able to change this from the Content Processor properties. No matter what key we search for in the model (specified through NormalMapKey), after we get this data in the pipeline, we will use the key NormalMap, because this is what our shader will expect.
  3. If a key with the required name is not found, we make a final attempt, and search, in the same directory as the model, for a texture named meshname_n.tga, where meshname is the name of a mesh inside the model. This case will not be illustrated in this tutorial, but someone might find it useful if they generate the normal maps manually for a multi-mesh model, where the option of using NormalMapTexture is not suitable.
  4. Finally, is all else fails, we can throw an exception specifying that normal map cannot be used, or we can use a "default" normal map. The Normal Mapping Sample uses the first approach, while the ShipGame starter kit uses the second. We will use the second, because we may have models where we don’t wish to apply any normal maps, and using a default texture will still allow these model to get inside the game and render properly.

    For the properties (NormalMapTexture and NormalMapKey ), we will need to specify some attributes that tell Visual Studio how this property will appear in the Properties window of the Content Processor.

[DisplayName("Normal Map Texture")]
[Description("If set, this file will be used as the normal map on the model, " +
"overriding anything found in the opaque data.")]
[DefaultValue("")]
public string NormalMapTexture
{
    get { return normalMapTexture; }
    set { normalMapTexture = value; }
}
private string normalMapTexture;
[DisplayName("Normal Map Key")]
[Description("This will be the key that will be used to search the normal map in the opaque data of the model")]
[DefaultValue("NormalMap")]
public string NormalMapKey
{
    get { return normalMapKey; }
    set { normalMapKey = value; }
}
private string normalMapKey = "NormalMap";

    Now we create the function LookUpTextures, which we mentioned earlier. We verify if the NodeContent we are processing is a MeshContent or not. If it is, we use the logic described previously to search for the normal map. After we determine which texture will be used for the normal map, we save it in the NormalMap key of each material (this key will later be used by RenderGBuffer.fx to load the normal map and apply normal mapping), and then we call LookUpTexture on all the children of the current node.

private void LookUpTextures(NodeContent node)
{
    MeshContent mesh = node as MeshContent;
    if (mesh != null)
    {
        //this will contatin the path to the normal map texture
        string normalMapPath;
        //If the NormalMapTexture property is set, we use that normal map for all meshes in the model.
        //This overrides anything else
        if (!String.IsNullOrEmpty(NormalMapTexture))
        {
            normalMapPath = NormalMapTexture;
        }
        else
        {
            //If NormalMapTexture is not set, we look into the opaque data of the model, 
            //and search for a texture with the key equal to NormalMapKey
            normalMapPath =mesh.OpaqueData.GetValue<string>(NormalMapKey, null);
        }
        //if the NormalMapTexture Property was not used, and the key was not found in the model, than normalMapPath would have the value null.
        if (normalMapPath == null)
        {
            //If a key with the required name is not found, we make a final attempt, 
            //and search, in the same directory as the model, for a texture named 
            //meshname_n.tga, where meshname is the name of a mesh inside the model.
            normalMapPath = Path.Combine(directory, mesh.Name + "_n.tga");
            if (!File.Exists(normalMapPath))
            { 
                //if this fails also (that texture does not exist), 
                //then we use a default texture, named null_normal.tga
                normalMapPath = "null_normal.tga";
            }
        }
        else
        {
            normalMapPath = Path.Combine(directory, normalMapPath);
        }
        //No matter what key we searched for in the model (specified through NormalMapKey)
        //from this point forward, we will name it "NormalMap". This is what our shaders will expect
        foreach (GeometryContent geometry in mesh.Geometry)
        {
            //in some .fbx files, the key might be found in the textures collection, but not
            //in the mesh, as we checked above. If this is the case, we need to get it out, and
            //add it with the "NormalMap" key
            if (geometry.Material.Textures.ContainsKey(normalMapKey))
            {
                ExternalReference<TextureContent> texRef = geometry.Material.Textures[normalMapKey];
                geometry.Material.Textures.Remove(normalMapKey);
                geometry.Material.Textures.Add("NormalMap", texRef);
            }
            else
                geometry.Material.Textures.Add("NormalMap",
                                                    new ExternalReference<TextureContent>(normalMapPath));
        }
    }
    // go through all children and apply LookUpTextures recursively
    foreach (NodeContent child in node.Children)
    {
        LookUpTextures(child);
    }
}

    Specular Map

    As mentioned earlier, specular maps are a way in which we can add quality to our models easily. Instead of having all the surface of a model lit the same way, we use a specular map, similar to a texture map or a normal map, to specify the specular attributes for each pixel on the surface of the model.

    The logic we use in searching for the specular map is very similar to the one we used for the normal map. We will also have two properties, SpecularMapTexture, and SpecularMapKey.

  1. If the SpecularMapTextureproperty is set, we use that specular map for all meshes in the model. As before, this is not desired when a model contains several meshes, which use different textures.
  2. If SpecularMapTexture is not set, we look into the opaque data of the model, and search for a texture with the key equal to SpecularMapKey. SpecularMapKeywill have the default value SpecularMap, but the user will be able to change this from the Content Processor properties. Just like with the normal map, no matter what key is used (specified through SpecularMapKey), after this data is in the pipeline, we will use the key SpecularMap, because this is what our shader will expect.
  3. If a key with the required name is not found, we make a final attempt, and search, in the same directory as the model, for a texture named meshname_s.tga, where meshname is the name of a mesh inside the model.
  4. Finally, is all else fails, just like we did with the normal map, we load a default texture, named null_specular.tga.

    As an example, the lizard model, taken from the normal mapping sample, does not have any specular maps defined in the opaque data. This means that option 2 is not usable. The model is composed of several meshes: the lizard body, its eyes and a rock. Naturally, we want different specular maps for each one of these meshes. Thus, option 1 is also not usable. This leaves us with option 3, where the specular texture will be searched with a predefined name.

    In the code, we add the two needed properties.

[DisplayName("Specular Map Texture")]
[Description("If set, this file will be used as the specular map on the model, " +
"overriding anything found in the opaque data.")]
[DefaultValue("")]
public string SpecularMapTexture
{
    get { return specularMapTexture; }
    set { specularMapTexture = value; }
}
private string specularMapTexture;
[DisplayName("Specular Map Key")]
[Description("This will be the key that will be used to search the specular map in the opaque data of the model")]
[DefaultValue("SpecularMap")]
public string SpecularMapKey
{
    get { return specularMapKey; }
    set { specularMapKey = value; }
}
private string specularMapKey = "SpecularMap";

    The code is, again, very similar. We will add the code that handles specular maps, right after the node for the normal maps.

private void LookUpTextures(NodeContent node)
{
    MeshContent mesh = node as MeshContent;
    if (mesh != null)
    {
        [...] //code for normal maps
        string specularMapPath;
        //If the SpecularMapTexture property is set, we use it
        if (!String.IsNullOrEmpty(SpecularMapTexture))
        {
            specularMapPath = SpecularMapTexture;
        }
        else
        {
            //If SpecularMapTexture is not set, we look into the opaque data of the model, 
            //and search for a texture with the key equal to specularMapKey
            specularMapPath =mesh.OpaqueData.GetValue<string>(specularMapKey, null);
        }
        if (specularMapPath == null)
        {
            //we search, in the same directory as the model, for a texture named 
            //meshname_s.tga
            specularMapPath = Path.Combine(directory, mesh.Name + "_s.tga");
            if (!File.Exists(specularMapPath))
            {
                //if this fails also (that texture does not exist), 
                //then we use a default texture, named null_specular.tga
                specularMapPath = "null_specular.tga";
            }
        }
        else
        {
            specularMapPath = Path.Combine(directory, specularMapPath);
        }
        //add the keys to the material, so they can be used by the shader
        foreach (GeometryContent geometry in mesh.Geometry)
        {
            //in some .fbx files, the key might be found in the textures collection, but not
            //in the mesh, as we checked above. If this is the case, we need to get it out, and
            //add it with the "NormalMap" key
            if (geometry.Material.Textures.ContainsKey(normalMapKey))
            {
                ExternalReference<TextureContent> texRef = geometry.Material.Textures[normalMapKey];
                geometry.Material.Textures.Remove(normalMapKey);
                geometry.Material.Textures.Add("NormalMap", texRef);
            }
            else
                geometry.Material.Textures.Add("NormalMap",
                                new ExternalReference<TextureContent>(normalMapPath));
            if (geometry.Material.Textures.ContainsKey(specularMapKey))
            {
                ExternalReference<TextureContent> texRef = geometry.Material.Textures[specularMapKey];
                geometry.Material.Textures.Remove(specularMapKey);
                geometry.Material.Textures.Add("SpecularMap", texRef);
            }
            else
                geometry.Material.Textures.Add("SpecularMap",
                            new ExternalReference<TextureContent>(specularMapPath));
        }
    }
    // go through all children and apply LookUpTextures recursively
    [...]
}

    The last step is making the model use our shader, from RenderGBuffer.fx. This is done inside the ConvertMaterial function, where we copy each texture that is relevant for our renderer.

protected override MaterialContent ConvertMaterial(MaterialContent material,
                                                                                ContentProcessorContext context)
{
    EffectMaterialContent deferredShadingMaterial = new EffectMaterialContent();
    deferredShadingMaterial.Effect = new ExternalReference<EffectContent>("RenderGBuffer.fx");
    // copy the textures in the original material to the new normal mapping
    // material, if they are relevant to our renderer. The
    // LookUpTextures function has added the normal map and specular map
    // textures to the Textures collection, so that will be copied as well.
    foreach (KeyValuePair<String, ExternalReference<TextureContent>> texture
    in material.Textures)
    {
        if ((texture.Key == "Texture") ||
                (texture.Key == "NormalMap") ||
                (texture.Key == "SpecularMap"))
        deferredShadingMaterial.Textures.Add(texture.Key, texture.Value);
    }
    return context.Convert<MaterialContent, MaterialContent>(deferredShadingMaterial, typeof(MaterialProcessor).Name);
}

    This is all code needed for the Content Processor. As you may have noticed, we’ve done lots of checking and branching to make sure we get the right data in the game. If we had a artist to make our models, and follow a set of conventions (like each model having a normal map and a specular map, which are set in the opaque data) then our job would have been easier. However, since many of us XNA programmers do not have the luxury of specifying rules for artists, we wrote the code in such a way that two models made with very different specifications can be loaded inside the same game, and after the processing, they can be used in the same way, with absolutely no problem.

    Loading Content

    Now that we have the new Content Processor, we need to use it in our project. Right click on the Content project, select Add Reference -> Projects, and add the  Deferred Pipeline project. Our project can now use the content processors defined in DeferredPipeline to load models. If you now select the ship1.fbx model in the solution explorer, we can modify the content processor used to load this model, and we can now see the properties we defined for the content processor.

ContentProcessor

    But ignore that for now. First, we need to add null_normal.tga and null_specular.tga to our Content project, to make sure our content build doesn’t fail. Next, let’s add normal mapping.

    Specular and Normal Mapping

    We need to modify our shader, RenderGBuffer.fx, to take into account the new changes. We need to add two textures and samplers for them, for the specular map, and normal map. Remember from the content processor, that when outputting the data, we used two set keys, no matter what texture keys we used for the input. We called them NormalMap and SpecularMap, and said that the shader will expect these values. This means that the shader parameters that will hold these textures need to have the exact same name as those keys.

texture SpecularMap;
sampler specularSampler = sampler_state
{
    Texture = (SpecularMap);
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};
texture NormalMap;
sampler normalSampler = sampler_state
{
    Texture = (NormalMap);
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

    For normal mapping, our vertex shader has to receive binormals and tangents. Also, it will output a matrix that is used in the pixel shader to finally compute the normal. The normals from the normal map have to be combined with the normal, tangent and binormal of each vertex. This is what this matrix does. The modified shader input and output structures look like this:

struct VertexShaderInput
{
    float4 Position : POSITION0;
    float3 Normal : NORMAL0;
    float2 TexCoord : TEXCOORD0;
    float3 Binormal : BINORMAL0;
    float3 Tangent : TANGENT0;
};
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
    float2 Depth : TEXCOORD1;
    float3x3 tangentToWorld : TEXCOORD2;
};

    Inside the vertex shader, we no longer pass the normal to the pixel shader. Insted, we compute the tangentToWorld matrix.

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
    float4 worldPosition = mul(float4(input.Position.xyz,1), World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
    output.TexCoord = input.TexCoord;
    output.Depth.x = output.Position.z;
    output.Depth.y = output.Position.w;
    // calculate tangent space to world space matrix using the world space tangent,
    // binormal, and normal as basis vectors
    output.tangentToWorld[0] = mul(input.Tangent, World);
    output.tangentToWorld[1] = mul(input.Binormal, World);
    output.tangentToWorld[2] = mul(input.Normal, World);
    return output;
}

    In the pixel shader, we read the specular attributes from the specular map, and set them to the corresponding outputs. The specular intensity is read from any of the RGB channels in the texture. Specular Power is read from the alpha texture of the specular map. For the normal mapping, we read the normal from the normal map, transform it into [-1,1] space, and then, using the tangentToWorld matrix, we transform it into world space. Before outputting it, we need to transform it back into the [0,1] range.

PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)
{
    PixelShaderOutput output;
    output.Color = tex2D(diffuseSampler, input.TexCoord);
    float4 specularAttributes = tex2D(specularSampler, input.TexCoord);
    //specular Intensity
    output.Color.a = specularAttributes.r;
    // read the normal from the normal map
    float3 normalFromMap = tex2D(normalSampler, input.TexCoord);
    //tranform to [-1,1]
    normalFromMap = 2.0f * normalFromMap - 1.0f;
    //transform into world space
    normalFromMap = mul(normalFromMap, input.tangentToWorld);
    //normalize the result
    normalFromMap = normalize(normalFromMap);
    //output the normal, in [0,1] space
    output.Normal.rgb = 0.5f * (normalFromMap + 1.0f);
    //specular Power
    output.Normal.a = specularAttributes.a;
    output.Depth = input.Depth.x / input.Depth.y;
    return output;
}

    Before we run the game, we need to make some modifications inside Scene.cs. Since we now set the effect directly on the model, we no longer need to manage the effect or the textures from the code. Delete everything that has to do with textures and effect. Now we just need to load the model normally, and draw it after setting the World, View and Projection parameters. We no longer bother calling DrawIndexedPrimitives ourselves.

class Scene
{
    private Game game;
    Model shipModel;
    public Scene(Game game)
    [...]
    public void InitializeScene()
    {
        shipModel = game.Content.Load<Model>("Models\\ship1");
       // we don't care about effect or textures anymore
    }
    public void DrawScene(Camera camera, GameTime gameTime)
    {
        game.GraphicsDevice.RenderState.DepthBufferEnable = true;
        game.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
        game.GraphicsDevice.RenderState.AlphaBlendEnable = false;
        //we set the effect parameters, and draw the model by calling mesh.Draw()
        // this will setup the normal map and other textures for us, because we told it so in the content processor
        foreach (ModelMesh mesh in shipModel.Meshes)
        {
            foreach (Effect effect in mesh.Effects)
            {
                effect.Parameters["World"].SetValue(Matrix.Identity);
                effect.Parameters["View"].SetValue(camera.View);
                effect.Parameters["Projection"].SetValue(camera.Projection);
            }
            mesh.Draw();
        }
    }
}

    If now you set the Content Processor for ship1.fbx to DeferredRendererModel, you won’t see any difference. This is because, as mentioned before, the models from the ShipGame have different name for the normal and specular texture keys. Go ahead, and in the properties window, edit the Content Processor properties. For the Normal Map Key, use "Bump0", and for the Specular Map Key, use "Specular0". Also, from the Resources.zip, copy ship1_s.tga and ship1_n.tga into Content\\Models folder, but don’t add them to the project.They just need to be in the same directory as ship1.fbx.

contentparameters

    Run the game now, and you will see out ship, with specular and normal mapping applyied. To see the effect of specular mapping you can observe that some surfaces are shinier than other. For example, the windows of the ship are very matte, while the hull shines. The normal mapping is easily observer in the details and lines covering the surface of the ship.

normal_specular_mapping

    Next, to really show off the renderer we have until now, we will add several models and a floor, and draw all of them together. When adding these, we will also see different ways of using out newly written Content Processor.

    Demo Scene

    Now, copy everything from the Models directory inside Resources.zip, into the Content\Model folder of the project. When using ship1.fbx with our Content Processor, we had to set the NormalMapKey to Bump0 and the SpecularMapKey to Specular0. Add ship2.fbx to the project, and use the same settings, and also set the X Axis Rotation parameter of the content processor to -90, to make it stand horizontally.

    The lizard model is stored in lizard.fbx. This model has a normal map attached to it, but it has no specular maps. The normal map is accessed using the NormalMap as the NormalMapKey. To use specular, we could have manually chosen a texture to apply to the whole model, but in truth, having the same specular values for the rock and the lizard’s skin is not really interesting. So we are using the third option, where the content processor searched for a texture named meshname_s.tga (where meshname is the name of each mesh inside the model). For this, I created three files: eye_geo_s.tga (full white, the eyes are very shiny), lizard_geo_s.tga (different shades of white. The most noticeable thing is that the claws and horns of the lizard were made black) and rock_geo_s.tga (very dark, a rock is not shiny, but I’ve added some white spots, which will be shinier – maybe the rock is wet). The names of the meshes (eye_geo, rock_geo, lizard_geo) were found by opening lizard.fbx in a text editor, and looking at the name of each entity defined inside it. It is in a easily readable form. By placing these files in the same directory as the model, and by leaving the Specular Map Texture field blank, the Content Processor tried to find if a specular map is defined inside the model, and when that fails, it tried to load files terminated in _s.tga, and there it finds our files.

    Next, we want to use the model Ground.x. Direct X model files do not support opaque data, so the only way someone could have specified specular and normal maps inside it was to use an EffectInstance for materials, and let the default content processor load the model. However, let’s assume we got the model from another source (like one of the samples on creators.xna.com). besides ground.x, there are three other files in the directory: ground_diffuse.jpg, ground_normal.jpg, ground_specular.jpg. The first one is loaded automatically in the Texture parameter. to use ground_normal.jpg, and ground_specular.jpg, we need to specify them as the Normal Map Texture and Specular Map Texture parameters.

groundContent

    So now we have four models, from different sources and with different ways to store the normal and specular maps, ready to be used in the same way once inside our game. This would have been more difficult without using a custom Content Processor.

    Open Scene.cs, and put the following code in.

class Scene
{
    private Game game;
    Model[] models;
    public Scene(Game game)
    {
        this.game = game;
    }
    public void InitializeScene()
    {
        models = new Model[4];
        models[0] = game.Content.Load<Model>("Models\\ship1");
        models[1] = game.Content.Load<Model>("Models\\ship2");
        models[2] = game.Content.Load<Model>("Models\\lizard");
        models[3] = game.Content.Load<Model>("Models\\ground");
    }
    public void DrawScene(Camera camera, GameTime gameTime)
    {
        game.GraphicsDevice.RenderState.DepthBufferEnable = true;
        game.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
        game.GraphicsDevice.RenderState.AlphaBlendEnable = false;
        DrawModel(models[0], Matrix.CreateTranslation(-30,0,-20),camera);
        DrawModel(models[1], Matrix.CreateTranslation(30, 0, -20), camera);
        DrawModel(models[2], Matrix.CreateScale(0.05f) * Matrix.CreateTranslation(0, 0, 27), camera);
        DrawModel(models[3], Matrix.CreateTranslation(0, -10, 0), camera);
    }
    private void DrawModel(Model model, Matrix world, Camera camera)
    {
        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (Effect effect in mesh.Effects)
            {
                effect.Parameters["World"].SetValue(world);
                effect.Parameters["View"].SetValue(camera.View);
                effect.Parameters["Projection"].SetValue(camera.Projection);
            }
            mesh.Draw();
        }
    }
}

    Next, go in the DrawLights function in DeferredRenderer.cs, and add the following code.

private void DrawLights(GameTime gameTime)
{
    // set renderstates
    [...]
    Color[] colors = new Color[10];
    colors[0] = Color.Red; colors[1] = Color.Blue;
    colors[2] = Color.IndianRed; colors[3] = Color.CornflowerBlue;
    colors[4] = Color.Gold; colors[5] = Color.Green;
    colors[6] = Color.Crimson; colors[7] = Color.SkyBlue;
    colors[8] = Color.Red; colors[9] = Color.ForestGreen;
    float angle = (float)gameTime.TotalGameTime.TotalSeconds;
    int n =10;
    for (int i = 0; i < n; i++)
    {
        Vector3 pos = new Vector3((float)Math.Sin(i * MathHelper.TwoPi / n + angle), 0.30f, (float)Math.Cos(i * MathHelper.TwoPi / n + angle));
        DrawPointLight(pos * 40, colors[i%10], 15, 2);
        pos = new Vector3((float)Math.Cos((i + 5) * MathHelper.TwoPi / n - angle), 0.30f, (float)Math.Sin((i + 5) * MathHelper.TwoPi / n - angle));
        DrawPointLight(pos * 20, colors[i%10], 20, 1);
        pos = new Vector3((float)Math.Cos(i * MathHelper.TwoPi / n + angle), 0.10f, (float)Math.Sin(i * MathHelper.TwoPi / n + angle));
        DrawPointLight(pos * 75, colors[i%10], 45, 2);
        pos = new Vector3((float)Math.Cos(i * MathHelper.TwoPi / n + angle), -0.3f, (float)Math.Sin(i * MathHelper.TwoPi / n + angle));
        DrawPointLight(pos * 20, colors[i%10], 20, 2);
    }
    DrawPointLight(new Vector3(0, (float)Math.Sin(angle * 0.8) * 40, 0), Color.Red, 30, 5);
    DrawPointLight(new Vector3(0, 25, 0), Color.White, 30, 1);
    DrawPointLight(new Vector3(0, 0, 70), Color.Wheat, 55 + 10 * (float)Math.Sin(5 * angle), 3);
    GraphicsDevice.RenderState.AlphaBlendEnable = false;
    GraphicsDevice.SetRenderTarget(0, null);
    //apply FinalCombine.fx
    //to obtain the final image
    [...]
}

    This will draw 43 moving lights. The result should look like this:

demoscene

    If you run this on a PC, and you’re not satisfied with the framerate, try setting n to a lower value; or if your computer is bored, you can set higher values, like 32 ( => 131 lights) or 64 ( => 259 lights)

    In this chapter, we learned how to write a custom content processor, that prepares models for our renderer, and makes using them in a deferred renderer easy. We also added normal mapping and specular mapping. Normal mapping greatly increases the amount of detail on a model, while specular mapping allows us to vary the material on an object based on a texture. Although this is not so visible in our sample (we can see it on the rock and claws versus lizard’s skin, and on the cockpit versus the hull of the ships), this can be very useful for some models. Imagine a medieval armor with rust on some parts of it, or a sci-fi suit with metal, glass and plastic parts, all using a single texture. Specular maps are also a great tool to increase the quality and detail of a model. You can find the code for this chapter here: Chapter5.zip.

    This concludes the tutorial on deferred shading. But it’s not over yet. We still have two chapters to go. Next, we will talk about what we would need to do in order to add other lighting models to the renderer, besides the Phong model we used until now. This chapter will be more like a discussion of the possibilities, and we won’t go into a detailed tutorial, because this could be the topic of a full article by itself. Finally, we will talk about other possible improvements and work that can be done with a deferred renderer.

  • Andrew

    When I run the program it shows me the blank screen. :(

  • tigerspidey

    push some keys man, i think it was Z key that zooms out which will allow u to see.

  • Aaron Auseth

    The Chapter5.zip needs a couple of tweaks…
    Need to change camera.cs to set cameraDistance = 300;
    Also most of the lights in DeferredRender.cs/DrawLights are commented out. Remove those comments and the scene will be fully lit.

  • http://www.catalinzima.com Catalin Zima

    That’s odd… I wonder how I managed to miss that…

  • http://web70.login-82.hoststar.ch/files/lines/ Lucas

    Hi, I experience some strange horizontal lines when I run your sample project of this chapter. I got some screenshots in my website tag. Do you know why this is? When I move the camera the lines are getting smaller and they “float” over the screen..

  • http://www.catalinzima.com Catalin Zima

    That bug is related to the depth precision, and it seems to appear on PC, on some video cards. Ididn’t have enough resources to investigate it thoroughly.

    I believe the problem could be solved by using different near and far planes, and by experimenting with different representations of depth in the G-Buffer, but I didn’t have time to find a better solution.

  • Vando

    Hi,

    I’m trying to integrate Deferred Shading in my engine, for that I use your shaders but I’ve used my cameras, when I change to my camera and i get back a little everything goes darker and darker, until I’ve got a black screen:\ The blackness of scene had something about with camera parameters? My far plane is enough far to see everything:p

  • Dheeraj Mehta

    Hi,

    I was trying to implement your code and after implementing point lights I found two issues which I need your help for:

    1) Specular Highlights – I do not really want specular highlights when working with spotlights. It just goes all around the scene rather than staying in its area. How can I remove it?

    2) I would also like to lighten up the rays in spotlight giving it a more realistic feel. Can you please suggest how should I go about it.

    3) Right now, the effect of light stop appearing as soon as you get out of the light radius. Shouldnt it be more if you are out of the lightRadius, you should still be able to see the effect of that light in the area it affects.

  • http://www.visionsofafar.com Garrett Hoofman

    My video card had the same problem with the Horizontal lines crossing the screen. So I did as you said and played with the near and far plane. It’s only the near plane that’s causing the issue. When you make it smaller the lines spread out further, and when you make it larger, they become smaller.
    Around 10.0f for the near plane, the lines get too small to notice anymore. It kind of sucks that the near plane has to be so far away, but if needed, that will “fix” it.

  • Chris

    Hey, thanks for the great tutorial!

    I was having an issue with using the SpriteBatch after implementing this engine. It seems to be messing with the lights whenever I draw something to the screen with the spritebatch. I’ve used both the default blending mode and none. Any ideas?

    Thanks!

  • http://www.catalinzima.com Catalin Zima

    Chris, read this to understand why you are encountering those problems: http://blogs.msdn.com/shawnhar/archive/2006/11/13/spritebatch-and-renderstates.aspx

  • Chris

    I’m loving the speedy response,

    That was exactly what I was looking for :)

    Thanks bud, keep up the great work!

  • Steve

    I’m trying to find the normal and spec map keys in the ship1.fbx file so I can apply this concept to terrain models, but I can’t. How do I find them?

  • http://www.catalinzima.com Catalin Zima

    If you want the references to the normal map and specular map to be included directly in the .fbx file, you’ll need to do this through you content creation tool (3DStudioMax, Maya, XSI, etc.). You can’t just take a .fbx file, add a couple of lines inside it, and have the maps referenced (actually, you can, by adding a few dozen lines, but the structure of what you should is rather complicated).

    However, read the above post, and you’ll see that the Content Processor tries to find normal and specular maps in several ways, if they are not specified inside the actual object. I built it so specifically to allow such maps on models made by people who don’t have enough skill to integrate the normal/specular maps directly through the content creation tool.

  • Christina Andersen

    Hi,

    I love your tutorial, its on the point and very good explained!
    But I got one question! How can I render many models, but not using the content pipeline you created? ( I got another content pipeline for other models so I cannot combine them ).

    Should I replace the code in DrawModel to set all parameteres requred for each model? Say I got two models that I want different textures on for color, normal and specular. Should I first call gbuffereffect.Begin() draw one with a few parameters set on the gbuffereffect, and then call gbuffereffect.End() and then on the next model, call gbuffereffect.Begin(), set the parameters and then gbuffereffect.End() and so on for all models?

    Thank you for reading :)

  • Alex

    Hey,

    I followed your tutorial and the result is working like a charm. I also tried your Chapter5.zip source for comparison but there I get the same horizontal lines as mentioned earlier. So I urge everyone who experiences these problems to work their way through the tutorial!
    One minor addition, I had to turn of depth testing in the lighting passes. Otherwise successive lights would be clipped away partially.

  • Aitor

    Hi
    I have implemented the tutorial succesfully in XNA but now I am trying to implement it in D3D. For now I have the 3 rendertargets working fine, but the Normal Map does nothing. If have the same normal texture if I use an empty normal map (the same of the tutorial) that if I use another normal map whit data. I´m using the HLSL code of the tutorial.

    Thanks, and sorry for my poor english.

  • http://www.igad.nl/ Wessel

    I resolved the horizontal lines by changing the SurfaceFormat of the depth render target to SurfaceFormat.Vector4

  • Pingback: Storing useful positions using meta-bones - MVI Network

  • http://Website Fabian

    Hello Catalin,

    thanks a lot for your tutorial. I have just worked it through using XNA 4. Due to differences between XNA 2 and 4 I had to make a few modifications but now everything is running as expected and I also added spot lights.

    Since there were quite some steps to take, maybe it would be a good idea to spare others who are using XNA 4 aswell the trouble and update your great tutorial.

    I’d be glad to provide my resources and information on what had to be done.