[LPP] Ambient Lights

April 2nd, 2010 | Posted by Michael in DirectX | Programming | XNA

Ambient lights are used in modern games to fake the indirect illumination that exists in the real world. By adding a generally very weak light to the scene, we can avoid the 100% black shadows that really should not exist, all at a very low cost compared to proper indirect illumination or baked ambient light maps.

I have found that the simplest way to integrate these ambient lights into the Light Pre Pass system, and allow for other elements to change the lights throughout the game is to create a new light type that fits into the normal lighting model.

This is probably the simplest type out there, you just create a full screen quad and render it using a shader that writes the colour information directly to the light buffer. All we have to pass to the shader is the colour and intensity. (you could always pre-compute this, but beware of the byte->int32 auto promotion C# does)

Here is the C# class:

public class AmbientLight
{
    public Color Color { get; set; }

    public bool Enabled { get; set; }

    private int intensity = 100;

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

    private Effect shader;

    public AmbientLight(Color col, int intensity)
    {
        this.Color = col;
        this.Enabled = true;
        this.intensity = intensity;
    }

    public void LoadContent(ContentManager content)
    {
        shader = content.Load<Effect>(@"Effects\Lights\l_ambient");
    }

    public void DrawLight(Renderer caller)
    {
        if (!Enabled)
            return;

        shader.Begin();
        shader.Parameters.TrySet("LightColor", Color.ToVector3());
        shader.Parameters.TrySet("Intensity", intensity);

        shader.CurrentTechnique.Passes[0].Begin();
        caller.FSQ.Draw();
        shader.CurrentTechnique.Passes[0].End();

        shader.End();
    }
}

and the shader:

float3 LightColor;
float Intensity;

float4 vs_main(in float4 pos : POSITION) : POSITION
{
    return pos;
}

float4 ps_main() : COLOR
{
    return float4(LightColor * (Intensity / 100), 0);
}

technique AmbientLight
{
    pass p0
    {
        VertexShader = compile vs_2_0 vs_main();
        PixelShader = compile ps_2_0 ps_main();
    }
}

I am in the middle of a crunch at work, and so future posts will be slow until it is all over. (That is why this one took a while to come out) I have not had the chance to do much work on my own XNA code, let alone tutorials. :)

Thanks for your patience, I cannot wait to get back to XNA. (and I cannot wait for XNA 4.0 with HiDef)

You can follow any responses to this entry through the RSS 2.0 You can leave a response, or trackback.

  • http://amapplease.blogspot.com/ Pete

    Nice!

    Let's hope we can share depth buffers among different rendertarget “buckets”.

    https://connect.microsoft.com/site226/feedback/…

  • http://www.gaspgames.com Alejandro

    Hi there Michael!,
    I have been playing around with rendering techniques, although still way behind in the process…

    I would like to comment about ambient color though, with a tip that John David said in his blog http://my-pseudocode-life.blogspot.com/

    Basically is to flash the device to the ambient color -> device.Clear(ambientColor), it's almost free. It would only work when additive blending all lights in the lighting pass of LPP. When the lighting pass actually starts blending all the lights, the ambient color is already there. You can clear it to a color with alpha zero so it doesn't mess with the speculars (in case specular is in alpha as your sample).

    And a question:
    Roughly seeing the sample I noticed that in your common.fxh, you have separated some sections using #ifdef preprocessors, so the actual .fx file only will get what is has been defined its “#define” directives. The question is, is this just a “best practices” to keep order in the shaders? or is it somehow faster since the shader has less “methods” to access to?
    (Although to my understanding until now, the shader parses the compiled file at run-time, where the unused fields and methods will be discarded on the compiled file, but you never now)

    Nice blog by the way!

  • http://mquandt.com/blog Chr0n1x

    Hi, thanks for your input.

    Yes you certainly can clear the device to do an ambient light, and if you are fine with restricting yourself to one ambient light or coding that directly into the renderer, then it is perfect.

    I decided for the tutorial to show the longer, but flexible method, however both work quite well, and generally you only need one ambient light anyway. (I am aiming to be very generic/open with my engine, so who knows what lighting setup I want to use in future :) )

    Yes you are right, the compiler should certainly remove unused methods, and hopefully unused variables. I add the pre-processor directives so that I can play around in future adding/disabling techniques. It also helps me in slightly more complicated shaders – by removing a #define I can trigger compiler errors telling me where the references to the now missing method/variable are, without having to delete the actual method, which I may want to reuse later.

    All of the code presented comes from my private engine at a certain point in time. I use that engine to experiment with techniques, and just copy over and edit the required parts for a sample.

    I'm glad you like my blog. =D
    I hope to have some more content coming soon, just finished up a big push towards a product release at work, and I have not had the time to do anything in XNA. I am also debating whether to wait for XNA 4.0 (HiDef) or continue in 3.1 and figure out some migration path when I can build against 4.0.

  • http://mquandt.com/blog Chr0n1x

    Hi, thanks for your input.

    Yes you certainly can clear the device to do an ambient light, and if you are fine with restricting yourself to one ambient light or coding that directly into the renderer, then it is perfect.

    I decided for the tutorial to show the longer, but flexible method, however both work quite well, and generally you only need one ambient light anyway. (I am aiming to be very generic/open with my engine, so who knows what lighting setup I want to use in future :) )

    Yes you are right, the compiler should certainly remove unused methods, and hopefully unused variables. I add the pre-processor directives so that I can play around in future adding/disabling techniques. It also helps me in slightly more complicated shaders – by removing a #define I can trigger compiler errors telling me where the references to the now missing method/variable are, without having to delete the actual method, which I may want to reuse later.

    All of the code presented comes from my private engine at a certain point in time. I use that engine to experiment with techniques, and just copy over and edit the required parts for a sample.

    I'm glad you like my blog. =D
    I hope to have some more content coming soon, just finished up a big push towards a product release at work, and I have not had the time to do anything in XNA. I am also debating whether to wait for XNA 4.0 (HiDef) or continue in 3.1 and figure out some migration path when I can build against 4.0.

  • http://www.gaspgames.com Alejandro

    Tips annotated!
    Reusable flexible code is half the battle.

    I can't wait to get my hands on XNA 4.0 also to start messing around with the render states objects and new render target rules so I can set aside current rules. The more work is done in 3.1 the more to change with 4.0. Hopefully test something on the phone.
    I'm really glad by the direction XNA is taking, who knows what we will see in the future.