Introduction

    In this article, I will try to show a way of using deferred rendering in an XNA game. First we will see what deferred shading means, and then explore each step required by this technique, from the creation of the Geometry Buffer, to the management of materials. In the end, we will take a look at creating a content processor to be used with our technique. In each step, I’ll try to paint a clear picture of the decisions that were made, and explain the alternatives. In later chapters, we will sometimes go back and rewrite (sometimes large) areas of code, because I think clarity is more important than flexibility in the earlier chapters. As a prerequisite, it is important to understand the different coordinate spaces that are used in graphics.  If you need to better understand the different coordinate spaces, like world space, view space, screen space, check the Shader Series on creators.xna.com. This being said, let’s get out hands dirty and see what deferred shading means.

    Real Time Lighting

    In current games, many objects need to be lit by many lights. Today this is an expensive task, and there is no perfect solution for this. Let’s look at the most common ways to solve this problem.

    In the Single-Pass lighting method, each object is rendered, and all lights affecting that object are computer in one shader. A sample of this technique can be found on creators.xna.com [4]. Since everything is done in a single shader, which has a limited instruction count, this technique is only suitable for a small number of lights (for example, the sample on Creator’s Club has a maximum of 2 lights in SM 2.0 and 8 lights in SM 3.0). In some games, which only need a small number of lights, such as an outdoor daytime environment, this may prove to be a good choice. The disadvantages of this technique are the small number of lights, and the fact that shading operations may be wasted on surfaces which will be hidden in the final image.

    Another method is the Multi-Pass lighting. Here, for each light, objects which are affected by the light are drawn with the current light’s shader. This causes a very high batch count (number of individual Draw calls), which in the worst case is equal to the number of lights multiplied by the number of objects. The problem of shading hidden surfaces still remains. And some operations are repeated many times each frame, such as vertex transformations.

