My technique for the shader-based dynamic 2D shadows

It’s been a long time coming, but finally I get the time to explain the technique I came up with for drawing shader-based dynamic 2D shadows.

If you want to dive right into the code, you can go to the sample’s page.

Still here? Ok then, let’s get started.

The core of the technique is drawing all the possible shadow casters around a light into a texture, and then using a few shaders to turn this image into one containing all shadows cast in the scene. First, for a general view, consider the following scene.

0_unlit_Scene

Out goal is to have all objects cast shadows, with the two orange squares being the sources of light.

The first step is to build for each light a image of all the casters around it. This basically means drawing all objects with a black color into a texture, centered around the light. The technical side of this should be pretty trivial, and can also be seen in the code.
1_casters_light11_casters_light2

The tricky part comes next and this is the main part of the technique. I will explain the whole process a little later, but for now just know that the technique I use takes as input these “caster maps” and produces a “shadow image”, as seen below.

7_shadows_blured_attenuated8_shadows_2

Having these two shadow images, it is now easy to blend them together, using the desired light color for each, and then blending the result over the “ground”.

9_shadows_combined

10_ground
In the end, drawing the items in the scene leads to our desired result.

11_scene

Most of these steps should be fairly easy. The problem comes in getting from the image containing all the shadow casters to the image containing the shadows themselves.

The technique is really not that complicated, so I’ll explain each step, together with shader code and images. So let’s start with the beginning, namely the shadow casters image.

1_casters_light1
1. Starting from this, for each non-transparent pixel (non-white in the illustration), I output the distance from the center of the texture to that pixel (i.e. the distance from the light source to the pixel).

float4 ComputeDistancesPS(float2 TexCoord : TEXCOORD0) : COLOR0
{
float4 color = tex2D(inputSampler, TexCoord);
     //compute distance from center
float distance = color.a>0.3f?length(TexCoord - 0.5f):1.0f;
//save it to the Red channel
distance *= renderTargetSize.x;
return float4(distance,0,0,1);
}

One thing to notice is that I multiply by the size of the render target and save to a floating point format. I do this in order to preserve precision. You could store this as a value between 0 and 1, and write it into a render target with the Color surface format, but this can lead to some imprecision when using large lights.

The results looks something like below (shown in grayscale for easy visualization).

2_distances
2. In the next step, I divide the image into four quadrants around the light (center of this image), and distort it in such a way that all “light rays” (which are at the moment spreading in all directions from the light) are positioned in parallel. This is much easier to understand with an image, as seen below.

2_distancesmarked3_distortedX - Copy
In the pictures above, the quadrant to the left (and the one to the right) has been distorted so it takes up half of the image, and all pixels are placed as if the rays starting from the light are parallel. The reason I am doing this will be obvious in the next step. Since I need this data for all the quadrants, I make the same operation for the vertical direction (rotating the result so it’s also horizontal).

3_distortedY

I know the text doesn’t make as much sense as I would like but hopefully the images illustrate what I mean. Finally, to reduce memory usage and to perform more computations in parallel, I store the result of the horizontal distortion in the red channel, and the result of the vertical distortion in the green channel.

