Skip to content

Conversation

mrdoob
Copy link
Owner

@mrdoob mrdoob commented Oct 16, 2025

Description

This PR enhances the ambient occlusion implementation in physical materials with two improvements:

1. Multi-bounce AO approximation

  • Adds energy back that bounces in occluded areas
  • Prevents overly dark AO, especially on bright/colored surfaces
  • Uses a conservative 50/50 blend to maintain AO visibility while adding subtle brightening

2. Improved specular occlusion with horizon fading

  • Reduces dark halos at grazing angles on object edges
  • Builds on existing specular occlusion formula with subtle horizon fade
  • Only reduces occlusion by ~30% at extreme grazing angles

These changes make AO integration more physically accurate while maintaining the overall occlusion effect. The improvements are most noticeable on models with baked AO maps, particularly on bright materials and at object edges.

For comparison:

https://raw.githack.com/mrdoob/three.js/dev/examples/index.html#webgl_animation_keyframes
https://raw.githack.com/mrdoob/three.js/multibounce-ao/examples/index.html#webgl_animation_keyframes

Before After Diff
image image image

https://raw.githack.com/mrdoob/three.js/dev/examples/index.html?q=gltf#webgl_loader_gltf_compressed
https://raw.githack.com/mrdoob/three.js/multibounce-ao/examples/index.html?q=gltf#webgl_loader_gltf_compressed

Before After Diff
image image image

https://raw.githack.com/mrdoob/three.js/dev/examples/index.html#webgl_random_uv
https://raw.githack.com/mrdoob/three.js/multibounce-ao/examples/index.html#webgl_random_uv

Before After Diff
image image image

@mrdoob mrdoob added this to the r181 milestone Oct 16, 2025
Copy link

github-actions bot commented Oct 16, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 349.91
84.78
350.68
85
+771 B
+215 B
WebGPU 591.36
163.34
591.36
163.34
+0 B
+0 B
WebGPU Nodes 589.97
163.09
589.97
163.09
+0 B
+0 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 481.57
119.56
482.34
119.78
+771 B
+221 B
WebGPU 660.79
178.69
660.79
178.69
+0 B
+0 B
WebGPU Nodes 614.79
167.87
614.79
167.87
+0 B
+0 B

@mrdoob mrdoob requested a review from WestLangley October 16, 2025 09:43
@FrostKiwi
Copy link

FrostKiwi commented Oct 17, 2025

Practical Real-Time Strategies for Accurate Indirect Occlusion Was a good read, thx for the reference.

I work on the machine configuration myMachine.dmgmori.com with extensive use of baked AO absolutely everywhere.

Can there be a toggle for this? Or a can the Energy mix in the shader become a uniform controllable via JS through Material Properties?

Before the PR, I also noticed AO being stronger than expected, especially on metals. With this PR the swings quite hard the other way though, makes interior surfaces under oblique angles glow, with the environment coming through strong for surfaces which were very AO Shaded before, AO baked lighting didn't make it into before. So with this change I would have to setup an extra environment probe for machine insides to avoid this.

Inside

Before After Diff
set1 set2 set_DIFF

Notice the glow on back wall and oblique angle panels of the spindles.

Outside

Before After Diff
outside1 outside2 outside_DIFF

Panel Gaps at oblique angles

Before After
image image

Panel gaps, which were previously fully occluded by their panel surroundings now start to glow, as they "reclaim" that energy by virtue of being seen at an oblique angle

vec3 multiBounce = ( ( x * a + b ) * x + c ) * x;
// Blend between simple AO and multi-bounce (50/50) to keep it conservative
return mix( vec3( x ), max( vec3( x ), multiBounce ), 0.5 );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love for this 50% to be tweak-able uniform. or the whole computeSpecularOcclusionImproved to be a glsl fragment in-off itself, so it could be dynamically disabled, similar to how Tone-Mapping is a dynamically included or excluded glsl fragment.

@mrdoob
Copy link
Owner Author

mrdoob commented Oct 17, 2025

@FrostKiwi

Thanks for testing!

