Alpha blending sorted!

I have uploaded a new release, 20226. It’s not a full v0.0.5 release, but it now supports alpha blending. You can see this when the moon comes out and shows it’s glow. The fix was in the RenderTargets, and the fact that Color doesn’t allow AlphaBlending. Just need to resolve the, what looks like, depth fighting issue as you should be able to clearly see the moon like in the debug output window.

Ah, tis time for bed

Advertisements

Slow and steady

Work is still continuing with the project. I’m looking at releasing the next lot of code soon. It won’t have the hemispheric light in there as that is part of the new lighting system, but it will have the shadow enhancements. Don’t forget that you can get the source code over at Project Vanquish Codeplex.

UDK maps

I caught wind that you could use UDK to develop maps and run them in XNA. This intrigued me some what, so I decided to give it a go. The lighting needs to be tweaked, but I think that there is more to the exporting process than just clicking Export. Still, my shadow mapping works really nicely.

New lighting system

Over the next couple of days, I’ll be revisiting the lighting system in order to get a more complete system which is easy to use, and also implement new lights like Spot Lights. Here is the new hemispheric light:

Got the kettle on?

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);
    game.Components.Add(fullscreenQuad);
    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["colorMap"].SetValue(colorRT);
    directionalLightEffect.Parameters["normalMap"].SetValue(normalRT);
    directionalLightEffect.Parameters["depthMap"].SetValue(depthRT);
    directionalLightEffect.Parameters["lightDirection"].SetValue(light.Direction);
    directionalLightEffect.Parameters["Color"].SetValue(light.Color);
    directionalLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
    directionalLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.ViewMatrix * camera.ProjectionMatrix));
    directionalLightEffect.Parameters["halfPixel"].SetValue(halfPixel);

    // Apply the Effect
    directionalLightEffect.Techniques[0].Passes[0].Apply();

    // 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["matInverseWorld"].SetValue(actor.World);
                hemisphericLightEffect.Parameters["vLightDirection"].SetValue(new Vector4(light.Direction, 1));
                hemisphericLightEffect.Parameters["ColorMap"].SetValue(hemisphericColorMap);
                hemisphericLightEffect.Parameters["LightIntensity"].SetValue(0.4f);
                hemisphericLightEffect.Parameters["SkyColor"].SetValue(new Vector4(light.Color, 1));

                // Apply the Effect
                hemisphericLightEffect.Techniques[0].Passes[0].Apply();

                // 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
    pointLightEffect.Parameters["colorMap"].SetValue(colorRT);
    pointLightEffect.Parameters["normalMap"].SetValue(normalRT);
    pointLightEffect.Parameters["depthMap"].SetValue(depthRT);

    // 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);
    pointLightEffect.Parameters["World"].SetValue(sphereWorldMatrix);
    pointLightEffect.Parameters["View"].SetValue(camera.ViewMatrix);
    pointLightEffect.Parameters["Projection"].SetValue(camera.ProjectionMatrix);

    // Light position
    pointLightEffect.Parameters["lightPosition"].SetValue(pointLight.Position);

    // Set the color, radius and Intensity
    pointLightEffect.Parameters["Color"].SetValue(pointLight.Color);
    pointLightEffect.Parameters["lightRadius"].SetValue(pointLight.Range);
    pointLightEffect.Parameters["lightIntensity"].SetValue(pointLight.Intensity);

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

    // Size of a halfpixel, for texture coordinates alignment
    pointLightEffect.Parameters["halfPixel"].SetValue(halfPixel);

    // 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;
    else
        device.RasterizerState = RasterizerState.CullCounterClockwise;

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

    // Apply the Effect
    pointLightEffect.Techniques[0].Passes[0].Apply();

    // 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
    device.SetRenderTarget(lightRT);

    // Clear all components to 0
    device.Clear(Color.Transparent);
    device.BlendState = BlendState.AlphaBlend;
    device.DepthStencilState = DepthStencilState.None;

    // Render either the Directional or Hemispheric light
    if (UseHemisphericLight)
        DrawHemisphericLight(device, scene, camera);
    else
        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
    device.SetRenderTarget(null);
}

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)
        GraphicsDevice.SetRenderTarget(sceneRT);

    // Set the effect parameters
    finalCombineEffect.Parameters["colorMap"].SetValue(colorRT);
    finalCombineEffect.Parameters["lightMap"].SetValue(lightRT);
    finalCombineEffect.Parameters["halfPixel"].SetValue(halfPixel);
    finalCombineEffect.Parameters["shadowOcclusion"].SetValue(shadowOcclusion);

    // Apply the Effect
    finalCombineEffect.Techniques[0].Passes[0].Apply();

    // 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
CombineFinal(shadowOcclusion);

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.

Some interesting findings

Whilst I was so eager to get the Sky dome rendering working, I hadn’t realised but I had broken the PointLight implementation. I had changed the RenderTarget settings so that the new Moon functionality would work, but this is where it all went wrong. For now, I’ve commented out the DrawGlow and DrawMoon methods and reinstated the correct RenderTarget settings:

Correct settings:

colorRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth24);
normalRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Color, DepthFormat.None);
depthRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Single, DepthFormat.None);
depthTexture = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Single, DepthFormat.Depth24);
lightRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Color, DepthFormat.None);
sceneRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth24);

That's fixed the Sky brightness

Adding new hemispheric light

It’s been a short while since I’ve posted, so I thought I’d let you know what I’ve been working on. Whilst I’ve managed to finish of the SSAO implementation, I have been trying to implement a new light. So far, it’s going well, but I have a problem with the models appearing transparent.

So, I’m looking into this at that moment and will post news as it happens.