<p><a href="https://en.wikipedia.org/wiki/Ambient_occlusion" rel="nofollow noreferrer">Ambient occlusion</a> 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 <span class="math-container">$L_e(\pm\infty, \omega_i) = 1$</span>, and a fully reflective (energy conserving) Lambertian <a href="https://en.wikipedia.org/wiki/Bidirectional_reflectance_distribution_function" rel="nofollow noreferrer">brdf</a> <span class="math-container">$f(\omega_o, x, \omega_i) = \frac{1}{\pi}$</span>. 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 <a href="https://en.wikipedia.org/wiki/Rendering_equation" rel="nofollow noreferrer">the rendering equation</a>:</p>
<p><span class="math-container">$$\require{cancel} L = L_e + TL = L_e + TL_e + T^2L \approx L_e + TL_e + \cancel{T^2L}$$</span></p>
<p>What ambient occlusion is computing is this <span class="math-container">$TL_e$</span> term with the discussed <span class="math-container">$L_e$</span> and brdf. The <span class="math-container">$TL_e$</span> term is of the form:</p>
<p><span class="math-container">$$(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$$</span>
<span class="math-container">$$= \frac{1}{\pi}\int_{\Omega_{N_x}}(N_x \cdot\omega_i)V_{x, \omega_i}\,d\omega_i,$$</span></p>
<p>where <span class="math-container">$N_x$</span> is the unit normal at <span class="math-container">$x$</span>, <span class="math-container">$\Omega_{N_x}$</span> is the upper hemisphere centered around <span class="math-container">$N_{x}$</span>, <span class="math-container">$r(x,\omega_i)$</span> is the ray-tracing function. I have used in the second equality that <span class="math-container">$f(\omega_o, x, \omega_i) = \frac{1}{\pi}$</span>, and <span class="math-container">$L_e(\pm\infty, \omega_i) = 1$</span>, where <span class="math-container">$V_{x,\omega_i}$</span> is one if the ray with origin <span class="math-container">$x$</span> and direction <span class="math-container">$\omega_i$</span> intersects nothing, and zero otherwise.</p>
<p>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 <span class="math-container">$V_{x, \omega_i}$</span> term). Note however that the integration happens over the upper hemisphere centered around the normal: <span class="math-container">$\Omega_{N_x}$</span>, 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).</p>
<p>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 <span class="math-container">$c_A$</span> and pixel B have ambient occlusion value <span class="math-container">$c_B$</span>, then if we multiply those by <span class="math-container">$k\ne 0$</span> the ratio remains the same:</p>
<p><span class="math-container">$$\frac{c'_A}{c'_B} = \frac{kc_A}{kc_B} = \frac{c_A}{c_B}.$$</span></p>
<p>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 <span class="math-container">$2$</span>, 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 <span class="math-container">$N_x$</span> 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 (<span class="math-container">$N_x \cdot (y-x) &lt;0$</span>) so that they'll end up on the correct side (<span class="math-container">$N_x \cdot (y-x) \geq 0$</span>).</p>

CC BY-SA 4.0
2021-10-31T11:11:22.610Z
2021-10-31T11:25:01.620Z
2021-10-31T11:25:01.620Z
lightxbulb
<p>1 possible answer from RTR4:
&quot;Doing so causes a <strong>flat surface to be darkened</strong>, with <strong>edges being brighter than their surroundings</strong>. Nonetheless, the results are <strong>often visually pleasing</strong>.&quot;</p>

CC BY-SA 4.0
2021-10-31T06:35:25.410Z
2021-10-31T06:35:25.410Z
macrod
<p>Screen space ambient occlusion</p>
<p><a href="https://learnopengl.com/Advanced-Lighting/SSAO" rel="nofollow noreferrer">https://learnopengl.com/Advanced-Lighting/SSAO</a>
&quot;Because the sample kernel used was a <strong>sphere, it caused flat walls to look gray</strong> as half of the kernel samples end up being in the surrounding geometry.&quot;</p>
<p>OK, let me assume the following facts:</p>
<ul>
<li>There is no absolute correctness in AO.</li>
<li>I am not talking about the later improvement of using hemi-sphere kernel.</li>
</ul>
<p>So, flat walls are 0.5, wall corners are 0.25. By <strong>multiplying 2, they become 1.0 and 0.5</strong> that obviously should be more correct. The question is, <strong>why didn't they think of this simple &quot;multiplying 2&quot; when the original algorithm was released?</strong></p>
<p>Thanks.</p>
<p>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.</p>

