Adding SSAO to glTF Models with Post-Processing
This article outlines the post-processing pipeline required to implement Screen-Space Ambient Occlusion (SSAO) on 3D models stored in the glTF format. While glTF inherently supports static ambient occlusion via pre-baked texture maps, dynamic SSAO calculates soft contact shadows in real time based on the rendered geometry. The following sections break down the multi-stage post-processing techniques used by modern real-time rendering engines—such as Three.js, Babylon.js, or custom WebGL/WebGPU pipelines—to extract scene data from glTF assets, compute occlusion values, and blend the final result.
1. Generating Depth and Normal Buffers (Pre-Pass)
Before computing occlusion, the renderer must extract the geometric information of the rendered glTF scene into screen-space textures (a mini G-buffer):
- Linear Depth Buffer: glTF meshes are rendered to an off-screen framebuffer to capture depth information. This depth buffer is typically linearized so that distance from the camera scales uniformly across the screen.
- View-Space Normal Buffer: Alongside depth, surface normals transformed into view space are rendered into an RGB texture. These normals define the orientation of the surface at each pixel, determining the direction in which occlusion tests should occur.
Engines often achieve this either through a depth-normal pre-pass or by writing directly to multiple render targets (MRT) during the standard rendering pass.
2. Hemisphere Sampling and Occlusion Testing
The core SSAO pass runs as a full-screen quad shader that reads the depth and normal buffers:
- Sample Kernel Creation: A set of sample points (usually 16 to 64 samples) distributed inside a hemisphere is generated. The hemisphere is oriented along the surface normal extracted from the normal buffer. Samples are clustered closer to the origin to favor nearby occlusion.
- Projection to Screen Space: Each sample point in the hemisphere is projected back into screen space using the camera's projection matrix.
- Depth Comparison: The shader reads the scene depth at the sample's projected coordinates and compares it to the sample’s calculated view-space depth. If the scene depth is closer to the camera than the sample depth, the sample is occluded.
- Range Check: A falloff function is applied to prevent distant, unrelated geometry from incorrectly occluding foreground objects (often referred to as "halo" artifacts).
3. Jittering and Randomization
Evaluating high sample counts per pixel is computationally expensive. To keep sample counts low while maintaining performance:
- Noise Texture Rotation: A small, tiled noise texture (typically 4x4 pixels containing random 2D rotation vectors) is mapped across the screen.
- Sample Rotation: The sample kernel is randomly rotated around the normal vector using this noise texture. This transforms banding patterns into high-frequency noise, which can be resolved efficiently in a subsequent blur pass.
4. Bilateral Filtering (Depth-Aware Blur)
Because the randomization step introduces high-frequency noise, a blur pass is essential to smooth the occlusion factor:
- Edge-Preserving Blur: A standard Gaussian blur would cause occlusion shadows to bleed across object boundaries and distinct depth layers. Instead, an edge-preserving bilateral filter is used.
- Weight Calculation: The filter weights neighbor samples based on both spatial distance and depth/normal differences. Samples with significant depth or normal disparities are discarded, keeping the contact shadows sharp along edges while smoothing flat surfaces.
5. Final Compositing
Once the smoothed occlusion texture is generated, it must be integrated with the glTF PBR (Physically Based Rendering) pipeline:
- Indirect Lighting Modulation: SSAO is designed to simulate the blockage of indirect ambient light. In physically accurate engines, the SSAO scalar (ranging from 0.0 to 1.0) is multiplied specifically by the ambient light term, image-based lighting (IBL), or the diffuse indirect component before direct lighting is added.
- Full-Scene Multiplication: In simpler rendering setups, the SSAO texture is directly multiplied over the final rendered color buffer of the glTF scene, darkening cavities, crevices, and contact points between meshes.