2010 February

2D (Deferred) Lighting

By | Blog, XNA Games | 36 Comments



2D Lighting Posts :

Last weekend i spended most of my time trying to create a lightning effect for my xna game that would support more then three lights at the same time (i had all lightning inside one shader, inside an array) and still support SM2.0. So i began building my own lightning system and the basics of it are now ready. This lightning system will first render four different types of textures:

  • The Color Map of the entire scene
  • The Depth Map of this scene (where needed)
  • The Normal Maps of all the objects inside the scene (where needed)
  • A combined effect that will become my Light Map.

Here are some examples of my current effect in action:



[Following up post effect]

Read more: 2D Lightning Continued

Now it is time for some code and let you try to achieve the same effect in your project and/or game.

Before we begin we need to declare some variables that we will be using inside most of the code segments.

Variables:

private RenderTarget2D _colorMapRenderTarget;
private RenderTarget2D _depthMapRenderTarget;
private RenderTarget2D _normalMapRenderTarget;
private RenderTarget2D _shadowMapRenderTarget;
private Texture2D _shadowMapTexture;
private Texture2D _colorMapTexture;
private Texture2D _normalMapTexture;
private Texture2D _depthMapTexture;

private VertexDeclaration _vertexDeclaration;
private VertexPositionTexture[] _vertices;

private Effect _lightEffect1;
private Effect _lightEffect2;

Before we can use any of these RenderTargets or Effects we must initialize or load them ofcourse. So before you do anything fancy with them, call the following initialization before you are going to render.

Initialization:

PresentationParameters pp = Globals.GraphicsDevice.PresentationParameters;
int width = pp.BackBufferWidth;
int height = pp.BackBufferHeight;
SurfaceFormat format = pp.BackBufferFormat;

_colorMapRenderTarget = new RenderTarget2D(graphicsDevice, width, height, 1, format);
_depthMapRenderTarget = new RenderTarget2D(graphicsDevice, width, height, 1, format);
_normalMapRenderTarget = new RenderTarget2D(graphicsDevice, width, height, 1, format);
_shadowMapRenderTarget = new RenderTarget2D(graphicsDevice, width, height, 1, format);

_lightEffect1 = Globals.ContentManager.Load<Effect>("ShadersLightningShadow");
_lightEffect2 = Globals.ContentManager.Load<Effect>("ShadersLightningCombined");

_vertices = new VertexPositionTexture[4];
_vertices[0] = new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0));
_vertices[1] = new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0));
_vertices[2] = new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1));
_vertices[3] = new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1));
_vertexDeclaration = new VertexDeclaration(Globals.GraphicsDevice, VertexPositionTexture.VertexElements);

Now that we have initialized and loaded the rendertargets and effect files, we can use them during our render process. The following steps will guide you through the rendering setup, altough i have my render setup like this you can always change this to your own needs. As long as you just get the three textures that are needed during the steps after this one.

ColorMap, DepthMap and NormalMap drawing:

// Set the render targets
graphicsDevice.SetRenderTarget(0, _colorMapRenderTarget);

// Clear all render targets
graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1, 0);

// Draw your scene here

// Reset the render target
graphicsDevice.SetRenderTarget(0, null);
graphicsDevice.SetRenderTarget(0, _normalMapRenderTarget);

// Clear all render targets
graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1, 0);

// Draw all your normal maps here

graphicsDevice.SetRenderTarget(0, null);
graphicsDevice.SetRenderTarget(0, _depthMapRenderTarget);

// Clear all render targets
graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1, 0);

// Draw all your depth maps here

// Deactive the rander targets to resolve them
graphicsDevice.SetRenderTarget(0, null);

// Gather all the textures from the Rendertargets
_colorMapTexture = _colorMapRenderTarget.GetTexture();
_normalMapTexture = _normalMapRenderTarget.GetTexture();
_depthMapTexture = _depthMapRenderTarget.GetTexture();

Now you should have three textures containing the scene in color, normal and depth variants. Like these:

[pictureframe image="http://www.soolstyle.com/wp-content/uploads/2010/02/Lightning001.png" align="left" lightbox="true" title="" link="" width="300" height="224"] [pictureframe image="http://www.soolstyle.com/wp-content/uploads/2010/02/Lightning002.png" align="left" lightbox="true" title="" link="" width="300" height="224"] [pictureframe image="http://www.soolstyle.com/wp-content/uploads/2010/02/Lightning003.png" align="left" lightbox="true" title="" link="" width="300" height="224"]

With the normal and depth textures we will begin drawing our lightmap. Our lightmap render function will make use of the shadowmap render target and after the render the generated texture will be returned.

private Texture2D GenerateShadowMap()
{
graphicsDevice.SetRenderTarget(0, _shadowMapRenderTarget);
graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1, 0);

