Code Snippet : Fog Shader


In this code snippet we are going to show some simple ways to create a fog like effect in 3D games.
The fog effect is a post process (operates on a full screen quad) that recieves the framebuffer texture (the one with the game rendered content) and a depth texture as main parameters.
The depth texture (used normally in deferred shading) contains the post projected Z/W coordinate of all objects (the Z buffer content actually =P).


To generate it, you can use the following shader in all objects of your scene (prefer rendering it to a single 32 bits texture for better precision)

float4x4 WVP;

struct VertexToPixel
{
	half4 Position		: POSITION;
	half2 ScreenPos			: TEXCOORD0;
};
struct PixelToFrame
{
	half4 Color 			: COLOR0;
};

VertexToPixel MyVertexShader(half4 inPos: POSITION0)
{
	VertexToPixel Output = (VertexToPixel)0;
	Output.Position = mul(inPos, WVP);
	Output.ScreenPos = Output.Position .zw;
	return Output;	

}

PixelToFrame MyPixelShader(VertexToPixel PSIn) : COLOR0
{
	PixelToFrame Output = (PixelToFrame)0;
	Output.Color = PSIn.ScreenPos.x/PSIn.ScreenPos.y;
	return Output;
}

technique Depth
{
	pass Pass0
    {
    	VertexShader = compile vs_2_0 MyVertexShader();
        PixelShader  = compile ps_2_0 MyPixelShader();
    }
}

It just output the z/w of each rendered “pixel”. The post process shader that creates the fog effect is:

float2 halfPixel;
float dz;
float4x4 InvertViewProjection;
float3 cameraPosition;
float far;
float near;
float3 fcolor = {0.5, 0.5, 0.5};

texture cena;
sampler cenaSampler = sampler_state
{
   Texture = ;
   AddressU  = Clamp;
   AddressV  = Clamp;
};

texture depth;
sampler depthSampler = sampler_state
{
   Texture = ;
   MinFilter = POINT;
   MagFilter = POINT;
   MipFilter = POINT;
   AddressU  = Clamp;
   AddressV  = Clamp;
};
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};
VertexShaderOutput VShader( float4 Pos: POSITION, float2 Tex : TEXCOORD)
{
	VertexShaderOutput output;
	Pos.x =  Pos.x - 2*halfPixel.x;
	Pos.y =  Pos.y + 2*halfPixel.y;
    output.Position = float4(Pos);
    output.TexCoord = Tex;
    return output;
}

float4 Pshader(VertexShaderOutput input) : COLOR
{
    float depthVal = tex2D(depthSampler,input.TexCoord).r;
    float3 cen = tex2D(cenaSampler ,input.TexCoord );

    //compute screen-space position
    float4 position;
    position.x = input.TexCoord.x * 2.0f - 1.0f;
    position.y = -(input.TexCoord.x * 2.0f - 1.0f);
    position.z = depthVal;
    position.w = 1.0f;
    //transform to world space
    position = mul(position, InvertViewProjection);
    position /= position.w;

	float d = length(position - cameraPosition);
	float l = saturate((d-near)/(far-near));
    return float4(lerp(cen,fcolor, l),1);
}

float4 PixelShaderExponencial(VertexShaderOutput input) : COLOR
{
    float depthVal = tex2D(depthSampler,input.TexCoord).r;
    float3 cen = tex2D(cenaSampler ,input.TexCoord );

    //compute screen-space position
    float4 position;
    position.x = input.TexCoord.x * 2.0f - 1.0f;
    position.y = -(input.TexCoord.y * 2.0f - 1.0f);
    position.z = depthVal;
    position.w = 1.0f;
    //transform to world space
    position = mul(position, InvertViewProjection);
    position /= position.w;

	float d = length(position - cameraPosition);
	float l = exp(-d * dz);
	l = saturate(1 - l);

    return float4(lerp(cen,fcolor, l),1);
}

float4 PixelShaderExponencialSquared(VertexShaderOutput input) : COLOR
{
    float depthVal = tex2D(depthSampler,input.TexCoord).r;
    float3 cen = tex2D(cenaSampler ,input.TexCoord );

    //compute screen-space position
    float4 position;
    position.x = input.TexCoord.x * 2.0f - 1.0f;
    position.y = -(input.TexCoord.y * 2.0f - 1.0f);
    position.z = depthVal;
    position.w = 1.0f;
    //transform to world space
    position = mul(position, InvertViewProjection);
    position /= position.w;

	float d = length(position - cameraPosition);
	float l = exp( - pow( d * dz , 2 ) );
	l = saturate(1 - l);

    return float4(lerp(cen,fcolor, l),1);
}

technique FogShader
{
	pass P0
	{
		VertexShader = compile vs_3_0 VShader();
		PixelShader = compile ps_3_0 Pshader();
	}
}

technique FogExponencialSquaredShader
{
	pass P0
	{
		VertexShader = compile vs_3_0 VShader();
		PixelShader = compile ps_3_0 PixelShaderExponencialSquared();
	}
}

technique FogExponencialShader
{
	pass P0
	{
		VertexShader = compile vs_3_0 VShader();
		PixelShader = compile ps_3_0 PixelShaderExponencial();
	}
}

The idea is very simple and naive, we just blend the scene color with the fog color using the scene distance (from depth texture) as a control parameter.
All the parameter used in the shaders are self explanatory (camera position, near plane, far plane …..). The only strange one is the dz (density) that we normally set to 0.001f.
The halfpixel is a directx 9c stuff (correction needed because of the directx rasterizer algorithm, in directx > 9c, this is not needed) and can be calculated in the following way (in XNA):

 Vector2 halfPixel = new Vector2()
            {
                X = 0.5f / (float)game.GraphicsDevice.PresentationParameters.BackBufferWidth,
                Y = 0.5f / (float)game.GraphicsDevice.PresentationParameters.BackBufferHeight
            };
           

We showed three ways of doing this (normal, exponencial and exponencial squared), you can choose each of then according with you quality/speed trade off.

, , ,