Ambient Occlusion wrong with Khronos Neutral Tone Mapper

(using Three.js r166)
I’m looking into the new Khronos Neutral Tone Mapper as I want to convert from using Linear.
However I noticed when applying an Ambient Occlusion the ‘shadowing’ had almost a negative affect where it is white instead of dark
Here is the linear with an ambient intensity of 20

and here is the same but with the Neutral tone mapper

Not sure if this is a bug with Three.js, the tone mapper code, or potentially something wrong with my implementation?

Textures are from Textures for 3D, graphic design and Photoshop!

Linear:


NeutralToneMapping:

https://www.textures.com/download/3DScans0419/133276

Are you applying tonemapping via the renderer or as a post process?

If you’re going to use post processing effects, like AO, i think the optimal setup is to do the tonemapping as a final pass in the post processing phase, instead of on the renderer itself.

Since the EffectComposer is an external class, the renderer doesn’t know about it, so the renderer will tonemap only the output of the renderer.render pass, which will then pass on the tonemapped (incorrect) data to the effect composer.
Instead I think you want to disable tonemapping on the renderer and do it in the effectcomposer instead, as a final pass after all the other effects have run.

Would it be possible to share a demo? I’m wondering if some negative values might have gotten into the render process somewhere, but it’s hard to say without a demo. Do you see similar issues with any other tone mapping?

three.js ignores renderer.toneMapping when drawing to a render target. You’d need the explicit ToneMappingPass or OutputPass (or a custom shader chunk), to apply tone mapping in post processing.

Hey sure thing, I put up a demo here:
https://shanemonck.azurewebsites.net/tone-mapping

and the code can be found here

Looks like it behaves correctly for all tone mappings except Neutral and ACESFilmic which is interesting.

To understand this it may help to look at how ambient occlusion is defined, either in the three.js codebase …

… or in the glTF specification for material.occlusionTextureInfo.strength.

An .aoMapIntensity value of 1.0 means that all ambient light is fully occluded; anything higher than 1.0 may negate the contribution of the light. Once the render produces negative RGB values, the result is likely to vary by tone mapping. The glTF spec prohibits values ambient occlusion strengths >1.0, but I don’t see anything in the three.js docs one way or another, and perhaps that should be clarified. For your purposes it may be best to keep the value <= 1.0.

Oops I never realised that, thanks Don that makes sense now, cheers!!

1 Like

What does a value of 20 for ambient occlusion represent? Is this supposed to be an artistic number or more like 0-100% ?

From three.js perspective 0–1 is the semantically meaningful range of the property; combined with the value in the red channel of .aoMap it determines the fraction of ambient light occluded:

float ao = aoMap.r ⨉ aoMapIntensity

Should this be clamped somewhere?

Perhaps, but I’m not sure. Will add a note in the docs at least.

2 Likes