There are 2 big problems with SSAO. First is locality. The lower radius, the higher quality. But set radius too low and instead of occlusion you get very slow edge detection filter. And main disadvantage of SSAO is how it looks on flat surfaces. This is typical implementation (similar to Crysis') and looks OK as long as you want to use it as only shading method. If you want to combine it with normal lighting & shadows, it's just too messy.
But there is a valuable trick to keep flat surfaces flat. If you check normal vector between sampled pixel and occluder, you can reject false positives. But this is very expensive. You need to sample depth, position and normal for N samples per pixel. This quickly kills FPS. But leave FPS alone, bigger problem arises. You usually store normals along diffuse/specular/position values in G-buffer. But those are normal-mapped normals. They just won't do the trick, creating even more noise that without filtering.
So what can we do? Store geometry normals just for SSAO? All those who have spare place in their MRTs, raise your hands up. Nobody? Well... there is other possibility. You can calculate normals in shader. How? By differentiating posision.
float3 get_normal(float2 uv)
{
float3 pos = get_position(uv);
float3 b = normalize(dFdx(pos));
float3 t = normalize(dFdy(pos));
return normalize(cross(b, t));
}
Assuming that pos is world-space position, this would look like this: (about uhm, player character: DON'T ASK)

Of course we should do this calculation in view space. And the result: just 1 sampling (depth) per sample.
Combine it with halo removal (by rejecting samples whose depth differ too much) and you get robust, high quality SSAO.
Comments:
-
Tweety:
For me, the way you showed how it looks on flat surfaces is absolutely great:)
06.05.2010 14:45:03