graphicsDevice.RenderState.AlphaBlendEnable = true;
graphicsDevice.RenderState.SourceBlend = Blend.One;
graphicsDevice.RenderState.DestinationBlend = Blend.One;

// For every light inside the current scene, you can optimize this
// list to only draw the lights that are visible a.t.m.
foreach (var light in LightCollection)
{
_lightEffect1.CurrentTechnique = _lightEffect1.Techniques["DeferredPointLight"];
_lightEffect1.Parameters["lightStrength"].SetValue(light.Power);
_lightEffect1.Parameters["lightPosition"].SetValue(light.GetWorldPosition());
_lightEffect1.Parameters["lightColor"].SetValue(light.Color.ToVector3());
_lightEffect1.Parameters["lightRadius"].SetValue(light.LightRadius);

_lightEffect1.Parameters["screenWidth"].SetValue(graphicsDevice.Viewport.Width);
_lightEffect1.Parameters["screenHeight"].SetValue(graphicsDevice.Viewport.Height);
_lightEffect1.Parameters["NormalMap"].SetValue(_normalMapTexture);
_lightEffect1.Parameters["DepthMap"].SetValue(_depthMapTexture);

_lightEffect1.Begin();
foreach (var pass in _lightEffect1.CurrentTechnique.Passes)
{
pass.Begin();

// Draw the full screen Quad
graphicsDevice.VertexDeclaration = _vertexDeclaration;
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, _vertices, 0, 2);

pass.End();
}
_lightEffect1.End();
}

// Deactivate alpha blending...
graphicsDevice.RenderState.AlphaBlendEnable = false;
// Deactive the rander targets to resolve them
graphicsDevice.SetRenderTarget(0, null);

return _shadowMapRenderTarget.GetTexture();
}

The HLSL code required to render this is as following. Please note that this only supports Point lights at the moment, i am working on implementing more.

LightningShadow.fx

float screenWidth;
float screenHeight;

float lightStrength;
float lightRadius;
float3 lightPosition;
float3 lightColor;

Texture NormalMap;
sampler NormalMapSampler = sampler_state {
	texture = <NormalMap>;
	magfilter = LINEAR;
	minfilter = LINEAR;
	mipfilter = LINEAR;
	AddressU = mirror;
	AddressV = mirror;
};

Texture DepthMap;
sampler DepthMapSampler = sampler_state {
	texture = <DepthMap>;
	magfilter = LINEAR;
	minfilter = LINEAR;
	mipfilter = LINEAR;
	AddressU = mirror;
	AddressV = mirror;
};

struct VertexToPixel
{
	float4 Position : POSITION;
	float2 TexCoord : TEXCOORD0;
};

struct PixelToFrame
{
	float4 Color : COLOR0;
};

VertexToPixel VertexToPixelShader(float4 inPos: POSITION0, float2 texCoord: TEXCOORD0)
{
	VertexToPixel Output = (VertexToPixel)0;

	Output.Position = inPos;
	Output.TexCoord = texCoord;

	return Output;
}

PixelToFrame PointLightShader(VertexToPixel PSIn) : COLOR0
{
	PixelToFrame Output = (PixelToFrame)0;

	float3 normal = tex2D(NormalMapSampler, PSIn.TexCoord).rgb;
	normal = normal*2.0f-1.0f;
	normal = normalize(normal);

	float depth = tex2D(DepthMapSampler, PSIn.TexCoord);

	float3 pixelPosition;
	pixelPosition.x = screenWidth * PSIn.TexCoord.x;
	pixelPosition.y = screenHeight * PSIn.TexCoord.y;
	pixelPosition.z = depth;
	//pixelPosition.w = 1.0f;

	float3 shading;
	if (depth > 0)
	{
		float3 lightDirection = lightPosition - pixelPosition;
		float distance = 1 / length(lightPosition - pixelPosition) * lightStrength;
		float amount = max(dot(normal + depth, normalize(distance)), 0);

                float coneAttenuation = saturate(1.0f - length(lightDirection) / lightRadius);

		shading = distance * amount * coneAttenuation * lightColor;
	}

	Output.Color = float4(shading.r, shading.g, shading.b, 1.0f);
	return Output;
}

technique DeferredPointLight
{
    pass Pass1
    {
	VertexShader = compile vs_2_0 VertexToPixelShader();
        PixelShader = compile ps_2_0 PointLightShader();
    }
}

The result you get should be somewhat similar to the following image:

[pictureframe image="http://www.soolstyle.com/wp-content/uploads/2010/02/Lightning004.png" align="left" lightbox="true" title="" link="" width="300" height="224"]

Our next step involves in combining the Lightning map you just created with the Color Map from our first step. This process will be done through our second shader. This shader will only be a Pixel Shader because i had some problems rendering in inside a full screen quad. The whole image would become blurry and now with only the Pixel Shader in effect results in a clean outcome.

