Why wasn't CryTek SSAO multiplied by 2?

Screen space ambient occlusion

https://learnopengl.com/Advanced-Lighting/SSAO "Because the sample kernel used was a sphere, it caused flat walls to look gray as half of the kernel samples end up being in the surrounding geometry."

OK, let me assume the following facts:

• There is no absolute correctness in AO.
• I am not talking about the later improvement of using hemi-sphere kernel.

So, flat walls are 0.5, wall corners are 0.25. By multiplying 2, they become 1.0 and 0.5 that obviously should be more correct. The question is, why didn't they think of this simple "multiplying 2" when the original algorithm was released?

Thanks.

RTR4: “edges being brighter than their surroundings”. Edges were 0.75, by x2 and clamping to 1.0 they would be the more correct 1.0.

• Multiplying by 2 would just make everything brighter, the walls are still relatively darker than they should be however. The issue is that Crytek's SSAO is a hack constructed with no real understanding of the rendering equation. They integrate over a sphere instead over the upper hemisphere. The obvious solution is to integrate over the upper hemisphere (as many follow-up SSAO implementations do). Oct 30, 2021 at 11:23
• @lightxbulb Walls will become 1.0 which is correct. Hemi-sphere was a much larger change to the algorithm later, at least, when CryTek released the algorithm, it should be fairly easy to think of "x 2" to avoid the gray feel. Oct 30, 2021 at 12:53
• It is not correct. The relative behaviour is still wrong. Hemisphere sampling is a trivial change. Oct 30, 2021 at 13:06
• 1.0 (no occlusion) for flat walls is correct and x2 for everywhere is more correct than the original algorithm. The question is why they left the effect gray without even thinking about a simple x2. Oct 30, 2021 at 13:34
• Because x2 is still wrong. Shading is relative to what you have around it, multiplying everything is equivalent to overexposure not to correction. If you have a reasonable tonemapper it will correct for this anyways and the image would be exactly the same as without the x2. It's simply the wrong "solution" to the wrong problem. I understand that you want me to tell you that you came up with a wonderful solution, but this is simply not the case. Oct 30, 2021 at 14:06

Ambient occlusion is the direct illumination at a Lambertian surface point due to a homogeneous light source at infinity. That means that you assume a light source with $$L_e(\pm\infty, \omega_i) = 1$$, and a fully reflective (energy conserving) Lambertian brdf $$f(\omega_o, x, \omega_i) = \frac{1}{\pi}$$. Intuitively this is is like putting your object inside an emissive sphere, and computing the illumination at every surface point due to direct illumination (ignoring illumination from later bounces). Formally it is a simplification of the rendering equation:

$$\require{cancel} L = L_e + TL = L_e + TL_e + T^2L \approx L_e + TL_e + \cancel{T^2L}$$

What ambient occlusion is computing is this $$TL_e$$ term with the discussed $$L_e$$ and brdf. The $$TL_e$$ term is of the form:

$$(TL_e)(x, \omega_o) = \int_{\Omega_{N_x}}f(\omega_o, x, \omega_i)L_e(r(x,\omega_i), -\omega_i)(N_x \cdot\omega_i)d\omega_i$$ $$= \frac{1}{\pi}\int_{\Omega_{N_x}}(N_x \cdot\omega_i)V_{x, \omega_i}\,d\omega_i,$$

where $$N_x$$ is the unit normal at $$x$$, $$\Omega_{N_x}$$ is the upper hemisphere centered around $$N_{x}$$, $$r(x,\omega_i)$$ is the ray-tracing function. I have used in the second equality that $$f(\omega_o, x, \omega_i) = \frac{1}{\pi}$$, and $$L_e(\pm\infty, \omega_i) = 1$$, where $$V_{x,\omega_i}$$ is one if the ray with origin $$x$$ and direction $$\omega_i$$ intersects nothing, and zero otherwise.

The ambient occlusion term is cheaper to compute than a full-blown light transport simulation with multiple bounces and varying light sources, while at the same time providing a somewhat convincing shading and visual cues for the geometry of an object. For this reason it is often considered in real-time graphics, with various approximations of it (e.g. integration based on information from the depth buffer with a range-limited $$V_{x, \omega_i}$$ term). Note however that the integration happens over the upper hemisphere centered around the normal: $$\Omega_{N_x}$$, and not over the whole sphere (integration over the full sphere would consider transmitting materials and various things would need to be adjusted). The integration over the whole sphere present in Crytek's SSAO is a mistake that can be rectified easily by considering the normal of the fragment being shaded (as many follow-up SSAO implementations do).

As for the suggested solution, consider the following. A multiplication by 2 is linear and thus doesn't change the relative appearance between two nearby pixels. Let pixel A have ambient occlusion value $$c_A$$ and pixel B have ambient occlusion value $$c_B$$, then if we multiply those by $$k\ne 0$$ the ratio remains the same:

$$\frac{c'_A}{c'_B} = \frac{kc_A}{kc_B} = \frac{c_A}{c_B}.$$

Since visual shading cues are relative, the above is not very useful, and amounts to rescaling the brdf in the ambient occlusion - it doesn't correct for the incorrect integration domain. By all means, you can multiply the SSAO by $$2$$, but even with clamping this will result in an incorrect solution. The simplest solution is to generate samples in the upper hemisphere by using the normal $$N_x$$ at the fragment being shaded. This is not a complex modification of the Crytek SSAO either, you just have to reflect the samples that end up in the wrong hemisphere ($$N_x \cdot (y-x) <0$$) so that they'll end up on the correct side ($$N_x \cdot (y-x) \geq 0$$).

1 possible answer from RTR4: "Doing so causes a flat surface to be darkened, with edges being brighter than their surroundings. Nonetheless, the results are often visually pleasing."