Implement multi-bounce ambient occlusion approximation#115426
Implement multi-bounce ambient occlusion approximation#115426KenzieMac130 wants to merge 10 commits intogodotengine:masterfrom
Conversation
|
@passivestar Could you give this a try on some materials of choice if you have time? I wonder how it behaves on wood, marble, stone, grass, etc. |
It is most visible on materials in indirect light with deep ambient occlusion and bright/saturated materials. Stylized materials and characters should be the clearest. Most scenes the ambient occlusion just looks slightly less over-darkened. I can get some better screenshots, this was just my playground scene. |
Ansraer
left a comment
There was a problem hiding this comment.
How exactly should this be handled when another technique (e.g. SDFGI, SSIL) already provides local albedo & lighting data?
Intuitively I would think this approximation should automatically disable itself, no?
Multi-bounce can exist fully alongside these techniques. It simply brings baked ambient occlusion maps and ssao closer to global illimunation by integrating curve fitting that approximates the appearance of GI onto single bounce ambient occlusion.
(*there will come a point where if the GI solution can capture every pore of occlusion that pre-baked AO/GI maps at any scale are unnecessary, but that is a distant future) Properly utilizing GI techniques together would look like this:
|
I did some quick testing and it may not be easy to see the albedo color contribution in a realistic scene with realistic materials besides the overall AO intensity. I would need to set up a stylized scene with clean and bright albedo to make the color contribution obvious That said, in principle I support adding this because even disregarding the color contribution, I did notice that godot SSAO looks a bit dark out of the box and has somewhat of a sharp blobby look to it, so I usually try to make it softer with power and intensity settings. Having AO that better approximates local GI out of the box should make it easier for people to get better visuals without having to mess with settings too much |
|
I wanted to specify since this has been a problem recently: No generative AI was used in the development of this PR. |
|
Am I correct in understanding that PR takes ambient light regardless of AO implementation? We have GTAO PR #113304, for which multi-bounce approximation was originally developed. Sorry for the off-topic question, but I think it's worth clarifying, since GTAO has proven itself and is probably implemented everywhere |
Correct. This implements the multi-bounce integration from the GTAO paper which is agnostic of AO source. Any source of single bounce ambient occlusion (which most realtime implementations and offline bakers produce) will benefit. I have also seen a misconception that the profile curve is based on human skin but that is not the case. The curve fitting was based on monochromatic albedo values and is scaled against the surfaces albedo. |
|
Rendering meeting: Kenzie gave a presentation on the how and why. All made good sense. |
BastiaanOlij
left a comment
There was a problem hiding this comment.
My nitpick is just that, probably not worth changing but I feel enabling syntax is better than disabling syntax (even though we use both in equal measures already).
Other than that, code looks good, and from the explanation of what this does, what's behind it, and that other engines have implemented this as well, it all makes sense to me that we adopt this.
| <member name="rendering/lights_and_shadows/directional_shadow/soft_shadow_filter_quality.mobile" type="int" setter="" getter="" default="0"> | ||
| Lower-end override for [member rendering/lights_and_shadows/directional_shadow/soft_shadow_filter_quality] on mobile devices, due to performance concerns or driver support. | ||
| </member> | ||
| <member name="rendering/lights_and_shadows/multi_bounce_occlusion/enabled" type="bool" setter="" getter="" default="true"> |
There was a problem hiding this comment.
So as discussed in the meeting, probably better to default this to false, but enable it in for new projects.
Existing projects become opt-in so people don't suddenly get an unexpected visual change.
New projects become opt-out, most devs will likely just accept the changed look.
There was a problem hiding this comment.
I am unsure of how to make this option false for old projects but true for new ones. Would appreciate any pointers to where/how this has been handled in the past.
There was a problem hiding this comment.
@Calinou I believe you are of the opinion that it should be enabled going forward regardless of legacy project status?
There was a problem hiding this comment.
@Calinou I believe you are of the opinion that it should be enabled going forward regardless of legacy project status?
Yes, same as microshadows: #112449 (comment)
| #endif // USE_LIGHTMAP_CAPTURE | ||
| #endif // !DISABLE_LIGHTMAP | ||
|
|
||
| #ifndef MULTI_BOUNCE_OCCLUSION_DISABLED |
There was a problem hiding this comment.
Pure nitpick, but my gut says:
| #ifndef MULTI_BOUNCE_OCCLUSION_DISABLED | |
| #ifdef MULTI_BOUNCE_OCCLUSION_ENABLED |
Makes more sense?
There was a problem hiding this comment.
Agreed. This was done to match #ifndef SPECULAR_OCCLUSION_DISABLED below it which also feels a bit off. Just a byproduct of some "When in Rome" programming.
There was a problem hiding this comment.
On this front I would need to push this inversion of code a bit further up the pipeline and do some more significant logic changes in order to make this change. Currently in godot's shader code there is already a mixed usage of negatives, positives, and double negatives which feels somewhat inconsistent and needs a future unification task. So if there are no strong objections I might just leave it as is for now.
Agreed, negatives for features are a bit iffy. This was just something I adopted to stay consistent with specular occlusion (which is right next to this in material controls and looked out of place if I inverted it) |
Calinou
left a comment
There was a problem hiding this comment.
Tested locally, it works as expected. Code looks good to me.
Testing project: test_ssao_multibounce.zip
For benchmarking: test_ssao_multibounce.zip
Preview
The default SSAO intensity is 2.0. By increasing it to a value around 3.0, you can get a more similar result to multi-bounce disabled on brighter materials, but this will make it stronger than before on darker materials.
| No multi-bounce | With multi-bounce | With multi-bounce + intensity 3.0 |
|---|---|---|
![]() |
![]() |
![]() |
| No multi-bounce | With multi-bounce | With multi-bounce + intensity 3.0 |
|---|---|---|
![]() |
![]() |
![]() |
| No multi-bounce | With multi-bounce | With multi-bounce + intensity 3.0 |
|---|---|---|
![]() |
![]() |
![]() |
Benchmark
Using the second testing project linked above, run with a release export template (production=yes lto=full). All the viewport's pixels are covered by opaque geometry and other rendering features are turned down to the minimum, so that we can measure and isolate material shading cost as much as possible.
Differences might be subtle, but all scenarios are GPU-limited, and I've made sure the difference is reproducible across multiple runs.
PC specifications
- CPU: AMD Ryzen 9 9950X3D
- GPU: NVIDIA GeForce RTX 5090
- RAM: 64 GB (2×32 GB DDR5-6000 CL30)
- SSD: Solidigm P44 Pro 2 TB
- OS: Linux (Fedora 43)
3840×2160 fullscreen
| Before | After |
|---|---|
| 2186 FPS (0.46 mspf) | 2166 FPS (0.46 mspf) |
7680×4320 fullscreen
| Before | After |
|---|---|
| 769 FPS (1.30 mspf) | 760 FPS (1.32 mspf) |
Co-authored-by: Hugo Locurcio <hugo.locurcio@hugo.pro>
(There is currently a bit of ongoing disagreement on this issue that I am waiting for a conclusion on before implementing it) |
Thank you for the benchmarks and feedback. On the subject of bright materials an abledo value approaching 0.9 is basically snow, using 1.0 would be creating a lab made super white surface. So the bounce lighting drowing out darkness closely mimicks ground truth. There is a common instinct among users to use 1.0 as just white plastic or concrete but a better PBR value for that kind of surface is actually in the 0.6-0.8 range and balancing the lighting or adjusting exposure. But for non-PBR aware or NPR games adjusting the AO intensity is an acceptable compromise. |














This patch implements a multi-bounce ambient occlusion approximation described in this 2016 Siggraph presentation that has since become widely adopted as an industry standard bounce approximation across other engines.
This approximation is applied before integrating ambient occlusion into indirect lighting. The bounce approximation affects both real-time (single bounce SSAO) and baked ambient occlusion (most often single bounce baked textures) which only stores monochromatic occlusion data. Currently this monochromatic data is directly multiplied onto color lighting can lead to over-darkening and produce artistically unpleasant gradients of color.
By bringing back some albedo data weighted along the occlusion visibility the appearance of local global illumination can be created (bringing SSAO closer to SSIL) while also creating a more artistically pleasing ambient gradient on saturated surfaces. (where otherwise directly multiplying monochromatic information on color can create sickly-grey colors)
Disabled:

Enabled:

This was proposed in godotengine/godot-proposals#13928.