PBR Material - Specular Workflow - Energy Conservation


in the source code of the pbr.fragment.fx shader that does the rendering of the PBRMaterial, I have seen that energy conservation is applied to the diffuse albedo with the following code (when using the specular workflow):

surfaceAlbedo.rgb = (1. - reflectance) * surfaceAlbedo.rgb;


float reflectance = max(max(surfaceReflectivityColor.r, surfaceReflectivityColor.g), surfaceReflectivityColor.b);

This means that the diffuse albedo is reduced by the maximum channel of the specular albedo. I do agree that this ensures energy conservation, but I am wondering that the maximum channel is used, instead of treating each channel separately (treating each RGB-channel as light of a specific wavelength that does not interact with the others).
By doing it in the way, it is programmed in the code, will one channel of the specular albedo with a value of 1.0 take away the whole diffuse contribution. Furthermore, if I have a diffuse and a specular texture, where the sum of each pixel pair is <= 1.0, will this shading model further reduce my diffuse values.
For example will a diffuse albedo of (0.5, 0.5, 0.5) and a specular albedo of (0.5, 0.5, 0.5) result in final diffuse albedo of (0.25, 0.25, 0.25).

This seems to me a little bit too restrictive, although it is part of the glTF specification (glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness at master · KhronosGroup/glTF · GitHub).

Is there some point I am missing on this topic?
Is there a way in Babylon to deactivate this behaviour somehow?

After reading some publications about PBR, I noticed that a lot of times it is suggested to weight the diffuse albedo with the result of the Fresnel equation. Something like:

surfaceAlbedo.rgb = (vec3(1.0) - Fresnel(VdotH, F0)) * surfaceAlbedo.rgb;

This is the correct way of simulating light transport, isn’t it?
The Fresnel equations describe the light that gets reflected at the surface of an object, dependent on the viewing direction. The amount of light that does not get reflected at the surface, penetrates into the material, scatters around and gets reflected in a diffuse way thereafter.
The Babylon (glTF) shading is missing the dependency on the viewing direction.

One more note: For the project I am doing, I have to use the specular workflow. The metallic workflow is no option for me.

It would be a pleasure, if you share your thoughts on this topic with me.

Best regards

pinging @sebavan

I guess this part of the code is just to ensure the setup is more consistent with real life material. Basically a metallic (e.g. specular or reflective) material on one channel would also be metallic on the other one. It mostly prevents the inputs to be wrong.

I am not against another energy conservation method for the input under a flag in the PBRMaterial. The code is all on this line and might be easy to #ifdef : Babylon.js/pbr.fragment.fx at master · BabylonJS/Babylon.js · GitHub

Could you please share the sources you found for the fresnel ? it might also help to check deeper on this part.

I do not think it impacted a lot of users so far so feel free to make a PR to add your new mode.

I get the idea of ensuring a correct black diffuse albedo for metals.
However with some of the proposed values for metals this does not prohibit incorrect diffuse values (for example look at these values from the substance pbr guide The PBR Guide - Part 2 on Substance Academy), as not every metal value has one channel with a factor of 1.0.

I have to admit, I was a bit fast at saying “a lot of” resources suggest 1 - Fresnel. I strongly apologize for that.
The ones I found are:

If I can come up with more resources from the scientific realm, I will add them as well.

If you agree I would like to add two modes, one using the Fresnel for energy conservation and one “without” energy conservation that just sums together the diffuse and specular part. With the last one, the user/artist has to ensure the energy conservation, but it would be a tremendous help for my work.

There are some other topics on my desk at the moment, but I will try to create a PR as fast as I can.

Thank you very much for your support!

Sounds totally reasonnable , but please also note that our diffuse term is using the Burley equations which despite being fully energy conservative account for on incoming and outgoing fresnel.

// Disney diffuse term
// https://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
// Page 14
float diffuseBRDF_Burley(float NdotL, float NdotV, float VdotH, float roughness) {
    // Diffuse fresnel falloff as per Disney principled BRDF, and in the spirit of
    // of general coupled diffuse/specular models e.g. Ashikhmin Shirley.
    float diffuseFresnelNV = pow5(saturateEps(1.0 - NdotL));
    float diffuseFresnelNL = pow5(saturateEps(1.0 - NdotV));
    float diffuseFresnel90 = 0.5 + 2.0 * VdotH * VdotH * roughness;
    float fresnel =
        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNL) *
        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNV);

    return fresnel / PI;

Thanks for the PR it is totally OK to have it. You could place your flag in the pbrBRDFConfiguration file. This is where we are currently handling the BRDF configuration.

A flag like useSpecularGlossinessInputEnergyConservation would totally do.