XNA Tutorials, Samples and Thoughts
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.
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.
![]()
![]()
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.
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”.
![]()
In the end, drawing the items in the scene leads to our desired result.
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. 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. 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.
![]()
![]()
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).
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.
![]()
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.
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)
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.
And this concludes the explanations about the algorithm I used for the shader-based soft 2D shadows. I hope my explanations were clear enough ![]()
You can download the source code from the sample’s page, found here.
Until next time, have fun coding!
| Print article | This entry was posted by Catalin Zima on July 11, 2010 at 10:36 pm, and is filed under 2D, sample, XNA. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |


about 1 year ago
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.
about 1 year ago
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.
about 1 year ago
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.
about 1 year ago
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.
about 1 year ago
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.
about 1 year ago
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.
about 1 year ago
Excellent sample, thanks for making this available
about 1 year ago
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.
about 1 year ago
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.
about 1 year ago
Hey thanks for the article, but cant seem to run sample, i have xna 4 so dont get it
about 1 year ago
Nevermind :p i fixed it, the references were gone somehow.
about 1 year ago
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..)
about 1 year ago
“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.
about 1 year ago
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?
about 1 year ago
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.
about 1 year ago
“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?
about 8 months ago
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
about 8 months ago
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
about 7 months ago
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.
about 6 months ago
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
about 5 months ago
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.
about 1 month ago
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
about 1 month ago
That looks awesome, Allen! Thanks for the link.
about 3 days ago
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.