Catalin's XNA Blog
XNA Tutorials, Samples and Thoughts
XNA Tutorials, Samples and Thoughts
Sep 20th
UberGeekGames just provided me with an version of the Pie Drawing sample, updated to XNA 4.0
You can find it on the sample’s page, or download it directly from here.
Enjoy!
Jun 14th
What is your preferred way of doing animations in 2D?
Most of you will probably answer “spritesheets”, and you’d be right. Creating animations using spritesheets is quick, easy, there are helpful resources on the net and if you work with an external artist, he only needs to give you the individual frames of the animation (which also makes his life easy).
You would want to have a framerate of at least 12 for your animations, and you soon end up with something like this:
And with minimal effort, you can write code that loads this and displays it as an animation [SpriteSheetAnimation.zip]
All is well in the world, until you realize your game needs LOTS of animations and the memory consumption goes way up, together with the time required to load all the data. Also, to limit the size, you need to limit yourself to a low FPS for the animation (like 12), which also means the animation doesn’t look as smooth as you’d like. This is where skeletal animation comes in.
When using skeletal animation, the animation is composed of several bones which are connected to one another. Affecting a bone also affects all of its children. By composing different transformations on each bone, you obtain different poses for the skeleton.
Now, if you define keyframes with certain transformations for a point in time for each of the bones in the skeleton, you can interpolate between the keyframes to obtain a smooth transition and thus animate the skeleton.
In the attached code, I used a class named Transformation, which contains data about 2D transformations, like translation, rotation and scale. Then, a Keyframe is defined by a frame number and one such Transformation. A collection of Keyframes defines a KeyframeAnimation. Finally, a SkeletonAnimation is a collection of KeyframeAnimations, one for each bone in the skeleton.
Separately, I use a Skeleton, which keeps a list of Joints that define the hierarchy of bones in the skeleton. Each bone is then assigned a certain texture, like the ones below:
Each of the parts are individually animated relative to their parent bone, and thus the animation is obtained. In the sample, the animation is stored in two XML files that are loaded directly into a Skeleton (see machine_skeleton.xml ) and a SkeletonAnimation (see machine_animation.xml ) using the ContentPipeline at runtime. Here’s a video of the sample using skeleton animation:
As you can see, it runs much smoother than the initial frame-based animation. Also, looking at the resources used, the amount of texture data loaded into memory is significantly reduced (from 5.12 Mb to only 73Kb).
A word on how the XML file was generated. There are multiple options here.
A word of warning: while skeletal animation helps solve some problems with memory, loading times, smoothness, it is not a silver bullet. The amount of detail that can go into a normal sprite-sheet animation far surpasses what can be done with a skeletal animation. In skeletal animations you are limited to certain transformations on each ‘piece’ of the animation: translate, rotate, scale, flip. Meanwhile, frame-based animation allows the artist to add any kind of effect he likes.
I hope you enjoyed this sample. Feel free to leave any feedback and questions below.
To get the code, either download it directly (SkeletalAnimation.zip) or go to the sample’s page.
Until next time,
Happy coding!
Jun 5th
Hi all,
I’ve updated the license for all code downloadable from this site. The licensing terms can be read here.
In short, the code is licensed under MIT, so you can use it freely (unless otherwise stated specifically).
However, I need to protect myself and my collaborators, so all content (images, sounds, etc) is not licensed, so you may not distribute it or use it in other circumstances than to be able to build and run the code as provided here for educational purposes.
These terms should satisfy most visitors on this site
Mar 13th
The first week of March I was together with a few other XNA MVPs at the Microsoft MVP Summit 2011 in Seattle.
I’d love to be able to tell you stuff we’ve learned there, or as Andy would also wish, to be able to tell you news about current issues you all know with XBLIG and the Creator’s Club AppHub site and forums. All guys present there were careful to repeatedly remind the team of the issues (especially George).
Of course, bound by our NDA’s we can’t share stuff we found out, but maybe we can tell you some things that might put you at ease.
Yes, Microsoft is not always moving as fast as we’d wish; Yes, there are pressing issues with the site; Yes, on some points they are behind the competition at this point… but I feel that YES, these (XNA, WP7, XBLIG) are awesome platforms to be on, only getting stronger, and having a great future.
On a personal note, this was a good summit. I had a great time with the other MVPs and met some great people in Microsoft, who I want to thank for their time and effort.
Also, I got a signed copy (thanks George and Chris) of this:
It’s a fantastic book, the writing style is fun, which makes it a real pleasure to read. And on the technical side, being written by these two guys, it’s probably the best you can get on the subject. The single complaint is that the tattoo didn’t make it on the cover.
Feb 27th
Well, this caught me off-guard…
When we submitted the game last Sunday, I was hoping the game would get through certification sooner, but apparently it decided to come out just as I was starting my long trip towards Seattle. I’m proud to say that Chickens Can Dream just went live on the Windows Phone 7 Marketplace on Friday.
Go ahead and download it (it’s free) if you haven’t already and let me know what you think
While Chickens Can Dream is just a milestone in the wonderful adventure that is the Chickens Can’t Fly project, it’s also a great achievement for me. While this is certainly not my first game, or not even the first game I’ve worked on that got published, I can certainly say it is the game I’ve put most of myself into, am most proud of and most excited about sharing with you (all 6 of you that still read my blog
).
It’s definitely not perfect yet, and we’re working on the next update for it as we speak (at least my team is, I’m on a plane flying over Canada), but our goal with Chickens Can Dream was to get it out there and get feedback on it.
Besides letting you know about the game and giving you the download link, I also wanted to take this opportunity to congratulate and thank my team and people that made this possible, so here goes:
Thank you all!
Download the game now because we want to know how to make Chickens Can’t Fly better!
Jan 26th
I posted a new tutorial recently about applying deformations as a post-processing… process using only stuff available in the Reach profile, without any shaders. This configuration is useful for Windows Phone 7.
Go ahead and read the whole article here.
Dec 28th
Roy Triesscheijn just posted an updated version of the code he obtained after following my Deferred Rendering tutorial.
You can read all about it on Roy’s blog, here.
So we now have a working 4.0 version of the tutorial.
Thanks, Roy!
Dec 26th
The company I work at, where I’m leading the efforts of the Windows Phone 7 department, has just launched our new site, with our identity for all our current and future mobile games and apps (and possibly XBLIG games also).
I’m proud to point you to the AmusedSloth.com site, which is now online. Here you can find information about our games and apps.
The first game that we launched is Rebellion Rise, a space shooter where you get to fight giant motherships. Watch the launch trailer below.
You can try the game on the Windows Phone Marketplace right now. Really… like… right now
( Thanks ) Let me know in the comments what you think about it and if you have any technical questions
I don’t want to ‘promise’ anything, but I plan to release a small sample showing off how we pulled the deformations and shockwave effects without using shaders.
I will also take this opportunity to announce what our next game will be. I can’t wait to release more information about it but right now it’s only going to be some screenshots ( gotta follow the marketing plan ). So, our next game is called…. Chickens Can’t Fly, and you can see two screenshots of it below (and a few more on the site)
You’ll be hearing more about it in January. That’s a promise!
Now you know what I’ve been doing in the last 6 months! I hope things go well, as WP7 is a great platform and having XNA to use as an API is absolutely terrific.
As I said above, comments and suggestion are welcome. Thanks!
P.S. I know there are some issues with the site, and our guys are working hard on fixing them early in January.
Jul 11th
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!