Monday, December 27, 2010

Depth of field

Depth of field usually relates to the lens effect often seen in photography and films that cause the objects that are out of focus to appear blurred. This is due to the fact that lens can precisely focus at only one distance at the time, gradually blurring objects that are too near or too far from the focal point. 



This effect is becoming more and more popular in 3D games, because it enhances the visual experience of the player, delivering a more cinematographic look and feel. This effect is widely used in Call of Duty, Crysis, and a lot of other games.

I recently got into the making of my own DOF shader to learn how it’s done, to use it in my engine, and of course to play with it! I wanted an adaptive DOF well suited for FPS, with a focal point located at the center of the screen, where the aim usually is. When the camera moves, the focal point changes, and the DOF dynamically adapt itself to focus on the new focal point. Here is the result:


The general algorithm:
  1. Copy the content of the Frame Buffer into a new buffer (The blur Buffer)
  2. Apply a Gaussian Blur to the Blur Buffer
  3. Repeat step two x time to increase the blur softness
  4. Clamp the Depth Buffer between 0 and 1
  5. Merge the Blur Buffer into the Frame Buffer. The more the depth of a pixel is different from the depth of the focal point (we assume this is the center of the screen), the more we blur that pixel
The GLSL code:

Horizontal Blur
[Vertex_Shader]
void main(void)
{
   gl_Position = ftransform();
   gl_TexCoord[0] = gl_MultiTexCoord0;
}

[Pixel_Shader]
uniform sampler2D blurTex; // 0
uniform float texWidth = 1024.0;

// Kernel weight vector
float kernel[5];
float samples[5];
                                        
void main()
{
   vec2 uv = gl_TexCoord[0].xy;
                   
   kernel[0] = 0.3;
   kernel[1] = 0.15;
   kernel[2] = 0.1;
   kernel[3] = 0.15;
   kernel[4] = 0.3;
                       
   vec4 total = vec4(0.0);
                   
   samples[0] = -2.0/texWidth;
   samples[1] = -1.0/texWidth;
   samples[2] = 0.0;
   samples[3] = 1.0/texWidth;
   samples[4] = 2.0/texWidth;
                       
   // Calculate a 5 X 5 Gaussian Blur Horiz
   for (int i = 0; i < 5; i++)
      total += texture2D( blurTex, vec2(uv.s + samples[i], uv.t)) * kernel[i];
                   
   gl_FragColor = total;
}

Vertical Blur
[Vertex_Shader]
void main(void)
{
   gl_Position = ftransform();
   gl_TexCoord[0] = gl_MultiTexCoord0;
}

[Pixel_Shader]
uniform sampler2D blurTex; // 0
uniform float texHeight = 768.0;

// Kernel weight vector
float kernel[5];
float samples[5];
                                        
void main()
{
   vec2 uv = gl_TexCoord[0].xy;
                   
   kernel[0] = 0.3;
   kernel[1] = 0.15;
   kernel[2] = 0.1;
   kernel[3] = 0.15;
   kernel[4] = 0.3;
                               
   vec4 total = vec4(0.0);

   samples[0] = -2.0/texHeight;
   samples[1] = -1.0/texHeight;
   samples[2] = 0.0;
   samples[3] = 1.0/texHeight;
   samples[4] = 2.0/texHeight;
                                              
   // Calculate a 5 X 5 Gaussian Blur Vert
   for (int i = 0; i < 5; i++)
      total += texture2D( blurTex, vec2(uv.s, uv.t + samples[i])) * kernel[i];
                   
   gl_FragColor = total * 1.0;
}

Buffer Merging
[Vertex_Shader]
void main(void)
{
   gl_Position = ftransform();
   gl_TexCoord[0] = gl_MultiTexCoord0;
}
                 
[Pixel_Shader]
uniform sampler2D sceneTex; //Frame Buffer
uniform sampler2D depthTex;
uniform sampler2D blurTex;

float clampDepth(vec2 uv)
{
   float n = 1.0;
   float f = 100.0;
   float z = texture2D(depthTex, uv).x;
   return (2.0 * n) / (f + n - z * (f - n));     
}
                                        
void main()
{
   vec2 uv = gl_TexCoord[0].xy;
   float d = clampDepth(uv);
   float centre = clampDepth(vec2(0.50.5));
   d = clamp(abs(centre - d) * 3.00.01.0);
                       
   gl_FragColor = mix(texture2D(sceneTex, uv), texture2D(blurTex, uv), d);
}

This Technique is quite fast. With a Frame Buffer and a Blur Buffer size of 1024x768, it runs at 750 fps with 3 iterations, at 600 fps with 4 iterations, and 500 fps with 5 iterations on my Nvidia gtx 470. With only 3 iterations, the visual quality is good enough for most applications, and is what was used to produce the screenshots in this post. It is possible to down-sample the Blur Buffer to 1/2 or even 1/4 of the Frame Buffer size to achieve better performances, at the cost of quality of course. 

The visual quality of the technique is good, but there is still place for improvement. We can see that there is a quite noticeable bleeding artifact around the edges:


This artifact is a side effect of the buffer merging operation, and can not be easily removed using the technique shown above. I will look forward to find a way to diminish this effect, and will post it as soon as I achieve better results.

No comments:

Post a Comment