Article : Sun- and Lens Flare as a Post Process
by Charles Humphrey

Now this sample has come from my current XNA toy which is the Blacksun engine I am currently writing in XNA 3.1. It uses deferred lighting, instancing and a post processing framework that I found on a great site here, all I had to do was make it engine ready.
If you want to have a look at the effect in the engine you can see it here.
This sample is just one of the elements from the post processing framework in the engine, there are none of the other goodies in this sample, just the Sun post process. This sample has also been created using the XNA 4.0 CTP, so I only had one render target to work with at the time. This means that you can't see the sun being culled behind objects in the scene as I can't create a depth map as well as the rendered scene, to be honest, I still don't have that bit of the shader 100% anyway
So, what do you get in this sample, well, the sun shader and the lens flare effect all in one post process and done in XNA 4.0. It's a pretty simple effect really, taking the suns position in the world, then just finding its screen coordinates on the render target and render the sun flare texture, then render the texture again over a set of offset texture coordinates to give the lens flare effect.
Lets take a look at the code...
Sun.fx
We have a few variables we can pass to the shader to control this effect, the names I hope are quite self explanatory, well most of them
float3 cameraPosition;
float SunSize = 1500;
float Density = 3;
float Exposure = -.1;
float Weight = .25;
float Weight2 = .125;
float Decay = .5;
float3 lightPosition;
float lightIntensity = 10.0f;
float3 Color = float3(1,1,1);
texture flare;
sampler Flare = sampler_state
{
Texture = (flare);
AddressU = CLAMP;
AddressV = CLAMP;
};
sampler BackBuffer : register(s0);
//depth
texture depthMap;
sampler depthSampler = sampler_state
{
Texture = (depthMap);
};
struct VertexShaderInput
{
float3 Position : POSITION0;
float2 texCoord : TEXCOORD0;
};
struct VertexShaderOutputToPS
{
float2 texCoord : TEXCOORD0;
};
Those that I guess are not that obvious are Density to Decay and these relate the the lens flare part of the shader and control the number of texture samples to display, density of those samples etc..
So onto the lane flare function that is used in the shader to give the lens flare effect.
float4 DoLenseFlare(float4 ScreenLightPosition,float2 texCoord,bool fwd)
{
// Calculate vector from pixel to light source in screen space.
float2 deltaTexCoord = (texCoord - ScreenLightPosition.xy);// Divide by number of samples and scale by control factor.
deltaTexCoord *= 1.0f / 3 * Density;
// Store initial sample.
float3 color = 0;
// Set up illumination decay factor.
float illuminationDecay = 1.0f;
for (int i = 0; i < 3 ; i++)
{
// Step sample location along ray.
if(fwd)
texCoord -= deltaTexCoord;
else
texCoord += deltaTexCoord;
// Retrieve sample at new location.
float3 sample = tex2D(Flare, texCoord);
// Apply sample attenuation scale/decay factors.
if(fwd)
sample *= illuminationDecay * Weight;
else
sample *= illuminationDecay * Weight2;
// Accumulate combined color.
color += sample;
// Update exponential decay factor.
illuminationDecay *= Decay;
}
return float4(color,1);
}
I can take very little credit for this bit of the code, as I got it from GPU Game 3 Chapter 13 Volumetric Light Scattering as a Post Process, not exactly using it as it was intended, but is does the job I wanted here well enough
So the pixel shader itself that pulls this all together looks like this
float4 PixelShaderFunction(VertexShaderOutputToPS input) : COLOR0
{
// Get the scene
float4 col = tex2D(BackBuffer,input.texCoord);// Find the suns position in the world and map it to the screen space.
float4 ScreenPosition = mul(lightPosition - cameraPosition,VP);
float scale = ScreenPosition.z;
ScreenPosition.xyz /= ScreenPosition.w;
ScreenPosition.x = ScreenPosition.x/2.0f+0.5f;
ScreenPosition.y = (-ScreenPosition.y/2.0f+0.5f);
// get the depth from the depth map
//float depthVal = 1- tex2D(depthSampler, input.texCoord).r;
// Are we lokoing in the direction of the sun?
if(ScreenPosition.w > 0)
{
float2 coord;
float size = SunSize / scale;
float2 center = ScreenPosition.xy;
coord = .5 - (input.texCoord - center) / size * .5;
//if(depthVal > ScreenPosition.z-.0003)
col += (pow(tex2D(Flare,coord) * float4(Color,1),2) * lightIntensity) * 2;
// Lens flare
col += ((DoLenseFlare(ScreenPosition,input.texCoord,true) + DoLenseFlare(ScreenPosition,input.texCoord,false)) * float4(Color,1) * lightIntensity) * 5;
}
return col;
}
You will see in here that I have left in the code for detecting the depth of objects in the scene for sun culling and just commented it out, when you have a full release XNA 4.0 you will be able to create and pass in the depth map, but it’s not possible in the current CTP.
So what am I doing in the pixel shader, first off we grab the current scene’s color, I then get the position of the light in screen space by multiplying its position by the camera position and then doing the homogeneous divide (ScreenPosition.xyz / ScreenPosition.w) to get the position of the sun (see the Get2DCoords method below for my inspiration for this). Then, if we are looking in the direction of the sun (ScreenPosition.w > 0) then we need to render the sun and or lens flare. I then calculate the size of the sun and find the tex coords required to get the texture from the Flare sampler and then add this with the sun color and raise it to the power of 2, this lightens the light bits and darkens the dark bits, this is then multiplied by the light intensity and the final values doubled again. Then we come to the lens flare, I call this twice: once for each direction of the flare (you will see in the clip there are two) and again tint this to the sun color and multiply by intensity.
Now please feel free to pick my dodgy math’s apart here I know I suck, but it is working and I have not thought much more abut it since writing it
Game Code
First thing we will do is set up a render target and and effect so we can apply our sun as a post process.
RenderTarget2D rt; Effect effect;
Also some way of representing the sun’s position, color intensity and size
Vector3 sunPosition = new Vector3(100, 200, -1000); Color sunColor = Color.White; float sunIntensity = 1f; float sunSunSize = 1500;
Now we have these variable lined up and ready to use we can initialize both the render target and the effect shader
rt = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
effect = Content.Load<Effect>("Shaders/PostProcessing/Sun");
effect.Parameters["flare"].SetValue(Content.Load<Texture2D>("Textures/PostProcessing/flare"));
effect.Parameters["Color"].SetValue(sunColor.ToVector3());
effect.Parameters["lightIntensity"].SetValue(sunIntensity);
effect.Parameters["SunSize"].SetValue(sunSunSize);
effect.Parameters["lightPosition"].SetValue(sunPosition);
Now we need to make sure the shader/effect is updated with the required variables. So in our update method we set the following parameters
effect.Parameters["VP"].SetValue(camera.View * camera.Projection); effect.Parameters["cameraPosition"].SetValue(camera.Position); //effect.Parameters["depthMap"].SetValue(depthBuffer);
And now for the render, keep in mind that this is all done just for this sample, you will want to encapsulate all this in its own class deriving from DrawableGameComponent or in a post processing framework as shown in the link above.
GraphicsDevice.SetRenderTargets(rt);
GraphicsDevice.Clear(<Color.Black);
base.Draw(gameTime);
// Post Processing.
GraphicsDevice.SetRenderTargets(null);
GraphicsDevice.Textures[0] = rt;
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
effect.Techniques[0].Passes[0].Apply();
spriteBatch.Draw(rt, new Rectangle(0, 0, rt.Width, rt.Height), Color.White);
spriteBatch.End();
So, we setup the render target, allow the scene to be drawn (nothing gets drawn in this sample), then we resolve the render target and then apply our effect to the newly captured render target.
As mentioned earlier I have a method I use to get screen coordinates from a 3D screen position, this method I am 99% sure I got (or at least the nuts and bolts of it) from Rocket Commander which was written by Benjamin Nitsche
public Vector2 Get2DCoords(Vector3 myPosition, Base3DCamera Camera)
{
Matrix ViewProjectionMatrix = Camera.View * Camera.Projection;
Vector4 result4 = Vector4.Transform(myPosition, ViewProjectionMatrix);
if(result4.W <= 0)
return new Vector2(Camera.Viewport.Width, 0);
Vector3 result = new Vector3(result4.X / result4.W, result4.Y / result4.W, result4.Z / result4.W);
Vector2 retVal = new Vector2((int)Math.Round(+result.X * (Camera.Viewport.Width / 2)) + (Camera.Viewport.Width / 2), (int)Math.Round(-result.Y * (Camera.Viewport.Height / 2)) + (Camera.Viewport.Height / 2));
return retVal;
}
As you can see it transforms the position by the ViewProjection matrix to then get the screen position if the result4.W >= 0.
Download the sample project here
Well that’s abut it, and this is my first post on Sgt Conker, hope it’s not my last one
Hope you find this useful, if not in it’s own right, then I hope it gives you some ideas for your own shaders…..
May 15th, 2010 - 03:08
Sup! So how do you do this in XNA 3.1? I’m having trouble running it.
Your line effect.Techniques[0].Passes[0].Apply(); I changed it to effect.Techniques[0].Passes[0].Begin(); and I get an unexpected error…
Can you help?
Thanks!
May 15th, 2010 - 18:34
Why did you do that?
I am guessing you are trying to use the technique in a XNA 3.1 project? If so then what is the error you are getting?
If not, then put it back to Apply…..
May 15th, 2010 - 18:37
Sorry, just got back and read your comment and missed the bit where you state you are using it in 3.1, what is the error you are getting?
May 16th, 2010 - 02:30
Hi there! Thanks for answering…
I cannot use Apply simply because ‘Apply’ doesn’t exist in 3.1. There is a ‘Begin’, although when that is used, I’m getting the message: “An unexpected error has occurred.” with no further information. This is the line that gives this error:
effect.Techniques[0].Passes[0].Apply();
Any ideas?
There were also quite a few additional lines that required converting from 4.0 to 3.1.
Thanks!
May 16th, 2010 - 06:58
Yes, there are a few changes from 3.1 to 4.0….
First of all, why are you converting to 3.1?
As you say there are a fair few changes to have this working in 3.1, there is no point me trying to guess where you may have gone wrong, can you post the draw call you have written/changed and I can have a look and see if I can spot what’s going wrong.
May 17th, 2010 - 00:13
Hi, how are you? Thank you for replying!
I am converting to 3.1 because I am working with XNA 3.1… Isn’t 4.0 PTR and for Windows Phone only? I’m thinking of using 4.0 when it is final.
Well, here are the changes:
—————————–
in void LoadContent
rt = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
->
rt = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 1, SurfaceFormat.Color);
—————————–
in void Draw(…)
GraphicsDevice.SetRenderTargets(rt);
to ——————>>>>>>>>>>>>>>>
GraphicsDevice.SetRenderTarget(0, rt);
—————————–
// Post Processing.
GraphicsDevice.SetRenderTargets(null);
GraphicsDevice.Textures[0] = rt;
to ——————>>>>>>>>>>>>>>>
// Post Processing.
GraphicsDevice.SetRenderTarget(0, null);
GraphicsDevice.Textures[0] = rt.GetTexture();
—————————–
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
effect.Techniques[0].Passes[0].Apply();
spriteBatch.Draw(rt, new Rectangle(0, 0, rt.Width, rt.Height), Color.White);
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
to ——————>>>>>>>>>>>>>>>
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
effect.Techniques[0].Passes[0].Begin();
spriteBatch.Draw(rt.GetTexture(), new Rectangle(0, 0, rt.Width, rt.Height), Color.White);
spriteBatch.End();
spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
—————————–
Those are the changes.
Again, the error is “An unexpected error has occurred.” on line: effect.Techniques[0].Passes[0].Begin();
Thank you!
May 17th, 2010 - 15:45
4.0 is in CTP but you can write code for both WP7 and PC, but at the moment not the 360. You will once we have the full release.
The only thing I can see in your code snippets is that you are not calling an End() on the effect, so you should have a effect.Techniques[0].Passes[0].End(); too.
Let me know if you are still having issues and you can send me your project and Ill have a better look at your code
May 17th, 2010 - 16:30
Hi, how are you?
You’re right, I’m missing the .End() statement. However, the code doesn’t reach that statement because it crashes as soon as it reaches .Begin().
How can I send you my project?
Thanks!
May 17th, 2010 - 16:36
PM me on my blog and we can sort something out.
http://xna-uk.net/blogs/randomchaos/
http://xna-uk.net/members/Nemo-Krad.aspx