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.

Felix Willebrand Westin
There's a problem with the code currently where not having the flare set up correctly it'll crash. Going to take a look this weekend and hopefully fix the code hack/add support for regular sun object as well.