Introducing the Light Manager…

I have finally managed to get round to implementing the LightManager. This post will explain how this in implemented into the engine and how to use the new class.

Back in the earlier version of this project, I wrote a simple Light Manager. This new LightManager class is based on the old one, but with a few new modifications. Start by creating a class in the Core folder called LightManager. Add the following namespaces:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ProjectVanquish.Renderers;
using ProjectVanquish.Cameras;

Declare the class as public and then add the following fields:

Effect directionalLightEffect, hemisphericLightEffect, pointLightEffect;
Model sphereModel;
QuadRenderer fullscreenQuad;
Vector2 halfPixel;
Texture2D hemisphericColorMap;
static Lights.DirectionalLight light;
static IList<Lights.PointLight> pointLights;

Here we are taking some of the fields from the DeferredRenderer class, so we can delete them from there. We can now add the constructor:

public LightManager(Game game)
    // Load Effects, Models and Quad Renderer
    directionalLightEffect = game.Content.Load("Shaders/Lights/DirectionalLight");
    pointLightEffect = game.Content.Load("Shaders/Lights/PointLight");
    hemisphericLightEffect = game.Content.Load("Shaders/Lights/HemisphericLight");
    sphereModel = game.Content.Load("Models/Sphere");
    fullscreenQuad = new QuadRenderer(game);
    halfPixel = new Vector2()
        X = 0.5f / (float)game.GraphicsDevice.PresentationParameters.BackBufferWidth,
        Y = 0.5f / (float)game.GraphicsDevice.PresentationParameters.BackBufferHeight

    // Load the Color Map for the Hemispheric Light
    hemisphericColorMap = game.Content.Load("Textures/ColorMap");

    // Make our directional light source
    light = new Lights.DirectionalLight();
    light.Direction = new Vector3(-1, -1, -1);
    light.Color = new Vector3(0.7f, 0.7f, 0.7f);

    // Instantiate the PointLights List
    pointLights = new List();

Back in the DeferredRenderer class, we had all of the old DrawLights code. We’ll start adding this into this class.

void DrawDirectionalLight(RenderTarget2D colorRT, RenderTarget2D normalRT, RenderTarget2D depthRT, Camera camera)
    // Set all parameters
    directionalLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.ViewMatrix * camera.ProjectionMatrix));

    // Apply the Effect

    // Draw a FullscreenQuad
    fullscreenQuad.Render(Vector2.One * -1, Vector2.One);

You’ll notice that the method declaration is different from the old one. This is because we don’t have direct access to the required fields like we did before, and I’d like to keep it that way. We’ll be passing them through the modified DrawLights method, which we’ll get to shortly. The next method to move is the DrawHemisphericLight:

void DrawHemisphericLight(GraphicsDevice device, SceneManager scene, Camera camera)
    device.BlendState = BlendState.Opaque;

    // Only apply the effect to those models in the Frustum
    foreach (Models.Actor actor in scene.Models.Where(a => camera.BoundingFrustum.Intersects(a.BoundingSphere)))
        foreach (ModelMesh mesh in actor.Model.Meshes)
            foreach (ModelMeshPart part in mesh.MeshParts)
                // Set the Effect Parameters
                hemisphericLightEffect.Parameters["matWorldViewProj"].SetValue(actor.World * camera.ViewMatrix * camera.ProjectionMatrix);
                hemisphericLightEffect.Parameters["vLightDirection"].SetValue(new Vector4(light.Direction, 1));
                hemisphericLightEffect.Parameters["SkyColor"].SetValue(new Vector4(light.Color, 1));

                // Apply the Effect

                // Render the Primitives
                device.SetVertexBuffer(part.VertexBuffer, part.VertexOffset);
                device.Indices = part.IndexBuffer;
                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount);

    device.BlendState = BlendState.AlphaBlend;

Before moving onto the DrawLights method, we’ll implement the DrawPointLights method:

void DrawPointLight(GraphicsDevice device, RenderTarget2D colorRT, RenderTarget2D normalRT, RenderTarget2D depthRT, Camera camera, Lights.PointLight pointLight)
    // Set the G-Buffer parameters

    // Compute the light world matrix
    // scale according to light radius, and translate it to light position
    Matrix sphereWorldMatrix = Matrix.CreateScale(pointLight.Range) * Matrix.CreateTranslation(pointLight.Position);

    // Light position

    // Set the color, radius and Intensity

    // Parameters for specular computations
    pointLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.ViewMatrix * camera.ProjectionMatrix));

    // Size of a halfpixel, for texture coordinates alignment

    // Calculate the distance between the camera and light center
    float cameraToCenter = Vector3.Distance(camera.Position, pointLight.Position);

    // If we are inside the light volume, draw the sphere's inside face
    if (cameraToCenter < pointLight.Range)
        device.RasterizerState = RasterizerState.CullClockwise;
        device.RasterizerState = RasterizerState.CullCounterClockwise;

    // Reset DepthStencilState
    device.DepthStencilState = DepthStencilState.None;

    // Apply the Effect

    // Draw the Sphere mesh
    foreach (ModelMesh mesh in sphereModel.Meshes)
        foreach (ModelMeshPart meshPart in mesh.MeshParts)
            device.SetVertexBuffer(meshPart.VertexBuffer, meshPart.VertexOffset);
            device.Indices = meshPart.IndexBuffer;
            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);

    // Reset RenderStates
    device.RasterizerState = RasterizerState.CullCounterClockwise;
    device.DepthStencilState = DepthStencilState.Default;

You may have noticed some changes to this method. We are now using a pointLight object. We’ll need to modify the PointLight class to allow for the new properties, but we’ll finish this class first. Lastly, we’ll add the modified DrawLights method:

public void DrawLights(GraphicsDevice device, RenderTarget2D colorRT, RenderTarget2D normalRT, RenderTarget2D depthRT, RenderTarget2D lightRT, Camera camera, SceneManager scene)
    // Set the Light RenderTarget

    // Clear all components to 0
    device.BlendState = BlendState.AlphaBlend;
    device.DepthStencilState = DepthStencilState.None;

    // Render either the Directional or Hemispheric light
    if (UseHemisphericLight)
        DrawHemisphericLight(device, scene, camera);
        DrawDirectionalLight(colorRT, normalRT, depthRT, camera);

    // Render each PointLight
    foreach (Lights.PointLight pointLight in pointLights)
        DrawPointLight(device, colorRT, normalRT, depthRT, camera, pointLight);

    // Reset RenderStates
    device.BlendState = BlendState.Opaque;
    device.DepthStencilState = DepthStencilState.None;
    device.RasterizerState = RasterizerState.CullCounterClockwise;

    // Reset the RenderTarget

We have now got our new LightManager class ready, so we can plug this into the DeferredRenderer class. In the DeferredRenderer class, add a new field:

private LightManager lightManager;

In the LoadContent method, we’ll instantiate the LightManager:

// Instantiate the LightManager
lightManager = new LightManager(Game);

We are reaching the final goal posts. There is one big change to the DeferredRenderer class. As we have modified the DrawLights call, we need to build a new method:

void CombineFinal(RenderTarget2D shadowOcclusion)
    // If SSAO is enabled, set the RenderTarget
    if (SSAORenderer.Enabled)

    // Set the effect parameters

    // Apply the Effect

    // Render a full-screen quad
    quadRenderer.Render(Vector2.One * -1, Vector2.One);

This will create our final scene. In the Draw method, we need to alter it to use the new LightManager.DrawLights method, and also include the new method. Place the following after the Render Shadows call:

// Draw Lights
lightManager.DrawLights(GraphicsDevice, colorRT, normalRT, depthRT, lightRT, camera, scene);

// Combine the Final scene

Before we can compile this, we need to modify the PointLight class. Below is the complete listing for the PointLight class:

protected float intensity = 0.0f;
protected float range = 0.0f; 

public PointLight(Vector3 position, Vector3 color, float range, float intensity)
    : base()
    Position = position;
    Color = color;
    Range = range;
    Intensity = intensity;

public float Intensity
    get { return intensity; }
    set { intensity = value;}

public Vector3 Position
    get { return worldMatrix.Translation; }
    set { worldMatrix.Translation = value; }

public float Range
    get { return range; }
    set { range = value; }

Because the SpotLight class is inherited from the PointLight class, we’ll need to modify the constructor of that too. Here is the modified version:

public SpotLight(Vector3 position, Vector3 color, float range, float intensity)
    : base(position, color, range, intensity)

Now, compiling the project shouldn’t give you any errors and you’ll be ok to run it, albeit without Point Lights as these are not yet implemented into the LightManager class yet. The full sourcecode can be downloaded from the Codeplex page, and the source code is found here.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s