Sun glare/screen dirt shader
by Felix Willebrand Westin · 07/13/2014 (8:16 am) · 23 comments
Since I got this working like I wanted, here's the promised how-to!

This shader is a two-component effect that adds screen dirt/bokeh and a sun glare/halo to the scene.
The bokeh appears around the sun and at the opposite end of the screen while the glare/halo appears only at the opposite end.
To get this shader working however, we need to tweak and add some source code, so follow along and we'll get this puppy up and running.
(Word of warning: this implementation currently only works with scattersky objects, however the code could be changed to support regular sun objects as well, more on that later.
The scatter sky object in the scene also has to be named "Sunlight" for this to work)
To get this running we're going to add a new shader constant that tells us how visible the sun currently is, which we are going to get from the way scatter sky occludes lens flares.
There is unfortunately a bug in scatter sky however that prevents flares from being occluded properly (instead relying on object's collision meshes to do so), so we'll have to fix that first (slightly hackishly).
In source/environment/scatterSky.cpp, line 657, add:
We will then add a function to get the sun's visibility from the scatter sky's lens flare object.
In source/environment/scatterSky.h, line 112, add:
Now let's add that function and class variable to the light flare data class:
In source/T3D/lightFlareData.h, line 96, add:
In source/T3D/lightflareData.cpp, line 125, add:
Next we are going to set up the new shader constant.
In source/postFx/postEffect.h, line 139, add:
In source/postFx/postEffect.cpp, line 47, add:
^ This is the part where the code could be changed to support regular sun objects as well. One would have to check what type of sun is active and change the code accordingly.
We've now made the necessary code changes, so compile your code and let's take a look at the shader/script part.
First off, you'll need these two textures (although you can replace them with your own of course):
screenBokeh, screenDirt
Save these as screenBokeh.dds and screenDirt.dds in core/scripts/client/postFx/textures/
screenBokeh is a tiling bokeh texture while screenDirt is the glare texture that is just mapped to the entire screen:

Next, here is the .cs file for the shader itself. Save this as core/scripts/client/postFx/sunBokeh.cs:
Finally, here's the HLSL file. Save this as shaders/common/postFx/sunBokeh.hlsl:
You should now have the effect up and running! To tweak it, use these variables:
If anyone could give implementing this a try to see if I missed anything, that'd be dandy! Be sure to post how it works out for you either way!

This shader is a two-component effect that adds screen dirt/bokeh and a sun glare/halo to the scene.
The bokeh appears around the sun and at the opposite end of the screen while the glare/halo appears only at the opposite end.
To get this shader working however, we need to tweak and add some source code, so follow along and we'll get this puppy up and running.
(Word of warning: this implementation currently only works with scattersky objects, however the code could be changed to support regular sun objects as well, more on that later.
The scatter sky object in the scene also has to be named "Sunlight" for this to work)
To get this running we're going to add a new shader constant that tells us how visible the sun currently is, which we are going to get from the way scatter sky occludes lens flares.
There is unfortunately a bug in scatter sky however that prevents flares from being occluded properly (instead relying on object's collision meshes to do so), so we'll have to fix that first (slightly hackishly).
In source/environment/scatterSky.cpp, line 657, add:
// Screen flare occlusion fix
F32 screenRadius = GFX->getViewport().extent.y * mFlareScale * 0.01f;
Point3F lightWorldPos = state->getCameraPosition() - state->getFarPlane() * mLight->getDirection() * 0.9f;
F32 dist = ( lightWorldPos - state->getCameraPosition() ).len();
mFlareState.worldRadius = screenRadius * dist / state->getWorldToScreenScale().y;We will then add a function to get the sun's visibility from the scatter sky's lens flare object.
In source/environment/scatterSky.h, line 112, add:
///
F32 getSunVisibility() const { return mFlareData->getVisibility(); } Now let's add that function and class variable to the light flare data class:
In source/T3D/lightFlareData.h, line 96, add:
///
F32 getVisibility() const { return mVisibility; }line 118, add:F32 mVisibility;
In source/T3D/lightflareData.cpp, line 125, add:
mVisibility( 1.0f ),line 449, add:
// Sun visibility, assume 0 at start of frame mVisibility = 0;line 525, add:
mVisibility = lightSourceBrightnessScale *
fadeInOutScale *
occlusionFade;Next we are going to set up the new shader constant.
In source/postFx/postEffect.h, line 139, add:
GFXShaderConstHandle *mSunVisibilitySC;
In source/postFx/postEffect.cpp, line 47, add:
#include "environment/scatterSky.h"line 277, add:
mSunVisibilitySC( NULL ),line 571, add:
mSunVisibilitySC = mShader->getShaderConstHandle( "$sunVisibility" );line 793, add:
if( mSunVisibilitySC->isValid() )
{
ScatterSky* pSky = dynamic_cast<ScatterSky*>(Sim::findObject("Sunlight"));
if(pSky)
mShaderConsts->set( mSunVisibilitySC, pSky->getSunVisibility() );
else
mShaderConsts->set( mSunVisibilitySC, 0.0f );
}^ This is the part where the code could be changed to support regular sun objects as well. One would have to check what type of sun is active and change the code accordingly.
We've now made the necessary code changes, so compile your code and let's take a look at the shader/script part.
First off, you'll need these two textures (although you can replace them with your own of course):
screenBokeh, screenDirt
Save these as screenBokeh.dds and screenDirt.dds in core/scripts/client/postFx/textures/
screenBokeh is a tiling bokeh texture while screenDirt is the glare texture that is just mapped to the entire screen:

Next, here is the .cs file for the shader itself. Save this as core/scripts/client/postFx/sunBokeh.cs:
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
///
$SunBokehFx::enabled = true;
$SunBokehFx::sunAmount = 0.4;
$SunBokehFx::edgeAmount = 0.4;
$SunBokehFx::haloAmount = 0.8;
$SunBokehFx::sunSize = 0.125;
$SunBokehFx::bokehSize = 500;
$SunBokehFx::debug = 0;
singleton GFXStateBlockData( PFX_DefaultSunBokehStateBlock )
{
zDefined = true;
zEnable = false;
zWriteEnable = false;
samplersDefined = true;
samplerStates[0] = SamplerClampPoint;
};
singleton ShaderData( PFX_SunBokehShader )
{
DXVertexShaderFile = "shaders/common/postFx/postFxV.hlsl";
DXPixelShaderFile = "shaders/common/postFx/sunBokeh.hlsl";
samplerNames[1] = "$bokehTex";
samplerNames[2] = "$dirtTex";
pixVersion = 3.0;
};
singleton PostEffect( SunBokehPostFx )
{
renderTime = "PFXAfterDiffuse";
renderPriority = 0.3;
isEnabled = true;
allowReflectPass = false;
shader = PFX_SunBokehShader;
stateBlock = PFX_DefaultSunBokehStateBlock;
texture[0] = "$backBuffer";
texture[1] = "textures/screenBokeh";
texture[2] = "textures/screenDirt";
target = "$backBuffer";
};
function SunBokehPostFx::setShaderConsts( %this )
{
%this.setShaderConst("$sunAmount", $SunBokehFx::sunAmount);
%this.setShaderConst("$edgeAmount", $SunBokehFx::edgeAmount);
%this.setShaderConst("$haloAmount", $SunBokehFx::haloAmount);
%this.setShaderConst("$sunSize", $SunBokehFx::sunSize);
%this.setShaderConst("$bokehSize", $SunBokehFx::bokehSize);
%this.setShaderConst("$debug", $SunBokehFx::debug);
%this.setShaderConst( "$sizeX",getWord($pref::Video::mode, 0) );
%this.setShaderConst( "$sizeY",getWord($pref::Video::mode, 1) );
}Finally, here's the HLSL file. Save this as shaders/common/postFx/sunBokeh.hlsl:
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "shadergen:/autogenConditioners.h"
#include "./postFx.hlsl"
#include "./../torque.hlsl"
uniform sampler2D backBuffer : register( S0 ); // Screen buffer
uniform sampler2D bokehTex : register( S1 ); // Screen bokeh texture
uniform sampler2D dirtTex : register( S2 ); // Screen dirt texture
uniform float sizeX; // %this.setShaderConst("$sizeX",getWord($pref::Video::mode, 0));
uniform float sizeY; // %this.setShaderConst("$sizeY",getWord($pref::Video::mode, 1));
uniform float3 camForward;
uniform float3 lightDirection;
uniform float2 screenSunPos;
uniform float sunVisibility;
static const float2 fViewportDimensions = float2(sizeX,sizeY); // float2(screenWidth, screenHeight)
static const float2 fInverseViewportDimensions = float2(1/sizeX,1/sizeY); // reciprocal of fViewportDimensions
uniform float sunAmount; // Amount of bokeh around the sun
uniform float edgeAmount; // Amount of bokeh around the screen edges
uniform float haloAmount; // Amount of dirt/halo
uniform float sunSize; // Sun circle size
uniform float bokehSize; // Bokeh texture tiling value
uniform bool debug;
float4 main( PFXVertToPix IN ) : COLOR0
{
// UV coords
float2 texCoord = IN.uv0;
// Screen color
float4 color = tex2D( backBuffer, texCoord );
// Screen aspect ratio
float aspect = sizeY / sizeX;
// Screen bokeh texture (tiling)
float3 screenBokeh = ( tex2D(bokehTex, texCoord * fViewportDimensions / bokehSize).xyz + 1.e-6f ) * 0.5;
// Screen dirt/halo texture
float3 screenDirt = tex2D(dirtTex, texCoord);
// Calculate current sun strength on screen
float sunStrength = saturate( dot( -lightDirection, camForward ) );
float circle = 0.0f;
circle = 1 - distance(texCoord * float2(1.0f, aspect), float2(screenSunPos.x, screenSunPos.y * aspect) );
circle = smoothstep(saturate(1-sunSize),1,circle) * sunAmount;
float3 circle2 = 0.0f;
circle2 = distance(texCoord , 0.5 );
circle2 = smoothstep(0.2,1,circle2);
float edgeMask;
edgeMask = lerp(0,1-screenSunPos.y,1-texCoord.y) + lerp(0,screenSunPos.y,texCoord.y) + lerp(0,1-screenSunPos.x,1-texCoord.x) + lerp(0,screenSunPos.x,texCoord.x);
edgeMask = (1-edgeMask) * circle2;
edgeMask = saturate( edgeMask * 10);
screenDirt *= edgeMask * sunStrength * haloAmount;
edgeMask *= edgeAmount;
circle = (circle + edgeMask) * sunStrength;
screenBokeh *= circle;
if( !debug )
return color + float4(screenBokeh + screenDirt,1) * 1 * sunVisibility;
else
return float4(screenBokeh + screenDirt,1) * 1.5;
}You should now have the effect up and running! To tweak it, use these variables:
$SunBokehFx::sunAmount = 0.4;Controls the amount of bokeh around the sun.
$SunBokehFx::edgeAmount = 0.4;Controls the amount of bokeh around the edge of the screen.
$SunBokehFx::haloAmount = 0.8;Controls the amount of the glare/halo.
$SunBokehFx::sunSize = 0.125;Controls the size of the sun mask.
$SunBokehFx::bokehSize = 500;Controls how much the bokeh texture tiles (eg. bokeh size).
$SunBokehFx::debug = 0;Set this to 1 if you'd like to see only the effect mask. Useful for tweaking.
If anyone could give implementing this a try to see if I missed anything, that'd be dandy! Be sure to post how it works out for you either way!
About the author
Hobbyist turned game dev. Worked at DICE for a short while, now working on personal stuff and Dota 2 things.
Recent Blogs
#2
I've updated the resource with some better code since I accidentally left some unused stuff in there, also fixed the HTML issues.
07/13/2014 (8:33 am)
Yeah, I noticed that certain characters get scrambled when you edit a post.I've updated the resource with some better code since I accidentally left some unused stuff in there, also fixed the HTML issues.
#3
07/13/2014 (9:00 am)
Thanks for share that! Awesome resource.
#4
More importantly, though - thanks for sharing! This is a very cool effect.
07/13/2014 (9:29 am)
That's very nice! I was just thinking, though - one could probably search the global gClientContainer (or maybe the gServerContainer) for any ScatterSky object instead of hardcoding the name. There should only be one in there....More importantly, though - thanks for sharing! This is a very cool effect.
#5
07/13/2014 (1:38 pm)
Amazing! Felix your work is impressive on all fronts, keep it up!
#6
07/13/2014 (2:35 pm)
Thanks for sharing this :)
#8
I placed my halo effect in the sunflare sheet, so it will appear at the same time as the flare; that's in the direction of the sun. To stop the element from scaling with position of the sun object on your screen, I made a tiny hack in lightFlareData.cpp, where I changed
If you define the elementScale[x] in your LightFlareData with a greater value then 16 (around screen size in my case) it stops scaling. It's surely not a great way to do it, it would be much better to add a boolean for this of course :)

Just an idea. Anyway, again muchas gracias!
07/14/2014 (6:28 am)
Mentioning your halo effect, I saw that you placed it as a fixed element that gets un-proportional scaled, depending on your canvas. If you'd like to add a halo around the sun; a round shape with a fixed size might be preferable.I placed my halo effect in the sunflare sheet, so it will appear at the same time as the flare; that's in the direction of the sun. To stop the element from scaling with position of the sun object on your screen, I made a tiny hack in lightFlareData.cpp, where I changed
size *= mElementScale[i] * mScale * lightSourceIntensityScale;into
if ( mElementScale[i] < 16.0f )
{
size *= mElementScale[i] * mScale * lightSourceIntensityScale;
}
else
{
size *= mElementScale[i] * mScale;
}If you define the elementScale[x] in your LightFlareData with a greater value then 16 (around screen size in my case) it stops scaling. It's surely not a great way to do it, it would be much better to add a boolean for this of course :)

Just an idea. Anyway, again muchas gracias!
#9
Anyways thanks a lot for this! If the effect is not too game-specific, it might be considered to add this to the main repo? (I know, I know keep the repo light, but new users likes that the engine looks great out-of-the-box and they don't have to set it up to look great :P
07/14/2014 (8:47 am)
Has anyone tested the performance of this?Anyways thanks a lot for this! If the effect is not too game-specific, it might be considered to add this to the main repo? (I know, I know keep the repo light, but new users likes that the engine looks great out-of-the-box and they don't have to set it up to look great :P
#10
07/14/2014 (11:12 am)
Lukas, if you do I'd suggest you make it a $pref:: attached to the shader quality config in core/scritps/client/default.cs
#11
07/15/2014 (1:45 pm)
Thank you Felix!
#12
Of course, I have upgraded my vid card from a 550ti to a 750ti so that might help a little. Either way, if I compare stats on old builds, there really is no real impact on performance and it makes the environment feel more cinematic. By the way, Nils little tweek adds a bit to the overall feel of this.
Ron
07/16/2014 (9:03 am)
Performance hit is basically nothing (at least with a DirectX9 refactor build). I am pulling between 60 and 80+ FPS with around 2.1 million polys per frame at true 1080p. Of course, I have upgraded my vid card from a 550ti to a 750ti so that might help a little. Either way, if I compare stats on old builds, there really is no real impact on performance and it makes the environment feel more cinematic. By the way, Nils little tweek adds a bit to the overall feel of this.
Ron
#13
07/16/2014 (6:05 pm)
Very cool, this is the best lens flare effect I've seen yet!
#14
07/17/2014 (8:45 am)
Just seen this, I can't wait to try it out!!
#15
07/17/2014 (11:29 am)
having some issues getting this to work - when I add the resource to a stock build, I get this after the level is loaded and my player is spawned:Mapping string: /c2Welcome to the Torque demo app %1. to index: 14 Mapping string: /cp/c11Visitor/co to index: 15 *** Initial Control Object Activating DirectInput... *** Control Object Changed Mapping string: Orange to index: 16 C:/blah/My Projects/bokehTBE/game/shaders/common/postFx/sunBokeh.hlsl(60,9): warning X3206: implicit truncation of vector type C:/blah/My Projects/bokehTBE/game/shaders/common/postFx/sunBokeh.hlsl(73,11): warning X3206: implicit truncation of vector type
#16
07/17/2014 (11:38 am)
Hmm, those are just warnings, the shader should still work fine. What is The visual ouput? Does it not work?
#18
When I add this line to lightFlareData.h at line 96 it won't compile because it's protected.
if I move it up a few lines like this:
it'll compile but anytime the engine calls the bokeh shader it crashes...
07/18/2014 (8:57 am)
@Felix:When I add this line to lightFlareData.h at line 96 it won't compile because it's protected.
///
F32 getVisibility() const { return mVisibility; }if I move it up a few lines like this:
/// Submits render instances for corona and flare effects.
void prepRender( SceneRenderState *state, LightFlareState *flareState );
///
F32 getVisibility() const { return mVisibility; }it'll compile but anytime the engine calls the bokeh shader it crashes...
#19
Do you have a scattersky object in your scene? Does it have a lensflare datablock and scale set?
07/18/2014 (9:26 am)
It should not be under protected, no.Do you have a scattersky object in your scene? Does it have a lensflare datablock and scale set?
#20
That was it! The default Empty Terrain mission's scattersky is setup for SunFlareExample1, which doesn't exist...
07/19/2014 (8:11 am)
@Feliz:That was it! The default Empty Terrain mission's scattersky is setup for SunFlareExample1, which doesn't exist...

Torque Owner Daniel Buckmaster
T3D Steering Committee
If only GG would get around to fixing the HTML escaping bug in resources...