A screenshot of Impaler's faux ambient occlusion without textured walls

Faking Ambient Occlusion

Impaler is a small game I created with a goal of having it run on integrated graphics (and still look nice). To achieve this, I had to find creative ways to fake expensive graphics techniques. Anything that relied on sampling or multiple render passes would be too expensive for low-end GPUs. This meant I had to find a new approach to implement ambient occlusion.


Background on Ambient Occlusion

Ambient occlusion, often abbreviated AO, is a graphics technique for self-shadowing. It estimates how much ambient light is blocked by nearby geometry and adds a soft shadow to those areas. It has a large impact on the visual quality of a scene. I wanted to use this to give the game world a dark and gloomy feel.

Screenshot of Crysis Crysis (2007) was one of the first games to incoprorate AO. It still looks good today!


Common AO Implementations

  • Screen space ambient occlusion (SSAO)
  • Horizon-based ambient occlusion (HBAO)

Both of these approaches perform calculations in screen space and do not slow with complex scenes. They work well for just about any game and produce convincing results. However, they require multiple depth buffer samples per pixel. This is too expensive for the low-end hardware and integrated graphics. A different approach was needed for Impaler.

In the future, I expect screen space AO implementations to disappear as games adopt ray tracing and global illumination models. These, by far, are the best looking solutions. Screen space effects can suffer from under-sampling and artifacts at the edges of the screen.

Screenshot of Quake 2 RTX Quake 2 RTX (2019) Features real-time ray tracing and global illumination. Indirect lighting and shadow is calculated via ray tracing.


Why Did Impaler Need AO?

Impaler takes place in a dark cathedral-like arena. Without proper light and shadow it simply did not create the illusion you were there. Most importantly, it didn’t look pleasing to the eye.

Screenshot of Impaler with ambient occlusion forced off

A screenshot from Impaler with AO forced off. Notice the scene is overly bright and the corners are hard to distinguish.

The environment is simple - it is a large rectangle with 6 columns and game object scattered throughout. Only a few areas in the world needed ambient occlusion:

  • Corners of the arena
  • Edges between walls and the floor
  • Bases of the stone columns

Approach Used in Impaler

As much I would have loved to use ray tracing, I needed a cheap solution and was willing to make tradeoffs. The tradeoff was to make the AO implementation scene-dependent and share actual map geometry with the fragment shader.

Pseudocode:

  • Pass the fragment shader a list of occluders and their geometry
    • Line segments for boundaries between wall and floor surfaces
    • Positions of the corners
    • Cylinders representing the stone columns
  • For each pixel, evaluate the contribution from each occluder in world space
  • Keep track of the largest occlusion value encountered (max() not sum())
  • Modulate the ambient light intensity based on the largest occlusion value
  • Render the final pixel using with occlusion-modulated ambient light term

GLSL:

The approach above achieves the desired visual effect. It is computationally cheap and doesn’t suffer from visual artifacts related to undersampling. It does a good job exploiting the simple geometry of the environment. However, it would not work for more complex scenes. Nor is it physically correct.


Impact on Visual Quality

Screenshot of Impaler with ambient occlusion enabled

Animation showing comparison of ambient occlusion on and off in Impaler

Not bad right?


Closing

Computer graphics is often about balancing performance and visual quality. For me, finding creative ways approximate complex lighting models is part of the fun.

If you enjoy these posts and would like to support my work, please consider buying my game on Steam!