06. Occlusion Query

Occlusion queries count the number of fragments (or samples) that pass the depth test, which is useful to determine visibility of objects.

If an object is drawn but 0 fragments passed the depth test, it is fully occluded by another object. In practice this means that a simplification of an object is drawn using an occlusion query (for example: A bounding box can be the occlusion substitute for a truck) and only if fragments of the simple object pass the depth test, the complex object is drawn. Please read Conditional Render for a convenient solution.

Note that the simplified object does not actually have to become visible, one can set GL.ColorMask and GL.DepthMask to false for the purpose of the occlusion query. The only GL.Enable/Disable state associated with it is the DepthTest. If DepthTest is disabled all fragments will automatically pass it and the occlusion test becomes pointless.

Occlusion Query handles are generated and deleted similar to other OpenGL handles:

uint MyOcclusionQuery;
GL.GenQueries( 1, out MyOcculsionQuery );
GL.DeleteQueries( 1, ref MyOcculsionQuery );

The draw commands which contribute to the count must be enclosed with GL.BeginQuery() and GL.EndQuery().

GL.BeginQuery( QueryTarget.SamplesPassed, MyOcculsionQuery );
// draw...
GL.EndQuery( QueryTarget.SamplesPassed );

It is very important to understand that this process is running asynchronous, by the time the CPU is querying the result of the count the GPU might not be done counting yet. OpenGL provides additional query commands to determine whether the occlusion query result is available, but before it is confirmed to be available any query of the count is not reliable. The following code will get a reliable result.

uint ResultReady=0;
while ( ResultReady == 0 )
  GL.GetQueryObject( MyOcculsionQuery, GetQueryObjectParam.QueryResultAvailable, out ResultReady );
uint MyOcclusionQueryResult=0;
GL.GetQueryObject( MyOcculsionQuery, GetQueryObjectParam.QueryResult, out MyOcclusionQueryResult );
// MyOcclusionQueryResult is now reliable.

However this is not very efficient to use because the CPU will spin in a loop until the GPU is done counting.

A better approach is to do the occlusion queries in the first frame and do not wait for a result. Instead continue drawing as normal and wait for the next frame, before you check the results of the query. In other words frame n executes the query and frame n + 1 reads back the results.

This approach hides the latency inherent in occlusion queries and improves performance, at the cost of slight visual glitches (an object may become visible one frame later than it should). You can read a very detailed description of this technique on Chapter 29 of GPU Gems 1, which also covers other caveats of occlusion queries.