CC BY-SA 4.0
2021-10-30T11:23:47.727Z
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).
lightxbulb
CC BY-SA 4.0
2021-10-30T12:53:07.897Z
@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.
macrod
CC BY-SA 4.0
2021-10-30T13:06:12.830Z
It is not correct. The relative behaviour is still wrong. Hemisphere sampling is a trivial change.
lightxbulb
CC BY-SA 4.0
2021-10-30T13:34:22.583Z
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.
macrod
CC BY-SA 4.0
2021-10-30T14:06:44.493Z
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.
lightxbulb
CC BY-SA 4.0
2021-10-31T00:50:41.567Z
@lightxbulb There is no absolute correctness in AO. The original all-gray appearance, and x2, which is more wrong? Obviously, x2 makes everywhere more correct by compensating for the sphere factor (the other way for compensating it is hemi-sphere, effectively turning 0.5 to 1.0), the 2 great examples are flat walls (becoming 1.0) and 90-degree corners (becoming 0.5). Flat walls get the correct 1.0 without overexposure. Every pages about CryTek SSAO only say "gray" without even mentioning the simple x2. I changed the description of question incase the answer goes to other directions.
macrod
CC BY-SA 4.0
2021-10-31T07:22:33.117Z
"There is no absolute correctness in AO." See: https://en.m.wikipedia.org/wiki/Ambient_occlusion#Implementation
Any approximation that uses the upper hemisphere is more correct.
lightxbulb
CC BY-SA 4.0
2021-10-31T10:40:09.840Z
@lightxbulb you should change all your comments to an answer your comments really hit the nail on the head.
pmw1234
CC BY-SA 4.0
2022-04-11T09:41:32.443Z
with a sphere, corners are brighter than 0.5 and multiplying by 2 makes them brighter than 1
Criticize SE actions means ban
<ambient-occlusion><ssao>
Why wasn't CryTek SSAO multiplied by 2?
macrod
# Why wasn't CryTek SSAO multiplied by 2?

(Asked by: username_0 on 2021-10-30)

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.

**Tags:** ambient-occlusion, ssao

> **Comments:**
> **username_1**: 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).  
> **username_0**: @username_1 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.  
> **username_1**: It is not correct. The relative behaviour is still wrong. Hemisphere sampling is a trivial change.  
> **username_0**: 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.  
> **username_1**: 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.  
> **username_0**: @username_1 There is no absolute correctness in AO. The original all-gray appearance, and x2, which is more wrong? Obviously, x2 makes everywhere more correct by compensating for the sphere factor (the other way for compensating it is hemi-sphere, effectively turning 0.5 to 1.0), the 2 great examples are flat walls (becoming 1.0) and 90-degree corners (becoming 0.5). Flat walls get the correct 1.0 without overexposure. Every pages about CryTek SSAO only say "gray" without even mentioning the simple x2. I changed the description of question incase the answer goes to other directions.  
> **username_1**: "There is no absolute correctness in AO." See: https://en.m.wikipedia.org/wiki/Ambient_occlusion#Implementation Any approximation that uses the upper hemisphere is more correct.  
> **username_2**: @username_1 you should change all your comments to an answer your comments really hit the nail on the head.  
> **username_3**: with a sphere, corners are brighter than 0.5 and multiplying by 2 makes them brighter than 1  

---

## 2 answers

### Answer by username_1 on 2021-10-31. Score: 5

[Ambient occlusion](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/Bidirectional_reflectance_distribution_function) $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](https://en.wikipedia.org/wiki/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$).


---

### Answer by username_0 on 2021-10-31. Score: 1

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**."


---

