Dynamic 2D Shadows
Update:
The XNA 4.0 version for Windows Phone 7 is available here: HardShadowsWP7.zip
This sample shows an implementation for dynamic 2D shadows in XNA. The code is based on an article from GameDev.net, written by Orangy Tang, but only implements hard-edged shadows.
As exercises to further understand the sample, you could try some of the following:
- Make some objects (ConvexHull) move around
- Add other lights, and modify their parameters
- Add player-world collisions. This shouldn’t be too hard, since you already have the ConvexHull class.
- Switch on/off the light surrounding the wizard
- Try various texture for light
- Make the player drop shadows. (Hint: switch off player light, and make a ConvexHull object that surrounds the player, and moves together with him)
The wizard character is controlled by the directional keyboard keys. My wired GamePad was not available these days
I want to credit George for the wizard sprite, and Studio Evil for the ground texture.
The sample can be downloaded here: dynamicshadows.zip
-
Hey Catalina, have a question for you if you have the time for a discussion. I downloaded and started up your sample and added a frame counter to it. With one added light to the player( just a different texture to simulate a cone light ) and the original 37 objects the framerate was running at 24 fps average. After profiling I discovered the called operations( and looking at the algorithm ) I discovered that the shadow generation algorithm was being called on each object for every light. This made sense to me as a sample so don’t think I’m knocking it in any way.
So, I fixed that with a quick and dirty SAT test against a bounding box for the hull, and the bounding circle for the light. If it passes the test, then I do the light calculations. This brough the number of shadows being cast from 259 to 61 at the starting position. Additionally I wanted to speed up the shadow generation algorithm so I moved the edge creation code into the constructor. (I also added convex hull rotation and movement into it, but that is seperate)
However, this only increased the frame rate from 22-24 up to 30-34. I know, around a 30 percent increase… but not good enough for a really robust system. It was apparent additional profiling was needed. That yielded the fact that calls were down to the shadow generation algorithm… but over 75% of the draw function was spent in draw lightmaps, and 52% of that was spent in the draw lightmaps function itself and not its callees.
It seems the culprit might be the renderstate changes in between each light. There isn’t much more I can do to opt out of shadow generation, so my optomizations have to focus on the creation of the lightmap now.
Here is where I was hoping you had an insight or a resource I have missed. Do you know of another technique for drawing the shadow areas besides using the alpha channel?
-
Yeah, those two are the bottlenecks at the moment. I tried to think of many ways to do the shadow generation in a vertex shader, but unfortunately didn’t find a solution.
One possible solution could be replacing the usage of the alpha channel for lightmaps with the usage of the stencil buffer. This might cut down on the number of renderstates that need to be set for each light, and maybe it is faster. The idea is mainly the same. Draw the shadows and while doing that, the affected pixels are marked in the stencil buffer. And then, when drawing the light, a test is made and only unmarked pixels are written.
-
Actually, a guy named Michael Anderson figured that one out and put it on his MSDN blog. Manders vs. Machine…
http://blogs.msdn.com/manders/archive/2007/03/14/shadows-in-2d.aspx
He even has the source for you to browse. I was thinking of using his shader program to speed up the shadow generation but that still leaves me with the main problem of render state switches.I’ll have a look into the stencil test method. Only thing I’m having a hard time warpping my head around is how a stencil test will handle overlapping shadows. Reason the current system works is because we clear the alpha map for each light… And I’m not currently sure how to clear the stencil buffer. If I can get it to work though I think it would eliminate the renderstate changes. I could render the shadows to a seperate render target just to fill in the stencil buffer. Then use that to render the lights.
Do you think this discussion should be conducted in the Creator’s club forum?
-
#12 written by Rautapalli 3 years ago
How could i implement a scrolling background with this?
I tried messing around with the viewMatrix, but i have no idea how it’s supposed to work.Currently my spritebatch uses Matrix.CreateTranslation(new Vector3(-Position, 0f)) that formula to have a moving background, but i have no idea how i would apply it to the shadows too.
-
#13 written by william bechard 3 years ago
Rautapalli:
i have a camera that i apply to spritebatch so that i can use world coordinates for all my objects and just move the camera for movement.
(i use World1.cam.get_transformation(GraphicsDevice)); in my spriteBatch.Begin())
//I get all my hull objects from this call
World1.GatherShadows();
DrawLightmap();graphics.GraphicsDevice.Clear(Color.White);
// TODO: This is where i do my map drawing spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState, World1.cam.get_transformation(GraphicsDevice)); /*Send the variable that has your graphic device here*/
spriteBatch.End();
//Now draw all shadows
spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.SaveState, World1.cam.get_transformation(GraphicsDevice)); /*Send the variable that has your graphic device here*/
//World1.Worldobjects are all my stored hull objects
foreach (ConvexHull hull in World1.Worldobjects)
{
hull.Draw(gameTime, World1.cam.get_transformation(GraphicsDevice));
}spriteBatch.End();
//Now draw the actual light
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState); /*Send the variable that has your graphic device here*/GraphicsDevice.RenderState.SourceBlend = Blend.Zero;
GraphicsDevice.RenderState.DestinationBlend = Blend.SourceColor;
GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;
spriteBatch.Draw(lightMap.GetTexture(), Vector2.Zero + new Vector2(400,300), null, Color.White, 0.0f, new Vector2(400.0f, 300.0f), 1.0f, 0, 0);spriteBatch.End();
base.Draw(gameTime);
//———————————————————
Now notice the changes in the class. I made no changes to the light class because it takes spritebatch in its draw and if you noticed above i already used the World1.cam.get_transformation(GraphicsDevice) in the spriteBatch.Begin(….however in the ConvexHull.cs
concentrate on 2 subs//Draw
public void Draw(GameTime gameTime, Matrix TransformWorld)
{
GraphicsDevice device = game.GraphicsDevice;
device.RenderState.CullMode = CullMode.None;
device.RenderState.AlphaBlendEnable = false;device.VertexDeclaration = vertexDecl;
drawingEffect.World = Matrix.CreateTranslation(position.X, position.Y, 0) * TransformWorld;
drawingEffect.Begin();foreach (EffectPass pass in drawingEffect.CurrentTechnique.Passes)
{
pass.Begin();
device.DrawUserIndexedPrimitives(PrimitiveType.TriangleFan, vertices, 0, vertices.Length, indices, 0, vertexCount);
pass.End();}
drawingEffect.End();
}//DrawShadow
public void DrawShadows(LightSource lightSource, Matrix TransformWorld)
{
//compute facing of each edge, using N*L
for (int i = 0; i 0)
backFacing[i] = false;
else
backFacing[i] = true;
}//find beginning and ending vertices which
//belong to the shadow
int startingIndex = 0;
int endingIndex = 0;
for (int i = 0; i startingIndex)
shadowVertexCount = endingIndex – startingIndex + 1;
else
shadowVertexCount = vertexCount + 1 – startingIndex + endingIndex;shadowVertices = new VertexPositionColor[shadowVertexCount * 2];
//create a triangle strip that has the shape of the shadow
int currentIndex = startingIndex;
int svCount = 0;
while (svCount != shadowVertexCount * 2)
{
Vector3 vertexPos = vertices[currentIndex].Position + new Vector3(position, 0);//one vertex on the hull
shadowVertices[svCount] = new VertexPositionColor();
shadowVertices[svCount].Color = Color.TransparentBlack;
shadowVertices[svCount].Position = vertexPos;//one extruded by the light direction
shadowVertices[svCount + 1] = new VertexPositionColor();
shadowVertices[svCount + 1].Color = Color.TransparentBlack;
Vector3 L2P = vertexPos – new Vector3(lightSource.Position, 0);
L2P.Normalize();
shadowVertices[svCount + 1].Position = new Vector3(lightSource.Position, 0) + L2P * 9000;svCount += 2;
currentIndex = (currentIndex + 1) % vertexCount;
}
Matrix NewTemp = Matrix.Identity;//draw the shadow geometry
game.GraphicsDevice.VertexDeclaration = vertexDecl;
drawingEffect.World = TransformWorld;
drawingEffect.Begin();
drawingEffect.CurrentTechnique.Passes[0].Begin();game.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, shadowVertices, 0, shadowVertexCount * 2 – 2);
drawingEffect.CurrentTechnique.Passes[0].End();
drawingEffect.End();}
//hope this helps.
-
#15 written by william bechard 3 years ago
hmm well i figured it out.
within the DrawLightmap method
I moved the
foreach (ConvexHull hull in World1.Worldobjects)
{
hull.Draw(gT, World1.cam.get_transformation(GraphicsDevice));
}right after the
foreach (ConvexHull ch in World1.Worldobjects)
{
//draw shadow
if (ch != null)
{ch.DrawShadows(light, World1.cam.get_transformation(GraphicsDevice));
}
}so to recap in DrawLightmap method it should look like the following:
foreach (LightSource light in lights)
{
//clear alpha to 1
ClearAlphaToOne();//draw all shadows
//write only to the alpha channel, which sets alpha to 0
GraphicsDevice.RenderState.ColorWriteChannels = ColorWriteChannels.Alpha;
GraphicsDevice.RenderState.CullMode = CullMode.None;
GraphicsDevice.RenderState.AlphaBlendEnable = true;
GraphicsDevice.RenderState.DestinationBlend = Blend.Zero;
GraphicsDevice.RenderState.SourceBlend = Blend.One;foreach (ConvexHull ch in World1.Worldobjects)
{
//draw shadow
if (ch != null)
{ch.DrawShadows(light, World1.cam.get_transformation(GraphicsDevice));
}
}
foreach (ConvexHull hull in World1.Worldobjects)
{
hull.Draw(gT, World1.cam.get_transformation(GraphicsDevice));
}GraphicsDevice.RenderState.ColorWriteChannels = ColorWriteChannels.All;
//draw the light shape
//where Alpha is 0, nothing will be written
//This is the light
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState, World1.cam.get_transformation(GraphicsDevice));
GraphicsDevice.RenderState.DestinationBlend = Blend.One;
GraphicsDevice.RenderState.SourceBlend = Blend.DestinationAlpha;
GraphicsDevice.RenderState.BlendFunction = BlendFunction.Add;
GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = true;
light.Draw(spriteBatch);
spriteBatch.End();}
Now this will have the hulls lit up (fully) when light touches it
If you would prefer them without light then make sure your color value is passed with an alpha of 0.
this is my line to create the object (hull)
objects = new ConvexHull(game2, Points, new Color(0,0,0,0), new Vector2(X, Y));
Well this seems to have worked for me anyway.
-
#16 written by Pnikosis 3 years ago
Hi Catalin, I’m stuck with something and I though maybe you can help me out (please). I’m using your dynamic 2D shadows code (can I mention you in the credits in the game by the way?), and everything’s working fine. I’ve made some optimizations and it’s looking quite good.
But now I’m trying to add some static shadows to my game, and for that I’m trying to calculate all the static shadows during the level loading period, and then just draw a texture with the static lights and shadows. The problem is that I’m not very good at graphics programming, and although I’m storing all the precalculated shadows in a big texture (with RenderTarget2D.GetTexture()) my problem is when my camera scrolls. RenderTarget2D gives me a texture with the viewport’s size, so when I move around and the camera moves, the rectangle containing the shadows stays in place :S What I need (I guess) is a bigger RenderTarget2D, with the size of the whole level, but I don’t know how to do this (gives me error when I try this. Any ideas?
With dynamic shadows I have no problem, as it calculates on each frame the lights and shadows position related to the camera position.
Thanks, sorry for the huge question.
-
Hello!
I’ve created an extremely fast (full hd ready, 100+ lights) vertex-based 2D lighting engine, optimised for both PC and the Xbox 360, running on the XNA 4.0 framework. If anyone is interested in a ready-to-use, opensource 2D engine, rather than implementing your own solution, you can check out http://Krypton.codeplex.com/
Props to OrangyTang and Catalin!
-
- Comment Feed for this Post
- Catalin Zima – XNA and HLSL blog » April Sample Online!
- XNA Game Development « XNA Scratch
- Catalin’s XNA Experiments » Working on shadows…
- Blind « AwkwardGames – Quote: "AwkwardGames ftw!"
- Jeff On Games » Posted Without (Too Much) Comment
Didn't find any related posts :(

YO! Nice sample! I have some problems with adding soft shadows and unable to rotate convex hull. would u be so kind as to help me?