Is IBL compression causing blowout?

So I’m working on a model viewing application that uses PBR and IBL. Our content is authored in Substance Painter so we’re trying to get PlayCanvas to look as close as reasonably possible. Unfortunately I think I’ve hit a limit.

This is what we get in Painter:

This is what we get in PlayCanvas: ((As a new user I can only post one image. Please look at the editor, but I’ll try to post it in the comments))

There are a couple of things going on. The first is that PlayCanvas is implementing a mipmapping on the reflection. This test scene is just an array of spheres showing metallic and roughness from 0 to 1. The chrome balls on the top left actually look less chrome than the spheres to their right because of this mipping. If you zoom in the sphere renders correctly. So my first question is: can I turn this mip mapping off?

The next issue is the lighting. You’ll notice that the spheres on the right side are not as visually distinct as they are in painter. I was messing around with settings and eventually got this result: ((As a new user I can only post one image. Here you’ll see that the spheres correctly reflect their metal/rough values if you go into the editor, turn off the environment cube map and turn on the directional light))

This is an image with the environment cube map disabled, but a directional light pointing from the same direction as the sun in the hdr image. Despite some artifacts, the spheres show correct reflections based on their metal and roughness values. This has lead me to believe that something is off in how IBL is calculating how bright something should be.

So based on this I started digging into the code and discovered that PlayCanvas is converting .exr images into pngs and encoding the color values in the alpha channel. On decoding these values in the rgbm.frag it’s multiplying the alpha by 8 and squaring the color. I’ve noticed that this blowout effect doesn’t happen on all hdrs. Maybe it only affects HDRs with a max value of 8? The exr image I’m using has a max value of around 2700. Unfortunately if I tone down the exr image the hot spot gets better but the rest of the image is darker.

Any thoughts on how I can remove this blowout? For comparison I checked another online viewing application, sketchfab, and it was able to use the same .exr file without blowout, so I don’t believe this is a limitation with WebGL. It may however be a limitation of RGBM.

Here is the editor scene:

This is the same scene with the environment cube map disabled and a single directional light turned on.

This is from play canvas with an environment cube map. Notice how we don’t see as much variation in the materials as we do from painter.

This was a test in sketchfab, another web based model viewer. Its results are comparable to painter showing WebGL can give accurate results.

Sounds like one for @Mr_F to me…

OK, so PlayCanvas implementation differs quite a bit.

  • Our diffuse lighting isn’t dependent on roughness (simple Lambert). That may be perhaps different in Substance/Sketchfab.
  • There might be an error in roughness->miplevel mapping :sweat:

Ideally I should reevaluate and double-check everything. That definitely can be matched to your other examples, BUT there’s a problem: if we suddenly change ways glossiness affects materials, all existing projects will break. We can also automatically convert all projects to use new mapping, but people can also set values in code, and that can’t be converted.
Probably the best option for us would be to create a new “Physical” material, while leaving current as “Physical (legacy)” or something.

@Mr_F so the corrent roughness -> miplevel

maybe ??

float getSpecularMIPLevel( const in float blinnShininessExponent, const in float maxMIPLevelScalar) {

    //float envMapWidth = pow( 2.0, maxMIPLevelScalar );
    //float desiredMIPLevel = log2( envMapWidth * sqrt( 3.0 ) ) - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );

    float desiredMIPLevel = maxMIPLevelScalar - 0.79248 - 0.5 * log2( blinnShininessExponent + 1.0 );

	// clamp to allowable LOD ranges.
	return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );



float linRoughnessToMipmap( float roughnessLinear )
    //return roughnessLinear;
    return sqrt(roughnessLinear);

vec3 prefilterEnvMap( float roughnessLinear, const in vec3 R )
    float lod = linRoughnessToMipmap(roughnessLinear) * uEnvironmentLodRange[1]; //( uEnvironmentMaxLod - 1.0 );
    return textureCubeLodEXTFixed( uEnvironmentCube, R, lod );

Thanks for pointing that out. I looked around and found prefilter-cubemap.js which looks to be where this implementation is contained. Is there anything else I might be missing or would want to look into to tackle this specific problem?

hey @amontville_lil. @Mr_F

compared to Sketchfab, two differences there in my watching…

  1. the top-left sphere -> less chrome than the right ones, because the mipmap problem implemented in ‘reflectionPrefilteredCube.frag’ and I tried to use the ‘reflectionPrefilteredCubeLod.frag’ and then it looks better…

  2. the right-top sphere looks more darker in sketchfab and the variations from left to right are more obvious…
    I guess it’s because playcanvas are missing intregrateBRDF part for IBL-Split-Sum (basically just consider the
    prefiltering) Missing IntegrateBRDF part in IBL?