Depth of Field
From GameStudio Wiki
The depth of field (DOF) is the distance in front of and beyond the subject that appears to be in focus, or in layman's-terms: A DOF Shader causes out-of-focus objects appear to be blurry.
In Computer graphics, a pinhole camera model is used which gives the advantage of having perfectly sharp images independently where the focus is as opposed to real cameras where the lenses have finite dimensions causing the DOF effect. The application of a DOF shader tries to emulate that "finite dimension" effect in order to create a higher level of realism or creating more impacting cinematic effects.
Although a DOF effect actually makes a game more realistic by not providing an infinite sharp sight to the player, an application in-game can create a high level of distraction or confusion and - when not used and adjusted properly - eye strain and a sinking fun factor of the game. The main appliance of DOF shaders are in the field of cutscenes to provide a more cinematic effect and/or a higher level of realism. However, DOF shaders can also be found in a in-game situation as for example a sharpshooter situation where, while zooming, only the focus is sharp and the rest of the field of view remainst blurry.
Contents |
Theory
The Shader code here is pure theory and will most likely not work in a real working environment.
Thesis
Depth of field is the simulation in 3D graphics of a lens-based camera. Conventional rendering techniques model a pinhole camera paradigm, this means that all aspects of the picture share the same level of focus (the entire picture is in focus). With a lens-based camera, the rendered image will have varying levels of focus based on the ‘focus’ of the lens. Since we’re not using a ray-tracing engine, it’s hard to approximate the effects of light bending through a lens in the projection phase of rendering so we can ‘fake’ Depth of Field by breaking the problem down.
Approach
To simulate depth of field we will need to employ two advanced rendering techniques, the first is Vertex/Pixel Shaders, and the second is Multi-Pass Rendering. Vertex/Pixel (or Fragment) Shaders are small programs that run directly on the GPU over a specific set of data. A vertex shader is a program that’s run for every vertex it’s applied to. A Pixel (Fragment) shader is a program that runs for every ‘fragment’ which is a visual body of pixels drawn to the buffer, this is usually interpolated accross every pixel. Multi-Pass Rendering is a technique in which we render the same scene multiple times per frame, with different effects and/or purposes and we merge the outputs in some fassion to provide the desired result.
First Pass
Firstly we need to render our scene, but this will be in pin-hole format, or completely in focus. We apply a Vertex/Fragment shader to all the objects in the scene, the purpose of this shader is to calculate the distance from the camera of each pixel and store the blurriness coefficient in the alpha channel of the scene.
The shader will need various inputs from the program, such as the camera location, the visual depth of the focus plane, the visual depth of the near focal plane, and the far focal plane. This will allow us to set up a gradient of blurriness between the focal plane and the far focal plane, likewise for the near focus plane.
let fp = focal plane
let ffp = far focal plane
let nfp = near focal plane
let d = distance from camera
let b = blurriness
if (d > ffp OR d < nfp) { b = 1.0; }
elseif (d > fp AND d < ffp)
{
b = (d - fp) / (ffp - fp);
}
elseif (d < fp AND d > nfp)
{
b = 1 - ( (d - nfp) / (fp - nfp) );
}
else
{
b = 0;
}
After the pass is rendered, the framebuffer is copied into a texture while maintaining the alpha channel for use in pass 2.
Second Pass
The second pass we will render a single quad to the screen and apply the texture generated by the first pass with a second Vertex/Fragment Shader. We will only need to pass the texture and the max blurriness to this shader, the rest of the information is stored inside the texture.
For each pixel we construct a unit circle (1.0 pixels big) with a possion distrobution of a number of points. If you’re lazy you can use a fast-blur technique instead of gaussian by setting your unit points to:
(1, 0), (-1, 0), (0, 1), (0, -1), (0.4, 0.4), (-0.4, 0.4), (-0.4, -0.4), and (0.4, -0.4), this will give a good average 8-point sample.
We then take the texture coordinates of the pixel we’re looking at and for each sample in our list (possion or lazy) add sample-point * (blurriness * max_blurriness) to the texture coordinates and sample the texture @ the resulting pixel. The samples are all averaged and a resulting color pixel is generated and output at the original location.
This is a simple blurring technique. Where blurriness is 0, the sample unit circle is effectively multiplied by 0 and will yield the source pixel. Where blurriness is 1.0, the unit circle is multiplied by max_blurriness, and will expand the sample points to sample nearby pixels.
We run into one problem however, at the edge of a sharp foreground object, we will get bleed from the sharp object into the blurred background/foreground. To compete this, we test the blurriness of each sample point, if the blurriness difference between the source point and the sample point is greater than a set threshold, we discard the sample and replace it with a sample from the point we’re inspecting.
And there you have it, a 2-Pass Depth of Field technique.
References
- ^Advanced Depth of Field. Thorsten Scheuermann. http://ati.amd.com/developer/gdc/scheuermann_depthoffield.pdf
- ^Wikipedia. http://en.wikipedia.org/wiki/Depth_of_field
- ^Reanmachine Blog. http://www.reanmachine.com/2008/03/11/depth-of-field-simplified/
