This shader is pretty fast, considering that a Depth of Field effect can be pretty heavy to calculate.
As the shader is a post process effect, it can easily be implemented in any scene, without having to reprogram a lot or program the scene around the effect.
Depth of Field( DoF ) is an effect that you probably see every day. If you stare at something, you can see that the foreground and background of the object you are staring is getting unclear. This happens using a camera aslo, where you have to adjust the focus so the thing you want to take picture of looks clear and not blurry. I took a fast image with my camera just to show you the effect:

Now, how can we implement this in XNA?
First of all, we need a way to get the depth of any point in the scene. This is a perfect opportunity for the depth buffer to show us it’s magic skills( see tutorial 14 ).
To blur a scene, we will need to implement a new post process shader! This shader will take the average of a number of the pixels neighbour pixels, and return that color as the color we want the player to see. The blur shader will have a distance variable that will be used to modify the texture coordinate we will use as a lookup texture containing our normal scene, so it takes the top-left, top-right, bottom-left and bottom-right pixel, adds them together and devides it by 4 so we get the average. More on this later!
On the image, you can see the blur shader in effect. The shader takes the average of the surrounding pixels with a distance( in this case 0.003 ) set in the shader.
Another tip to blurring is to downscale a rendertexture multiple times, but I decided to use a shader for this instead!
For this, we will need something that describes where we want to have the focus point( distance from the eye ), the range of the focus( how quickly will we go from in-focus to out-of-focus? ), our cameras near clipping plane and the a modified version of the cameras far clipping plane. The modified Far clipping plane is calculated by taking the given Far clipping plane( say 300 ), and devide it by ( Far – Near ). If near is 1, we get 300/(300-1). This will convert the far clipping plane to a correct range [0,1] for use in the next equations.
The following equations will use a given distance and range to calculate what will be in-focus:
This returns a value between 0 and 1, where the value is 0 if it’s in focus, and 1 if it’s out-of-focus. I rendered the values here, where the range is pretty small( 10 ) and a distance of 70 units from the eye:
We got three shaders in our application. The first one is a depth texture shader covered in tutorial 14, the 2nd is the blur post process shader, and the last one is the Depth of Field shader.
Let’s start with the blur-shader:
// The blur amount( how far away from our texel will we look up neighbour texels? )
float BlurDistance = 0.003f;
sampler ColorMapSampler : register(s0);
{
float4 Color;
// Get the texel from ColorMapSampler using a modified texture coordinate. This
// gets the texels at the neighbour texels and adds it to Color.
Color = tex2D( ColorMapSampler, float2(Tex.x+BlurDistance, Tex.y+BlurDistance));
Color += tex2D( ColorMapSampler, float2(Tex.x-BlurDistance, Tex.y-BlurDistance));
Color += tex2D( ColorMapSampler, float2(Tex.x+BlurDistance, Tex.y-BlurDistance));
Color += tex2D( ColorMapSampler, float2(Tex.x-BlurDistance, Tex.y+BlurDistance));
// a color to it, in this case 4, to get the avg. color
Color = Color / 4;
// returned the blurred color
return Color;
}
We first need to declare a few variables that will be used to calculate whats in focus and whats not:
float Distance;
float Range;
float Near;
float Far;
Then we need the usual textures:
sampler SceneSampler : register(s0);
texture D1M;
sampler D1MSampler = sampler_state
{
Texture = <D1M>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
sampler BlurSceneSampler = sampler_state
{
Texture = <BlurScene>;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
float4 PixelShader(float2 Tex: TEXCOORD0) : COLOR
{
// Get the scene texel
float4 NormalScene = tex2D(SceneSampler, Tex);
// Get the blurred scene texel
float4 BlurScene = tex2D(BlurSceneSampler, Tex);
// Get the depth texel
float fDepth = tex2D(D1MSampler, Tex).r;
// Invert the depth texel so the background is white and the nearest objects are black
fDepth = 1 – fDepth;
// Calculate the distance from the selected distance and range on our DoF effect, set from the application
float fSceneZ = ( -Near * Far ) / ( fDepth – Far);
float blurFactor = saturate(abs(fSceneZ-Distance)/Range);
// Based on how far the texel is from "distance" in Distance, stored in blurFactor, mix the scene
return lerp(NormalScene,BlurScene,blurFactor);
}
Whats happeing here is that we take the pixel color from the normal scene, the blurred scene and the inverse of the depth texture as we want the distant objects to be white and the near objects to be black before starting on the next calculations:
float fSceneZ = ( -Near * Far ) / ( fDepth – Far);
float blurFactor = saturate(abs(fSceneZ-Distance)/Range);
now, using blurFactor, we can lerp between the color in the normal scene( will be fully visible if blurFactor is 0 ) and the blurred scene( will be fully visible if blurFactor is 1 ).
return lerp(NormalScene,BlurScene,blurFactor);
And that’s it!
Using the shader
In this case, we know the camera is about 80 units away from the spinning squidlike objects center, so we set the distance to about 70, with a range of 10 giving us a rather small in-focus part.
void SetShaderParameters( float fD, float fR, float nC, float fC )
{
focusDistance = fD;
focusRange = fR;
nearClip = nC;
farClip = fC;
farClip = farClip / ( farClip – nearClip );
effectPostDoF.Parameters["Range"].SetValue(focusRange);
effectPostDoF.Parameters["Near"].SetValue(nearClip);
effectPostDoF.Parameters["Far"].SetValue(farClip);
}
Download: Executable + Source
Hello!I implemented this shader into my game and it looks great. But there is one problem with it. All my semitransparent stuff (like particles) is blurred out, because I don\’t have a shader that gets the depth information out of the particles. I use quads like shown in the Microsoft quad sample.Do you have any idea how I can get semitransparent stuff rendered correct with your shader?Greetings!
Pingback: Depth of Field in XNA 4 | Nelxon Studio
Pingback: Depth of field, revisited! « Electronic Meteor
This is an excellent article. I do have a question about the equation:
Dz = -Near*Far/(D-far)
I believe this is converting D from a 0-1 depth value(projection space) to world space. Is that accurate?
I’d appreciate it if you could explain the math behind those equations or point me to some good reference for it. Like, I don’t know how this equation comes into play and how the modified version of Far was determined, etc.
Thanks again though, this article is great!
Pingback: Parallel-split shadow maps « Electronic Meteor
I don’t understand your shader…Especially where you say 0 <= 300/299 <= 1 in the calculation of the modified far plane.
You may want to add this to your shader code:
//Convert the Far distance to a value between [0-1]
Far = Far / (Far – Near);