3_distortedCombine
I know it doesn’t look like much, but know that all the data we need for all directions is now stored in a single texture. The code that achieves this step is the shader function below. This code can probably be simplified (to avoid the conversions between the [0,1] and [-1,1] domains, but I find it clearer this way.

float4 DistortPS(float2 TexCoord : TEXCOORD0) : COLOR0
{
//translate u and v into [-1 , 1] domain
float u0 = TexCoord.x * 2 - 1;
float v0 = TexCoord.y * 2 - 1;

//then, as u0 approaches 0 (the center), v should also approach 0
v0 = v0 * abs(u0);
//convert back from [-1,1] domain to [0,1] domain
v0 = (v0 + 1) / 2;
//we now have the coordinates for reading from the initial image
float2 newCoords = float2(TexCoord.x, v0);

//read for both horizontal and vertical direction and store them in separate channels

float horizontal = tex2D(inputSampler, newCoords).r;
float vertical = tex2D(inputSampler, newCoords.yx).r;
return float4(horizontal,vertical ,0,1);
}

3. Now that we have the distorted images of the distances from the light along each ray going out from the light, it’s time to compute the minimum along each of these rays. I do this by successive reduction of the image along the horizontal direction, until a texture of width 2 is obtained. This texture is something very similar to a shadow map in 3D, in that it contains for each ray, the minimum distance from the light where a shadow caster is present.

4_shadowMap

This step is also currently the bottleneck of the algorithm, since reducing a texture of 512*512 to a size of 2*512 requires about 9 passes (the dimension is reduced by a factor of 2 through each pass). The reduction shader is a simple one:

float4 HorizontalReductionPS(float2 TexCoord : TEXCOORD0) : COLOR0
{
float2 color = tex2D(inputSampler, TexCoord);
float2 colorR = tex2D(inputSampler, TexCoord + float2(TextureDimensions.x,0));
float2 result = min(color,colorR);
return float4(result,0,1);
}

At this point, it should be clear why I distorted the pixels in such a way (in order to be able to apply a horizontal reduction) and why the vertical components were also rotated and stored in a separate channel. Basically, we make the reductions on all directions at the same time.

Note: because this is the bottleneck, an option is to store the caster texture and do all operation on half the size of the actual light area. Thus for a light area of 512*512 you could make the processing on textures of 256*256. This way you loose some crispiness (not always a bad thing when it comes to shadows) but you also gain some performance.

4. At this moment, we can generate the shadow image. We draw the area surrounding the light, and for each pixel, we compare the distance between this pixel and the light to the distance stored in the shadow map along the same ray. This information tells us if we are in front or behind the shadow caster.

float4 DrawShadowsPS(float2 TexCoord: TEXCOORD0) : COLOR0
{
// distance of this pixel from the center
float distance = length(TexCoord - 0.5f);
distance *= renderTargetSize.x;

//apply a 2-pixel bias
distance -=2;

//distance stored in the shadow map
float shadowMapDistance;
//coords in [-1,1]
float nY = 2.0f*( TexCoord.y - 0.5f);
float nX = 2.0f*( TexCoord.x - 0.5f);

//we use these to determine which quadrant we are in
if(abs(nY)<abs(nX))
{
shadowMapDistance = GetShadowDistanceH(TexCoor
}
else
{
shadowMapDistance = GetShadowDistanceV(TexCoord);
}

//if distance to this pixel is lower than distance from shadowMap,
//then we are not in shadow
float light = distance < shadowMapDistance ? 1:0;
float4 result = light;
result.b = length(TexCoord - 0.5f);
result.a = 1;
return result;
}

The functions that read from the shadow map make an inverse transformation of coordinates in order to read from the proper location in the shadowmap.

float GetShadowDistanceH(float2 TexCoord)
{
 float u = TexCoord.x;
 float v = TexCoord.y;

 u = abs(u-0.5f) * 2;
 v = v * 2 - 1;
 float v0 = v/u;
 v0 = (v0 + 1) / 2;

 float2 newCoords = float2(TexCoord.x,v0);
 //horizontal info was stored in the Red component
 return tex2D(shadowMapSampler, newCoords).r;
}

float GetShadowDistanceV(float2 TexCoord)
{
 float u = TexCoord.y;
 float v = TexCoord.x;

 u = abs(u-0.5f) * 2;
 v = v * 2 - 1;
 float v0 = v/u;
 v0 = (v0 + 1) / 2;

 float2 newCoords = float2(TexCoord.y,v0);
 //vertical info was stored in the Green component
 return tex2D(shadowMapSampler, newCoords).g;
}

(Note: in the source code, you might notice a commented out version of the DrawShadowsPS function. That one was using three taps in order to smooth out the aliasing of the shadows, quite similar to what PCF method for 3D shadow maps does. This was left out because the smoothing through blur is much better looking)

5_shadows_unfiltered_red

Another thing to notice is that we store the distance from the center in the Blue component of the result. This is useful when we want to smooth out the shadows by applying Gaussian blur, with radius depending on the distance from the light. Thus we obtain harder shadows closer to the light source, and softer shadows further away.

5. Next in my implementation, I apply a horizontal blur, followed by a vertical blur. The code for this is not too special, except for the fact that the blur radius depends on the distance from the light. All shader source can be seen in the attached archive, and I will not reproduce the blur shader here. When doing the final blur step, I also add some attenuation to the light, and the result is finally the one we were looking for.

7_shadows_blured_attenuated

And this concludes the explanations about the algorithm I used for the shader-based soft 2D shadows. I hope my explanations were clear enough Smile

You can download the source code from the sample’s page, found here.

Until next time, have fun coding!

  • http://www.gaspgames.com Alejandro

    Awesome technique!

    The math behind it really goes beyond my understanding, its kind of the 2d version of dual paraboloid mapping. From the 512×512 to 2×512 (why 2?), the U coord ends having the minimum distance, and whats in the V coord and those 512 vertical pixels? Are those like a full circle divided in 512 rays?.
    -Perhaps the performance is due to render target changes, maybe you can pump it up a little and calculate the min of 8 or more pixels at once instead of two.

    Also since you are using R for horizontal and G for vertical, does it mean that you may be able to process two maps at once? One with RG and another one with BA.

    Is it for XNA 4.0? I couldn’t run the sample.

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

    I’m not familiar with paraboloid mapping, but it may be so.

    Yes, the v coord is like a quarter of a circle divided in 512 rays. and because in on 512×512 image (after distorsion) I have two opposing quarters, I reduce it to two columns. The left column corresponds to the left quarter and the right column corresponds to the right quarter.

    Yes, I also thought of min-ing more than 2 pixels in a pass, but for clarity’s sake I left it at two for the moment.

    And yes, it is quite possible to process two maps at once, because in all steps I use at most two channels.

    And yes, the sample is in XNA 4.0 beta. I had to re-check that everything is ok since the first time I wrote it (back in november), and I did it in 4.0.

  • http://www.gaspgames.com Alejandro

    Dual paraboloid mapping tries to warp the geometry into two hemispheres from the light’s point of view. In one texture map the geometry in front of the light is rendered, and in another texture map all the back geometry. Depth maps textures are used for shadows, color map textures are used for reflection.

    This is kind of the same in the way that you render the scene centered around the light’s position, and this encompasses four quarters of a full circle.
    You then do what I find the most interesting thing of this technique and parallelize those quarter circles (did I say interesting? I meant neat-awesome!).

    Thanks for clearing it up!. I was writing how two quarters can possibly end up being a full circle! Until finally got it (at least I think), both columns of the R channels contain the left and right quarters, and G the top and bottom parallelized quarters.

    Judging from this, could then be possible to do two half circles? or perhaps one column with the whole circle?

    I will definitely take a look at this and put it to work whenever I get the chance to work in a 2D game.

    Thanks for this article man.

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

    Trust me, when the idea of parallelizing the rays struck me, I was super-excited, and couldn’t stop smiling all day :)

    >>both columns of the R channels contain the left and right quarters,
    >>and G the top and bottom parallelized quarters
    That’s exactly what happens.

    If I could find a function to parallelize a half-circle, then yes, it could be possible. Same foe the whole circle. But in these cases, you loose precision for the points away from the light (because a perimeter of 2048 pixels now becomes 512 pixels) But considering that shadows are more blury at that distance, it might not be quite such a bad trade-off. And with a little math and trigonometry, I guess the function to achieve that parallelization is not THAT hard to find.

  • Pnikosis

    Man, you are awesome. I had a hard time to understand it but because I’m not a graphics programmer and I’m not familiar with effects and such.
    Anyway, it is really amazing, I’ll download XNA4.0 just to see it working and see if I can somewhat understand it better and apply this to my XNA3.1 game.

    Thanks a lot.

  • http://mort8088.com/ mort8088

    Dude, awesome!

    Took you long enough, but just as I thought it was totally worth it, I can go back to working on my game now with this sweet new effect :-)

    Thanks

    mort8088.

  • David

    Excellent sample, thanks for making this available

  • http://None calsmurf2904

    Hello Catalin,

    I’m trying to port your sample over to directx in C++.
    One problem I keep getting is that the lightarea rendertarget shows that the whole scene is rendered correctly to it (in black&white ofcourse), but the Distance rendertarget from the ShadowmapResolver remains white.

    My shadowmapresolver code can be found here:
    http://pastebin.com/T536XiZb

    The lightarea code is here:
    http://pastebin.com/c8wyMBAH

    I use your original shaders.

    I hope you can help me, I used your previous sample for my game before you released this tutorial and I was very satisfied. The only problem was that it was really cpu-expensive.

  • Pingback: MikeCann.co.uk » Blog Archive » Shader Based 2D Shadowing

  • Zms

    This is awesome!

    But how do I get global (ambient) lighting (with or without shadows)? I don’t want the rest of the “world” (background) to be pitch black.

  • Robin

    Hey thanks for the article, but cant seem to run sample, i have xna 4 so dont get it

  • Robin

    Nevermind :p i fixed it, the references were gone somehow.

  • Pingback: We have lighting!

  • http://Website Alvise

    Hi Catalin! I’m really impressed of your idea on how to resolve that problem! it’s very impressive! now i’m searching to move that on xna 3.1, and to make that program run on my netbook, to see how that run on pc with no-so-high video card.. now, i translate evrything, (on rendertarget i use surfaceFormat.color) but the sample doesn’t work, so i start to try to make every pass of your solution in a new project to solve one step for time. now, i got some result but i’ve some big trouble because i’ve to delete all the VertexShader function, cause that seems to black my output. i really want a help, because i have a lot of good idea in how to solve problem, but i miss some experience on programming shader (i’ve just read your crash guide) an other tips..

    can you give me a help?
    Alvise
    (sorry for the bad english..)

  • http://projectorgames.net DjArcas

    “But how do I get global (ambient) lighting (with or without shadows)? I don’t want the rest of the “world” (background) to be pitch black.”

    Just render a quarter-strength (64,64,64) quad over the ShadowCaster map.

  • http://indi.solubl.es Niko

    A little question, I’ve been trying tomodify the shader in order to make the center radius (the part with “full” light) wider, but I can’t find where can I modify this, has anyone an idea?

  • Pingback: Zombie Accountant Sales, Thoughts « Ben Kane

  • http://Website Andy

    Does anybody know what I would need to change to alter the perspective of the shadows? I’m trying to integrate this into an isometric-view game.

  • http://Website Robin

    “But how do I get global (ambient) lighting (with or without shadows)? I don’t want the rest of the “world” (background) to be pitch black.”

    “Just render a quarter-strength (64,64,64) quad over the ShadowCaster map.”

    I dont get it, sorry not that in to lighting but i need it in my game, can someone show this?

  • Pingback: indi.solubl.es - Update

  • Pingback: Intro and an Early Update « Technicolor Teen

  • http://deja-visite.tumblr.com Jareth

    Thanks! Great tutorial.

    I actually followed it through and adapted it for Flash and Pixel Bender.

    I don’t think it’s quite as fast as yours as I had to move the compression to 2 pixels off the GPU and into the CPU (in actionscript) because Pixel Bender can’t support those kinds of operations.

    I’ve got a demo running here: http://programmerart.tumblr.com/post/5991439557/shadow-bending

  • http://Website Rens2Sea

    Hi, i’m working on a platformer that uses the Reach profile, this tutorial however is build for the HiDef profile.
    I’ve tried to convert it but i cannot get it to work properly, mainly the blending stuff.
    Can you, or anyone else, explain how to make this work using the Reach profile?

    Thanks, Rens2Sea

  • http://www.gmlscripts.com/ xot

    Hey, this is neat! When I saw the video, I wondered if it was showing what I thought it was — and it is!

    I came up with an almost identical system a couple of years ago. I don’t use shaders, but the geometric transform to make light rays parallel is very much the same idea. I can totally relate to your sense of excitement when the idea first hit me. In my version, the entire area is transformed in one shot, not per quadrant. A more detailed explanation and Game Maker example is linked below.

    http://www.gmlscripts.com/forums/viewtopic.php?id=1657

    The problem I never got a satisfactory solution to was shadowing the shadow casters without them self-shadowing themselves. Curious to know if you have any thoughts on this issue.

  • http://quad-engine.com Darthman

    Hi there!
    I used yours technique to do something similar to shadowcasting. Thnx a lot. I write about your technique and my sample in my blog here:
    http://quadengine.blogspot.com/2011/07/2.html

    and that’s what i’ve done:
    http://quad-engine.com/public/Light.zip

  • http://RabidLion.com RabidLionGames

    Hi,

    I’ve been working on a lighting system inspired by this sample, and I thought I should mention that I’ve come up with a slightly different approach to ‘flattening’ the shadowmap down to 1-2 columns of pixels (I use 1 as I map a semi-circle at a time rather than a quarter of a circle).

    Basically, I use spritebatch with the source rectangle parameter to sample 1 row of pixels at a time from the source texture, and draw it over the 1-2 column rendertarget with a custom BlendState, using blendfunction.Min (the rendertarget needs to be cleared to white first for it to work). This gives you the same outcome as your 9 pass reduction, but in a single pass.

    Of course you might already be aware of all of that, and have avoided it for good reason!

    I’ll be publishing a tutorial on my approach at some point soon, so you can see the code then if need be.

  • http://blog.cjcat.net Allen Chou

    I have implemented a non-shader version of this effect in ActionScript 3.0 based on your approach.

    http://wonderfl.net/c/foPH

    Thanks a lot for the helpful tutorial! The effect is awesome :)

  • Catalin Zima

    That looks awesome, Allen! Thanks for the link.

  • http://www.addictivecolors.com AddictiveColors

    Awesome idea, this screen space solution looks to be quite neat for games with many shadow casting objects :). With some modifications you should be able to do some self-shadowing too.

  • Pingback: Rabid Lion Games » Blog Archive » Tumbleweed

  • Pingback: Rabid Lion Games » Blog Archive » 2D Lighting System tutorial series: Part 3 – The Algorithm, and the LightRenderer class

  • Pingback: Rabid Lion Games » Blog Archive » 2D Lighting System tutorial series: Part 6 – Blurring the LightMap: Creating soft shadows

  • http://www.punchbagentertainment.com Steve Lillis

    Hi Catalin,

    First of all; thank you! This is the only solution I have seen of such elegance made publicly available.

    I’ve implemented it and added the improvement mentioned in the comments regarding operating on two lights at once using the b and a channels. I have a question though, is there any reason that you chose to use multiple rendertargets for the minning process over rendering the slivers of texture manually to the shadowmap, still using a min process?

    As far as I can see, that should be faster, as it avoids setting the render target 9 times per light.

  • Pingback: I hate shadows! | Void Star

  • http://Website Andrew

    hi. i really wanted to thank you for the tutorial. I never thought of using a distortion map to align the rays and simply collapse them from there. I was wondering what kind of frame rate you attained from your four quadrant method?

  • http://Website Spectre

    Hi Catalin! Great tutorial, I would’ve never figured these algorithms out by myself, so thanks for that :) anyways, is there an easy way to have these lights move around with a camera? Because I tried messing with that idea, and the shadows just wouldn’t behave. Here is my camera: http://pastebin.com/6WjruD4J it uses a method globalToLocal(Vector2 pos) to localize positions on the screen, but I’ve tried putting this on the shadow resolvers and various of the other lighting properties that you showed us in the tutorial, but so far the shadows will still not be mapped properly. If you have the time, perhaps you can help me figure this out? Thanks :)

  • http://www.funhazard.com Fun Hazard

    Catalim Zima’s shadow system is simply genial.

    I loved this system so much that I wanted to make a Reach Profile compatible version, along with some improvements:

    – Many quality settings to best tune your lights.
    – Improved shadow calculation performance (rendering is about +15% faster than Part One and by tuning the lights quality settings you can go +250% faster than the original code without noticeable differences).
    – Different draw methods and effects can be assigned to each light.

    It can be downloaded here:

    http://www.funhazard.com/xna-resources.html

  • http://cabbynode.net NightCabbage

    Great work Fun Hazard :)

  • Pingback: 2D Light Mapping | Killed By A Pixel

  • Geezuz

    very nice algorithm. I have one question…how would I find out what brightness a given pixel on screen has? ie. if I have a point, or vector2, how would I find out the brightness of that position on the shadowmap?

  • Geezuz

    If anyone cares…here’s how:

    int X, Y, width, height; // initialize with values of pixel to get

    Rectangle sourceRectangle =
    new Rectangle(X, Y, width, height);

    Color[] retrievedColor = new Color[1];

    // screenLights is the shadowmap of all the assembled lightsources in one big rendertarget2d
    screenLights.GetData(
    0,
    sourceRectangle,
    retrievedColor,
    0,
    1);

  • Catalin ZZ

    Hi Geezuz,

    Yes, that’s the way to do it, but using Texture2D.GetData() is not very efficient if done too often.
    If you plan to do it only from time to time, when the user clicks something, than you can get away with it, but otherwise it gets pretty expensive pretty soon.

    Unfortunately, there’s no other way to do it.

  • Geezuz

    actually I’ll have to do it every time the player’s position has changed. I’ll just have to deal with that. I tried it yesterday in my engine and it didn’t slow it down too much – my AI routines still cost more :)

  • Geezuz

    edit: I’ll have to do it pretty much every frame, since I’ll have other animated objects – guards – with lightsources walking about, not just static lights in the level and the entire idea is to give some ‘stealth’ functionality.

  • FRex

    Is that method in any way patented or copyrighted? I’d like to adapt it to C++, opengl and glsl and I don’t know if it’s worth starting.

  • Catalin Zima

    Hi FRex,

    The code is released under the MIT License, and the technique is not patented. So you are free to use it in any way you desire.

    If you release your implementation as a tutorial somewhere, I would appreciate a link back, but I do not require it.

    Best Regards,
    Catalin

  • Pingback: 2D soft shadows using pixel shaders | Aethor

  • http://www.funhazard.com Fun Hazard

    The enhanced version of this shadow system has been updated:
    – More quality without affecting performance
    – Print portions of shadow map over texture

    It can be downloaded here:

    http://www.funhazard.com/xna-resources.html

    Thank you Zima for the original code

  • Pingback: Optimising Dark – Andrew Russell

  • Sharpnel

    Andrew Russell wrote an article about lights optimisation for his game “Dark”. Your blog appears in it.
    You should read it ^^.
    http://andrewrussell.net/2013/02/optimising-dark/