Deferred Shading approaches the problem in a different manner. First, all objects are rendered without lighting computations. Instead, the objects output a set of attributes for each drawn pixel, such as position, normal, specular intensity, etc. After this, each light is applied on the final image as a 2D post process, using the data written in the previous pass. Because all objects use the same shader (the one which outputs the attributes), the engine management is greatly simplified. We no longed need to sort the scene objects based on the material they use. The number of draw calls is reduced to the number of objects + number of lights. Moreover, lighting calculations will only be done for visible pixels (the ones that make it into the final image).

    Deferred Shading

    Let’s look into more detail at what deferred shading does. As said before, we first need to render all object in order to obtain a set of attributes that are later used in the lighting process. This information is stored in a buffer called a Geometry Buffer (G-Buffer). Data usually stored in this buffer is:

  • Position – this is needed for local lights (lights which do not affect all objects). A global light (ambient and directional lights) affect all object equally. Local lights (point lights and spotlights) affect only objects which are close enough to them. To be able to make this differentiation, we need the position of each pixel.

  • Normal – this is required by ANY light computation, except for ambient light. This will most often be used to determine if the surface should be lit or not, using a dot product between the light direction and the normal of the surface. When generating the Normal, we can also use normal mapping to increase details on the surface.

  • Color – also called diffuse color, or albedo. This is usually the color that comes from the object’s texture.

  • Other attributes – depending on the lighting model we use, we may need other attributes such as: specular power, specular intensity, glow, some weird coefficients, etc.

    As you can see, that’s a lot of data needed for each pixel. From this comes the first disadvantage of deferred rendering, namely memory usage. Memory usage, and the fact that some of this data (normals, positions) needs to be stored in high precision (floating point textures); these are the main reasons why deferred rendering only became a viable option in the last few years. To accelerate this process, we also need to use Multiple Render Targets. We will explore the decisions we need to make about the G-Buffer in Chapter II. Creating the G-Buffer.

    After this step is done, we have all the information needed to apply lighting. For each light in the scene, we will do a 2D post processing of the image, and generate shading information. In this step, we can also compute shadows cast by objects. A shadow technique that integrates nicely into deferred shading is the usage of Shadow Maps.

    When applying a light, we first determine the screen area that may be influenced by this light (we’ll see how this is done later). For each pixel in this area, we get the corresponding information from the G-buffer, and compute how lit the current pixel is based on the equation of the light. The lightings from each light are blended, and in the end we combine the color data, with this lighting data, and obtain the final image. Because of how this process works, we can see that only visible pixels will be processed. Another thing we can observe is the fact that the amount of time spend on computations for each light is tightly connected to the screen-area covered by that light. This means that many small lights are just as cheap as a few large ones.

    If you analyze this process, you’ll probably notice two other disadvantages of deferred shading. Since the same lighting shader is applied on all pixels, and because we can only put so much data in the G-Buffer, the number of different materials that can be applied on the objects is limited. While in real-life, a single shader is applied on all objects, in games we usually want specific shaders for specific objects. We will see how we could alleviate this problem in a later chapter. The second disadvantage is the inability of deferred shading to handle transparent objects. This is cause by the fact that deferred shading only works with the nearest surface. This will also be discussed briefly in the last chapter.

    Finally, when rendering the final image, we can use the data we have until now for some more effects, like Volumetric Fog, Glow, HDR, Bloom, Edge Smoothing, Screen-Space Ambient Occlusion and others.

    Starting Code

    Before anything, please download Resources.zip. This archive contains the following files:

  • Camera.cs is a GameComponent that handles the camera. It is taken from the Skinned Model Sample.
    • use the Triggers or the Z and X keys to zoom out / in
    • use the Right Stick, or WASD to move the camera
  • QuadRendered.cs is a GameComponent taken from Ziggyware, which helps us draw quads on the screen, for 2D postprocessing. I prefer this class instead of using the SpriteBatch, because SpriteBatch is known to mess with some of our shader variables, like textures and samplers
  • null_normal.tga and null_specular.tga are two textures that will be used later
  • Models folder contains some models that will be used in this tutorial. These models come from samples or starter kits on creators.xna.com.

    The rest of this section sets up the starting code that will be used in the rest of the tutorial. If you want to skip it, download the prepared code : startup.zip, and then go to Chapter II. Creating the G-Buffer.

    This article will design the deferred renderer in such a way as to obtain a solution that is easily integrated in existing games. First, create a new project in XNA, and name it however you want (I named it DeferredShadingTutorial). Add the files Camera.cs and QuadRenderer.cs to your project.

    Next, create a new GameComponent( Right click on the project, select Add->New Item, and choose GameComponent), name it DeferredRenderer and then make it inherit from DrawableGameComponent. This will be the class containing most of our code. We will need to override the standard functions: LoadContent, Draw and Update. We then add two variables, one for a Camera, and one for a QuadRenderer, and initialize them in the Initialize function. At this moment, this class (DeferredRenderer.cs) should look like this:

using System; 
using System.Collections.Generic; 
using Microsoft.Xna.Framework; 
using Microsoft.Xna.Framework.Audio; 
using Microsoft.Xna.Framework.GamerServices; 
using Microsoft.Xna.Framework.Graphics; 
using Microsoft.Xna.Framework.Input; 
using Microsoft.Xna.Framework.Storage; 
using Microsoft.Xna.Framework.Content; 
namespace DeferredShadingTutorial 
{ 
    public class DeferredRenderer : Microsoft.Xna.Framework.DrawableGameComponent 
    { 
        private Camera camera; 
        private QuadRenderComponent quadRenderer; 
        public DeferredRenderer(Game game) 
            : base(game) 
        { 
        } 
        public override void Initialize() 
        { 
            base.Initialize(); 
            camera = new Camera(Game); 
            quadRenderer = new QuadRenderComponent(Game); 
            Game.Components.Add(camera); 
            Game.Components.Add(quadRenderer); 
        } 
        protected override void LoadContent() 
        { 
            base.LoadContent(); 
        } 
        public override void Draw(GameTime gameTime) 
        { 
            base.Draw(gameTime); 
        } 
        public override void Update(GameTime gameTime) 
        { 
            base.Update(gameTime); 
        } 
    } 
} 

    To keep the management of the scene separate from the rest of the code, we will also create a class that manages the scene. For this, create a new file, called Scene.cs, and add functions for initialization and drawing. This class should look similar to this:

