GLTF AO Strenght

I and @Ali_RS had a little talk about ao strength in gltf.
so what about the ao multiplier i remember it wasn’t straight like assigning metallic mult for example
can it be done for ao too i can’t see ao too in def
also didn’t even notice about ao map multiplier.
Looks like gltf supports “strenght” factor for ao map. .
so does it need to implement in the shader below:

and so far i found this :smiley: nothing related to our problem but they had a bug that when the multiplier sets to 0 rather than no ambient effect object goes fully dark, maybe rather zero put 0.0001 for ex so no zero
GLTF ao strength is applied directly as a scale factor (0 = black AO) · Issue #6106 · google/filament · GitHub maybe we can run into this problem too just saying :smiley:

so my suggestion is:
Read from gltf then multiply:

uniform float m_AmbientOcclusionStrength;

#ifdef AO_MAP
   vec3 ao = texture2D(m_AmbientOcclusionMap, texCoord).rgb;

ao *= m_AmbientOcclusionStrength;

and this into the j3md

MaterialParameters {

    Float AmbientOcclusionStrength : 1.0 //or read from gltf

according to ali this is the gltf specs for occulusion strength.

1 Like

The question is where the occlusion strength factor should be applied.

Are lightmap and ambient occlusion the same?

Should it be applied to this:

or these too

according to specs the formula is

occludedColor = lerp(color, color * <sampled occlusion texture value>, <occlusion strength>)

if someone knows how the occlusion strength should be applied in the shader please let me know.


I think they are different, lightMap is usually used for baked lighting and can have color, but AO maps get read as a grayscale texture and are just used to simulate crevices on a model where less natural light reaches.

It seems like the PBR shader was written so they both can use the same texture because there would never be a good reason to use both together, since you might as well just combine the ambient occlusion lighting into the lightmap to do 1 texture read instead of 2.

But I’m pretty sure that this is how you would want to change the PBR shader to use AO strength, just multiply AOStrength by the final AO value right after its retrieved from the lightmap or metallicRoughnessMap texture reads.

    vec3 ao = vec3(1.0); 

    #ifdef LIGHTMAP
      // ...lightmap / ao code here...

    #if defined(AO_PACKED_IN_MR_MAP) && defined(USE_PACKED_MR)
       ao = aoRoughnessMetallicValue.rrr;

    ao *= m_AoStrength;

And then I think that if anyone needs to scale their lightmaps, then a seperate m_LightMapStrength variable should be created to do that, since it is technically possible to use a LightMap while also using AmbientOcclusion if the AO value is coming from the packed MeatllicRoughness map.


Thanks @yaRnMcDonuts

I submitted a PR based on that

Sorry, this was an outdated version. Here is the latest version for the specs:

so based on it I guess it should be

ao = 1.0 + (strength * (ao - 1.0))


but in my PR I am just doing ao *= strength as suggested by @yaRnMcDonuts.

I am pinging @RiccardoBlb and @zzuegg just in case if they want to comment on this.


Yeah I think this way is actually the right way to do it for a strength factor for AO, since a higher strength should mean that the dark parts of the AO map get darker, resulting in more intensity and contrast in the AO map.

The way I suggested with multiplication was wrong since it would just be a scale factor and would make the whole model brighter if > 1 and all darker if < 1, and that isn’t what you’d expect from an AO strength factor.


Ok fixed it in this commit


what about this:

ao = pow(ao, m_AoStrength);

if ao is 1 and m_AoStrength is 2, then ao=max(ao,0.0) would give ao = 2, which doesn’t make sense. am i wrong?
this keeps ao between 0 and 1 for any value of m_AoStrength

But if that’s the only goal, aren’t there waaaaaay cheaper ways to do it than pow?

clamp() comes to mind.

1 Like

you mean like this:

ao = clamp(pow(ao, m_AoStrength), 0.0, 1.0);

pow is a common way to control contrast of image, is not much expensive tho, if we use power function correctly we won’t need use clamp since for any positive value of m_aostrenght ao will be 0-1

I thought you were only using pow() to keep the value between 0 and 1. I realize now that you were just calling it a positive side effect.

Nope, it will give 1 :slightly_smiling_face:

1 + 2 * (1 - 1) = 1

According to gltf specs the formula is

`1.0 + strength * (<sampled occlusion texture value> - 1.0)`

so not sure why you mentioned using pow.

Also again according to gltf specs occlusion strength must be in [0, 1] in which case the calculated ao result would be in [0, 1].

But if someone provides a value for ao strength that is outside of the range I added a sanity check to constraint it as proposed.

 ao = clamp(ao, 0.0, 1.0)

i mean how much difference there is between the dark and light areas of the ambient occlusion map a higher contrast means more visible shadows and creases on the model a lower contrast means more subtle and smooth shading on the model
the logic you use to scale the ambient occlusion effect can affect the contrast of the ao map ex, if you use a linear logic like this:

ao = 1.0 + m_AoStrength * (ao - 1.0);

then you are basically adding or subtracting a constant value from ao depending on m_AoStrength this will change the brightness of the ao map but not the contrast.

however, if you use a nonlinear logic like this:

ao = pow(ao, m_AoStrength);

then you are raising ao to a power depending on m_AoStrength this will change both the brightness and the contrast of the ao map

i’m completely in a wrong understanding of situation…

1 Like

TBO, I do not know much about this subject really to be able to comment. I am just doing this based on the gltf specs which could be wrong, Idk.

Maybe @RiccardoBlb can help, afaik he has a much better understanding of the subject.

1 Like

I mean, if the spec is clear on this then why would we do it differently?

Unless we can show that every other tool also ignores the spec then I don’t think it’s a good idea.

1 Like

IMO in most cases, adjusting the ambient occlusion from the code after the model is exported is unnecessary. However, if the specifications require it, we should support it as defined to better integrate with tools that expect a certain behavior from the exported models.

1 Like