Can you check if #32073 works better for you?

@mrdoob mrdoob removed the request for review from WestLangley October 17, 2025 07:55
@FrostKiwi
Copy link

FrostKiwi commented Oct 17, 2025

@FrostKiwi

Thanks for testing!

Can you check if #32073 works better for you?

@mrdoob
They all behave pretty differently, Screenshots don't do it justice. You gotta move the camera around to see the interaction with specular highlights. I setup a 3-Way comparison. Open up three browser Tabs, select one of the Machine Option Category tabs, which sets up a defined camera and flick between all three-three^^

In #32073 Because of how AO blends, it appears like a strong contrast boost! Try looking in Working Space --> Right Spindle. Also switch to a tail stock, as these have a comparison between dark and bright metals. On the bright side, the Oblique angles aren't "lightleak" like bright anymore heh.

v0.180.0 multibounce-ao multibounce-ao-specular
image image image

Without oblique angles, the AO is pitch black, where light was before. But at oblique angles the deletion is stronger --> "Contrast boost" effect.

v0.180.0 multibounce-ao multibounce-ao-specular
image image image

Also, Metal with AO + Very Oblique angle + multibounce-ao-specular = pitch black again.

v0.180.0 multibounce-ao multibounce-ao-specular
image image image

Compared to v0.180.0, multibounce-ao-specular tends to influence AO more and create a discontinuity, making AO change based on viewing angle. This was also previously the case, but now it "appears like the shadow not being applied correclty" at certain angles more often than not.

Here Occlusion is the the same on both panels, but due to angle changes they appear "discontinuous". This was totally the case before as well, reflections overpowering AO (as they should), but now it happens more often, or maybe the darkening is coming through stronger on one panel than the other, more often than not. It's all a bit vague, Asset dependent. Direct browser comparisons make this a bit more clear.

v0.180.0 multibounce-ao-specular
image image

@mrdoob
Copy link
Owner Author

mrdoob commented Oct 17, 2025

Super useful! Thanks! 🙏

Damn, we sure lack good examples that test all this...

@FrostKiwi
Copy link

FrostKiwi commented Oct 17, 2025

My fav JS library, so Happy to help!

These test are of course scene, asset and use-case dependent and I don't want to overrule any shading changes with my use-case, but I think I can nail down a general statement for these PRs:
If a multibounce-ao like path is chosen, then it has to remain an artistic choice as to whether or not pitch Black AO has the ability to override all environment light contribution.
In v0.180.0 and multibounce-ao-specular, Pitch black AO multiplies away any environment light contribution. In multibounce-ao, it does not.

Here is the pure AO data, notice the pitch black corner and pitch black under side of the machine. There is no light under the machine, no light in that dark corner, but it glows. Not seen in the screenshot, this glow changes you move the camera.

Pure AO Data v0.180.0 multibounce-ao
image image image
Pure AO Data v0.180.0 multibounce-ao
image image image

In Meta Quest WebXR (not public yet) this especially looks weird, as one eye can sees a corner as darker than another.
You can gamma curve tune the underlying asset to lean one way or the other in how strong AO is, but you wouldn't be able to tune that change away, the artistic freedom to make dark room corners not glow should remain. Makes me think of older shooter having physics Props sticking out in Dark levels by glowing and not respecting the darkness of the room.

@mrdoob
Copy link
Owner Author

mrdoob commented Oct 17, 2025

Thank you!

Any chance you could do another ?threejs= version of the site using the PathTracer?
https://github.com/gkjohnson/three-gpu-pathtracer

@mrdoob
Copy link
Owner Author

mrdoob commented Oct 18, 2025

Oh, but I wonder what does the PathTracer do with the AO baked in the texture...

@gkjohnson Does it ignore it? Or does it take into account when rendering?

@FrostKiwi
Copy link

Physically correct would be without
Or rather I wouldn't want to mix both things. 🤔

@gkjohnson
Copy link
Collaborator

Does it ignore it? Or does it take into account when rendering?

Yeah AO maps are ignored in the path tracer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants