Assorted Notes about Ambient Occlusion

by Greg Coombe



Motivation

Ambient occlusion is a lighting technique used to give models a global illumination-like effect, as if it were lit from the entire hemisphere (rather than a point light). A set of visibility samples are collected from the hemisphere above a point, and a scalar "ambient occlusion" value is computed based on the percentage of unoccluded samples. For a nice overview of ambient occlusion, check out Steve Hill's article in Gamastura. Some other very good ambient occlusion tutorials have been written by Zhang Jian, Bob Moyer, and Andrew Whitehurst.


Approaches

There are two basic approaches for computing ambient occlusion. The first is ray tracing; simply shoot out rays in uniform pattern across the hemisphere. The percentage of rays that hit geometry is divided by the total number of rays to get a scalar value. This could also be done with hardware rendering by placing a camera at every triangle and counting the percentage of the hemisphere that is visible. Since this requires computing occlusion at every triangle, this approach is in general O(N^2), although with efficiency structures (such as bounding volume hierarchies) it can be reduced to O(NlogN).

The second approach is an "outside-looking-in" approach. The model is rendered from a set of M random points on the sphere surrounding the model. The occlusion information is stored for each viewpoint, and averaged. The complexity of this approach is O(N*M), or O(MlogN) with efficiency structures. Since M is generally much smaller than N, this makes it interesting for realtime work. Since this is the approach I am interested in, let me explain the algorithm in more detail.

Outside-Looking-In Algorithm

for( some number K camera samples )
place a camera at a random point on the sphere surrounding the object
     // First pass - fill the depth buffer
glClear();
     glEnable( GL_depth_compare );
     for( each triangle i in model )
          render( tri[i] );
     end    

// Second pass - render into the depth buffer w/occlusion queries.
// This will give the number of unoccluded fragments
     for( each triangle i in model )
glBeginOcclusionQuery( i );
          render( tri[i] );
          glEndOcclusionQuery();
     end
     // Get occlusion queries with depth testing      for( each triangle i in model ) triangle[i]->TotalFragments = glGetOcclusionQuery(i);      end      // Third pass - render without depth testing.
// This gives the total number of fragments (unoccluded + occluded)

     glDisable( GL_depth_compare );
     for( each triangle i in model )
glBeginOcclusionQuery( i );
          render( tri[i] );
          glEndOcclusionQuery();
     end
     // Get occlusion queries without depth testing      for( each triangle i in model ) tri[i]->VisibleFragments = glGetOcclusionQuery(i); tri[i]->AmbientOcclusion += tri[i]->VisibleFragments / (tri[i]->TotalFragments * K);      end end
Using a three-pass algorithm like this means that you don't have to store off depth maps and use depth comparison. As a trade-off, however, you have to deal with large amounts of occlusion queries. In fact, you don't want to code the algorithm as I did above; the hardware will stall while waiting for the each of the occlusion queries to finish. A better way to implement this is use two (or more) batches of occlusion queries, and ping-pong between them.

More importantly, there is a neat advantage that you get from using occlusion queries in this manner. Notice that above I divide the VisibleFragments by the TotalFragments after each pass. However, there is an alternate method; maintain a running total of the fragments across all viewpoints, and only divide at the end. Interestingly enough, this gives you a cosine-weighted ambient occlusion (because nTotalFragments is larger when you face the polygon straight-on than at an oblique angle).

Bent Normal

Bent normals are an extension to ambient occlusion to allow more intesting lighting. The idea is to average the unoccluded vectors and use this vector (which is "bent" in the direction of unoccluded hemisphere) to look up in an environment map.

Results

These results aren't very impressive. I'll try to post more results as I get them.

Ball & Ground Plane

Head Model Cosine-Weighted Head Model




email at coombe@cs.unc.edu.