The follow shader is just a simple shader that combines the two textures with each other.

LightningCombined.fx

float ambient;
float4 ambientColor;
float lightAmbient;

Texture ColorMap;
sampler ColorMapSampler = sampler_state {
	texture = <ColorMap>;
	magfilter = LINEAR;
	minfilter = LINEAR;
	mipfilter = LINEAR;
	AddressU = mirror;
	AddressV = mirror;
};

Texture ShadingMap;
sampler ShadingMapSampler = sampler_state {
	texture = <ShadingMap>;
	magfilter = LINEAR;
	minfilter = LINEAR;
	mipfilter = LINEAR;
	AddressU = mirror;
	AddressV = mirror;
};

float4 CombinedPixelShader(float4 color : COLOR0, float2 texCoords : TEXCOORD0) : COLOR0
{
	float4 color2 = tex2D(ColorMapSampler, texCoords);
	float4 shading = tex2D(ShadingMapSampler, texCoords);

	float4 finalColor = (color2 * ambientColor * ambient);

	finalColor += color2 * shading * lightAmbient;

	return finalColor;
}

technique DeferredCombined
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 CombinedPixelShader();
    }
}

The actual drawing:

private void DrawCombinedMaps()
{
    _lightEffect2.CurrentTechnique = _lightEffect2.Techniques["DeferredCombined"];
    _lightEffect2.Parameters["ambient"].SetValue(_ambientPower);
    _lightEffect2.Parameters["ambientColor"].SetValue(_ambientColor.ToVector4());

    // This variable is used to boost to output of the light sources when they are combined
    // I found 4 a good value for my lights but you can also make this dynamic if you want
    _lightEffect2.Parameters["lightAmbient"].SetValue(4);
    _lightEffect2.Parameters["ColorMap"].SetValue(_colorMapTexture);
    _lightEffect2.Parameters["ShadingMap"].SetValue(_shadowMapTexture);

    spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
    _lightEffect2.Begin();
    foreach (var pass in _lightEffect2.CurrentTechnique.Passes)
    {
        pass.Begin();

        spriteBatch.Draw(_colorMapTexture,Vector2.Zero,Color.White);

        pass.End();
    }
    _lightEffect2.End();
    spriteBatch.End();
}

You see that i draw the effect onto the scene just by using a spritebatch and drawing the color map onto the screen. This way i can just use the Pixel shader to draw the effect onto the screen, as i said before the full screen quad blurred my image and made it look bad. I guess there is a fix for this but i didn’t encounter it yet.

Now you should have the desired effect drawn onto your screen like in the images i posted at the beginning of this blog post. Please note that this is just the beginning and i am very sure that i can have some improvements here and stere, but i just wanted to share what i got right now with you all. If you encounter any difficulties applying this effect, please let me know and maybe we can find a solution for it :).

I hope you found this post usefull, until the next time :D.

Spotlights:

I just finished up my SpotLight implementation. It currently looks like this:

[pictureframe image="http://www.soolstyle.com/wp-content/uploads/2010/02/Lightning009.png" align="left" lightbox="true" title="" link="" width="300" height="224"]

Game Object Component System

By | Blog, Featured, XNA Games | 10 Comments

Last time i updated with saying that i included a Game Object Component system. I have update this system, so that it uses interfaces instead of overriding virtual methods defined inside the GOComponent base class.

Here is a class diagram of my current state of the GO’s and GOC’s.

The Manager on the Top Left is optional, that is just a helper class to add new GO’s into the game and draw/update them when it is needed.

The following image show’s an object ingame:

This object is made of several GOC’s that are attached to one GO. The following list is an overview of the type of GOC’s that are attached to this GO.

  • GOCOnHit – Makes my GO hittable
  • GOCPhysicsObject – Attaches a Physics Body/Geom to the GO
  • GOCTexture – Makes the GO visible for the player (Texture)
  • GOCHealth – Because its hittable, it will need a Health Pool :).

These four GOC’s make up most of my environment objects for now that can be destroyed by the player. All four derive from the GOComponent base class, but only some of them are implementing the Interfaces as you can see on the class diagram. Now when the object will be Hit by another Physics object, the Physics GOC’s will process the collision handler. If the collision is valid, a message will be broadcasted among all the GOC’s and they act according to that specific message. Here is another picture if i keep hammering it with my guns, it explodes when its health reaches zero:

All those green lines and dots around my GO’s come from the Farseer Physics debug viewer. Every Game Object that has a Physics Body attached to it can be debugged this way.

I can post some code if it is needed, but this code will mostly be focused on my needs inside the game. So if you do want to see some code, just post a reply and we will make it work :).