More Post-Processing Tricks: Lens Flare

I was playing Killzone 2 the other day, which reminded me of the lens flare trick they used.  Unlike most games, which use some sprites controlled by an occlusion query, they applied the effect as a post-process similar to bloom.  The upside is that it works for all bright areas and not pre-defined areas (the sun), and you don’t have to do occlusion queries or anything like that since that’s handled automatically.  Plus it’s really easy to fit it into a post-processing chain, since you can use your bloom results as the input.  The downside is that it’s pretty far from realistic…I’m not sure that most would like the end result.  This screen here shows the effect pretty clearly (it’s the orange and purple blobby areas by the left bad guy’s head, on the opposite side of the screen from the bright light source).

I haven’t seen anyone duplicate or even discuss the technique since before the game out, so I figured I’d take a crack at deciphering it myself.  After some experimenting I came up with the following basic approach:

1.  Render a bloom buffer using standard downscale + threshold + blur
2.  Flip the texture coordinates by doing float2(1, 1) – texCoord
3.  Blur both towards the center of the screen and away from it
4.  Combine additively with the bloom buffer

To fake a chromatic aberration, Killzone 2 uses a strong orange tint for areas closer to the center of the screen and a purple tint on the periphery.  Upon some further close analysis it started to look like they were doing it in two passes with a different tint and different texture coordinate scaling for each pass.  I decided to make my implementation the same,  so I could produce similar results.  This is the shader code I came up with:


const static float4 vPurple = float4(0.7f, 0.2f, 0.9f, 1.0f);
const static float4 vOrange = float4(0.7f, 0.4f, 0.2f, 1.0f);
const static float fThreshold = 0.1f;

float4 LensFlarePS (    in float2 in_vTexCoord    : TEXCOORD0,
 uniform int NumSamples,
 uniform float4 vTint,
 uniform float fTexScale,
 uniform float fBlurScale)    : COLOR0
{
 // The flare should appear on the opposite side of the screen as the
 // source of the light, so first we mirror the texture coordinate.
 // Then we normalize so we can apply a scaling factor.
 float2 vMirrorCoord = float2(1.0f, 1.0f) - in_vTexCoord;
 float2 vNormalizedCoord = vMirrorCoord * 2.0f - 1.0f;
 vNormalizedCoord *= fTexScale;

 // We'll blur towards the center of screen, and also away from it.

 float2 vTowardCenter = normalize(-vNormalizedCoord);
 float2 fBlurDist = fBlurScale * NumSamples;
 float2 vStartPoint = vNormalizedCoord + ((vTowardCenter / g_vSourceDimensions) * fBlurDist);
 float2 vStep = -(vTowardCenter / g_vSourceDimensions) * 2 * fBlurDist;

 // Do the blur and sum the samples
 float4 vSum = 0;
 float2 vSamplePos = vStartPoint;
 for (int i = 0; i < NumSamples; i++)
 {
 float2 vSampleTexCoord = vSamplePos * 0.5f + 0.5f;

 // Don't add in samples past texture border
 if (vSampleTexCoord.x >= 0 && vSampleTexCoord.x <= 1.0f
 && vSampleTexCoord.y >=0 && vSampleTexCoord.y <= 1.0f)
 {
 float4 vSample = tex2D(PointSampler0, vSampleTexCoord);
 vSum +=  max(0, vSample - fThreshold) * vTint;
 }

 vSamplePos += vStep;
 }

 return vSum / NumSamples;
}

float4 CombinePS (in float2 in_vTexCoord    : TEXCOORD0) : COLOR0
{
 float4 vColor = tex2D(PointSampler0, in_vTexCoord);
 vColor += tex2D(PointSampler1, in_vTexCoord);
 vColor += tex2D(PointSampler2, in_vTexCoord);
 return vColor;
}

technique LensFlareFirstPass
{
 pass p0
 {
 VertexShader = compile vs_3_0 PostProcessVS();
 PixelShader = compile ps_3_0 LensFlarePS(12, vOrange, 2.00f, 0.15f);

 ZEnable = false;
 ZWriteEnable = false;
 AlphaBlendEnable = false;
 AlphaTestEnable = false;
 StencilEnable = false;
 }
}

technique LensFlareSecondPass
{
 pass p0
 {
 VertexShader = compile vs_3_0 PostProcessVS();
 PixelShader = compile ps_3_0 LensFlarePS(12, vPurple, 0.5f, 0.1f);

 ZEnable = false;
 ZWriteEnable = false;
 AlphaBlendEnable = false;
 AlphaTestEnable = false;
 StencilEnable = false;
 }
}

Obviously the code is severely unoptimized, but it’s late and I’m tired.  Here’s a screen of what it looks like (ignore the obnoxious brightness and bloom, please):



About these ads

4 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s