OpenGL Programming/Motion Blur
Introduction
editApart from color, depth and stencil buffers, most graphics cards also provide a so-called accumulation buffer. These are essentially color buffers with a precision 16 bits or more per color. However, it is not possible to draw directly into the accumulation buffer. Instead, there are only a few operations that work on the entire accumulation buffer at once.
glAccum(GL_LOAD, value)
: Copy the contents of the normal color buffer, multiplied byvalue
, to the accumulation buffer.glAccum(GL_RETURN, value)
: Copy the contents of the accumulation buffer, multiplied byvalue
, to the color buffer.glAccum(GL_ACCUM, value)
: Add the contents of the normal color buffer, multiplied byvalue
, to the accumulation buffer.glAccum(GL_ADD, value)
: Addvalue
to all the pixels in the accumulation buffer.glAccum(GL_MULT, value)
: Multiply all the pixels in the accumulation buffer byvalue
.
In all cases, value
is a float. These functions make it easy to calculate the (weighted) sum or average of a number of frames.
In this first tutorial, we will see how we can use this to easily add motion blur to almost any OpenGL program.
Motion blur
editMotion blur happens when objects move so fast (with respect to the camera), or the exposure time of a camera is so long, that during the exposure, some objects have moved more than half a pixel, and therefore appear smeared out. Motion blur is normally not desirable, but it is sometimes hard to avoid with real cameras, because the exposure time depends (for a large part) on the amount of light in the scene. The less light there is, the longer the exposure time, and the stronger the effect of motion blur is. This is one of the reasons that video shot outdoors with a camcorder "feels" different from video shot indoors with the same camera.
Simulating motion blur
editIt is actually very easy to create motion blur effects with the accumulation buffer. The usual way an OpenGL application works is this:
while(true) {
do_time_evolution(timestep);
draw_scene();
glSwapBuffers();
wait_until_next(timestep);
}
Where the function do_time_evolution()
takes care of updating the coordinates of moving objects in the scene, and/or of the movement of the camera itself.
After the scene is drawn, it is shown on screen with glSwapBuffers()
, and optionally you wait for a while to limit the framerate.
To add motion blur, you can draw the scene multiple times with smaller timesteps, and only show the average of those:
int n = <number of frames to accumulate>
int i = 0;
while(true) {
do_time_evolution(timestep / n);
draw_scene();
if(i == 0)
glAccum(GL_LOAD, 1.0 / n);
else
glAccum(GL_ACCUM, 1.0 / n);
i++;
if(i >= n) {
i = 0;
glAccum(GL_RETURN, 1.0);
glSwapBuffers();
wait_until_next(timestep);
}
}
The best effect is achieved by accumulating a large number of frames, but you will be quickly limited by the power of your GPU.
It is best to make the number of frames you accumulate configurable, or otherwise try to automatically determine how many frames you can render within one full timestep
.
Exercises
edit- Add motion blur to any of the previous tutorials. The more interesting ones are the mini-portal and glescraft, where you can walk around the scene.
- If you are adding post-processing effects to your scene, should you do this before or after applying motion blur?
- In case your GPU doesn't provide an accumulation buffer, could you recreate glAccum() functionality by rendering to textures?