using System; 
using System.Collections.Generic; 
using System.Text; 
using Microsoft.Xna.Framework; 
namespace DeferredShadingTutorial 
{ 
    class Scene 
    { 
        private Game game; 
        public Scene(Game game) 
        { 
            this.game = game; 
        } 
        public void InitializeScene() 
        { 
        } 
        public void DrawScene(Camera camera, GameTime gameTime) 
        { 
        } 
    } 
}

    Now, add a variable of class Scene into DeferredRenderer.cs, and initialize it inside LoadContent.

public class DeferredRenderer : Microsoft.Xna.Framework.DrawableGameComponent 
{ 
    [...] 
    private Scene scene;
 
    public DeferredRenderer(Game game) 
                    : base(game) 
    { 
        scene = new Scene(game); 
    } 
    protected override void LoadContent() 
    { 
        scene.InitializeScene(); 
        [...] 
    } 
} 
 

    This implementation of the scene class is only a small one, just enough to suit our needs for this tutorial. What I want to achieve by adding this class is more code clarity, and separation of scene code from deferred rendering code. This way, if someone might want to add some sort of scene management, like a quadtree, or octree, he/she would only need to modify the Scene class, not having to modify any of the DeferredRenderer code. The last thing to do is add the following lines the the constructor of the game class (Game1.cs).

public Game1() 
{ 
    [...] 
    DeferredRenderer renderer = new DeferredRenderer(this); 
    Components.Add(renderer); 
}

    Now that we have the basics set up, we can get to work.

    Requirements

    In order to implement the techniques described in this article, you will need a graphics card that support Multiple Render Targets and floating point textures. For ATI cards, this means Radeon 9500 or higher, and for NVIDIA, this means 6000 series or higher (Note: I’m not sure about the high-end FX series, like GeForce FX 5700+, because I didn’t get the chance to test them). The code for this article was written and tested on a GeForce 6600 GT, GeForce 7600 GTS, and on the Xbox360.

  • cwxwwwxwwxwx

    well, hi admin adn people nice forum indeed. how’s life? hope it’s introduce branch 😉

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

    Hi.
    This is not really a forum, but rather a blog hosting samples and tutorials :)

  • http://afallenapple.wordpress.com Daniel Hoctor

    I don’t know when you redid this, but this is a very nice read. I’ve been using Single-Pass Lighting for a while because every time I even glanced at deferred lighting, I would end up cowering in the corner.

    Thank you very much–this will be my new task for today and tomorrow; hopefully it works :)

  • http://catalinzima.com Catalin Zima

    I didn’t redo it. It’s the same tutorial that’s been here for months :)

  • http://afallenapple.wordpress.com Daniel Hoctor

    Hmmm… maybe I’m just getting better at understanding Xna 😉 lol

    Either way, thank you. This looks like it shouldn’t be too hard to implement. It’s a shame though, I’ve been using baked lighting for my environments–Single-Pass for my world objects–but I imagine I’ll be throwing away my baking system once I get DR up and running.

  • http://www.virgil.weebly.com vm

    Thanks so much Catalin! Excellent explanation of deferred rendering! Best I found so far! You helped me finally understand this!! :)

  • Md. mojammel haque

    Basically I am a asp.net web application developer and I know nothing about game development specially xna game development. Now I want to learn about xna game development. Can you please suggest me best resource about learning xna game development?

    Thanks.