diff --git a/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js index 77a94079ab1b5b..330e502c8fe783 100644 --- a/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js @@ -426,6 +426,48 @@ void computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const } +// GGX BRDF with multi-scattering energy compensation for direct lighting +// Based on "Practical Multiple Scattering Compensation for Microfacet Models" +// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf +vec3 BRDF_GGX_Multiscatter( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) { + + // Single-scattering BRDF (standard GGX) + vec3 singleScatter = BRDF_GGX( lightDir, viewDir, normal, material ); + + // Multi-scattering compensation + float dotNL = saturate( dot( normal, lightDir ) ); + float dotNV = saturate( dot( normal, viewDir ) ); + + // Precomputed DFG values for view and light directions + vec2 dfgV = DFGApprox( vec3(0.0, 0.0, 1.0), vec3(sqrt(1.0 - dotNV * dotNV), 0.0, dotNV), material.roughness ); + vec2 dfgL = DFGApprox( vec3(0.0, 0.0, 1.0), vec3(sqrt(1.0 - dotNL * dotNL), 0.0, dotNL), material.roughness ); + + // Single-scattering energy for view and light + vec3 FssEss_V = material.specularColor * dfgV.x + material.specularF90 * dfgV.y; + vec3 FssEss_L = material.specularColor * dfgL.x + material.specularF90 * dfgL.y; + + float Ess_V = dfgV.x + dfgV.y; + float Ess_L = dfgL.x + dfgL.y; + + // Energy lost to multiple scattering + float Ems_V = 1.0 - Ess_V; + float Ems_L = 1.0 - Ess_L; + + // Average Fresnel reflectance + vec3 Favg = material.specularColor + ( 1.0 - material.specularColor ) * 0.047619; // 1/21 + + // Multiple scattering contribution + vec3 Fms = FssEss_V * FssEss_L * Favg / ( 1.0 - Ems_V * Ems_L * Favg * Favg + EPSILON ); + + // Energy compensation factor + float compensationFactor = Ems_V * Ems_L; + + vec3 multiScatter = Fms * compensationFactor; + + return singleScatter + multiScatter; + +} + #if NUM_RECT_AREA_LIGHTS > 0 void RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) { @@ -490,7 +532,7 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geome #endif - reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material ); + reflectedLight.directSpecular += irradiance * BRDF_GGX_Multiscatter( directLight.direction, geometryViewDir, geometryNormal, material ); reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); }