Energy conservation in RE_Direct_Physical

I’ve been digging deep into the rendering equations used for the physical material (lights_physical_pars_fragment.glsl.js).

What I don’t understand is why the same irradiance is used for both the specular and diffuse reflection in RE_Direct_Physical.

reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );
reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );

From my understanding, only the light that wasn’t reflected from the surface (reflected specularly) should enter the material where it reflects diffusely. This split (amount reflected and the amount entering) should be governed by fresnel equations (based on IOR and angle). It’s being accounted for in directSpecular (as part of BRDF_GGX), but not when directDiffuse is being calculated.

What I think should be happening is something more like this:

vec3 F = F_Schlick( f0, f90, dotNL );
reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );
reflectedLight.directDiffuse += (1 - F) * irradiance * BRDF_Lambert( material.diffuseColor );

Is there a misunderstanding in my thinking? Or is this split being taken into account in some other part of the code? If it’s not and my thinking is correct, then this is not conserving energy as more light is reflected than it’s entering.

EDIT: I’m guessing it has something to do with the microsurface distribution of normals … I wonder if I can use dotNL above to calculate F. Inside BRDF_GGX dotVH is used since only microfacets facing H will reflect light to the viewer. I guess microsurface distribution has to affect how much light enters the material, which I’m not accounting above either.

2 Likes

You are correct, diffuse BRDF should include some fresnel factor.
Disney BRDF specifies the equation for this but is generally more performance-expensive, with no significant quality gains (compared to lambert). Although, I cannot say for sure that three.js doesn’t use it for this reason.
It’s pretty simple to extend the physical material to use disney diffuse BRDF.

float fd_Burley(float NoV, float NoL, float LoH, float roughness) {
    float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
    float lightScatter = F_Schlick(NoL, 1.0, f90);
    float viewScatter = F_Schlick(NoV, 1.0, f90);
    return lightScatter * viewScatter * (1.0 / PI);
}

Just multiply the value from this function to irradiance and diffuseColor.

Although, even after this, you cannot assume that energy will be perfectly conserved. You can read more about this in section 4.5 and 4.7 of the amazing Physically based rendering in Filament The above function is also taken from section 4.5.

5 Likes

Thank you so much, this is very helpful (so I wasn’t going crazy)!

I’ll see if I have any other questions. I was thinking of implementing Oren-Nayar model for the diffuse part and see what results I get. But I first wanted to be sure I’m even sending the right portion of irradiance into the diffuse part.

Related issue at GitHub:

Please don’t be confused since the issue is closed but the feature was never implemented.

2 Likes

@Mugen87 do you know why that issue was closed without any comment as to why? Seems like it would be a good enhancement to look into at some point.

I’m afraid I don’t.

1 Like