Implementing 3 Screen Space Ambient Occlusion Methods in WebGL

How do we get movie-quality lighting in real time?

Traditional 3D pipelines take a number of shortcuts when simulating lighting in a scene in order to get realtime performance. The single largest shortcut is to disregard global illumination when calcuating lighting in the scene and instead only look at local, direct lighting. This is much more efficient but also much less realistic. There has been a lot of research around ways to efficiently capture some of the feel and realism of a movie-quality global illumination simulation without the extra overhead, and one of the most popular approaches is known as ambient occlusion (AO).

What is ambient occlusion?

Ambient occlusion is a local approximation of global illumination in a scene estimated by computing the visibility around a given point. E.g., corners don't "see" as much of the scene as flat planes so they would intuitively receive less light, and this is exactly what ambient occlusion is getting at. In essence, AO makes scenes look more realistic by darkening areas that wouldn't receive as much indirect light at a fraction of cost of a full global illumination simulation.

Screen-space ambient occlusion

The most common way of incorporating ambient occlusion into a modern realtime graphics pipeline is via some variant of screen-space ambient occlusion (SSAO), an estimation of AO that is calculated each frame per-pixel. SSAO fits in particularly well to a modern, deferred rendering pipeline such as Luma as a post-process effect operating on the g-buffer, where the depth values of scene geometry are viewed as a coarse-grained heightmap such that pixels with lots of occluders nearby in the heightmap approximation will have a higher amount of occlusion.

Comparing SSAO Methods in Luma

Currently, we've implemented three of the most popular SSAO techniques:

For all the algorithms, we perform an edge-aware blur pass over the AO buffers to reduce noise introduced by sampling artifacts and we support AO generation at a lower resolution with bilateral upsampling to match the render target size in the blur pass to improve performance.

Here is a direct comparison of the filtered AO buffers for the three techniques, with default settings applied.

Basic SSAOHBAOSAO

And here is a corresponding comparison of the rendered scene with AO applied. Hovering over a screenshot will display the base render without ambient occlusion. Note that the intensity of the effect has been increased for the purposes of this visualization, but in general all three SSAO techniques capture geometric creases and contact shadows between objects.

Basic SSAOHBAOSAO

Interactive SSAO demo

You can try various parameters for each method and compare the results for yourself in the following demo:

### SAO produces the best results

With respect to visual quality, SAO is capable of producing similar results to HBAO but at a fraction of the cost, and the basic SSAO version can only compete by bumping up the sampling rate to an unrealistic level for realtime performance. With carefully tuned settings to produce similar quality results, SAO requires ~9 texture fetches per pixel (SAO sample count set to 8), HBAO requires ~50 texture fetches per pixel (HBAO 7 sample directions and 7 steps along each direction), and basic SSAO requires ~17 texture fetches per pixel (SSAO sample count set to 16), though the output quality of basic SSAO is still not as good as HBAO or SAO.

The main difference between basic SSAO and HBAO/SAO is that basic SSAO takes occlusion samples in camera-space within a normal-oriented hemisphere around the camera-space position of a given fragment, whereas HBAO and SSAO both perform the sampling in screen-space and then project those samples back to camera-space in order to determine occlusion.

A number of existing WebGL demos and applications implement basic SSAO and could be improved at no additional performance cost by switching to SAO.