@@ -426,6 +426,48 @@ void computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const
426426
427427}
428428
429+ // GGX BRDF with multi-scattering energy compensation for direct lighting
430+ // Based on "Practical Multiple Scattering Compensation for Microfacet Models"
431+ // https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf
432+ vec3 BRDF_GGX_Multiscatter( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {
433+
434+ // Single-scattering BRDF (standard GGX)
435+ vec3 singleScatter = BRDF_GGX( lightDir, viewDir, normal, material );
436+
437+ // Multi-scattering compensation
438+ float dotNL = saturate( dot( normal, lightDir ) );
439+ float dotNV = saturate( dot( normal, viewDir ) );
440+
441+ // Precomputed DFG values for view and light directions
442+ vec2 dfgV = DFGApprox( vec3(0.0, 0.0, 1.0), vec3(sqrt(1.0 - dotNV * dotNV), 0.0, dotNV), material.roughness );
443+ vec2 dfgL = DFGApprox( vec3(0.0, 0.0, 1.0), vec3(sqrt(1.0 - dotNL * dotNL), 0.0, dotNL), material.roughness );
444+
445+ // Single-scattering energy for view and light
446+ vec3 FssEss_V = material.specularColor * dfgV.x + material.specularF90 * dfgV.y;
447+ vec3 FssEss_L = material.specularColor * dfgL.x + material.specularF90 * dfgL.y;
448+
449+ float Ess_V = dfgV.x + dfgV.y;
450+ float Ess_L = dfgL.x + dfgL.y;
451+
452+ // Energy lost to multiple scattering
453+ float Ems_V = 1.0 - Ess_V;
454+ float Ems_L = 1.0 - Ess_L;
455+
456+ // Average Fresnel reflectance
457+ vec3 Favg = material.specularColor + ( 1.0 - material.specularColor ) * 0.047619; // 1/21
458+
459+ // Multiple scattering contribution
460+ vec3 Fms = FssEss_V * FssEss_L * Favg / ( 1.0 - Ems_V * Ems_L * Favg * Favg + EPSILON );
461+
462+ // Energy compensation factor
463+ float compensationFactor = Ems_V * Ems_L;
464+
465+ vec3 multiScatter = Fms * compensationFactor;
466+
467+ return singleScatter + multiScatter;
468+
469+ }
470+
429471#if NUM_RECT_AREA_LIGHTS > 0
430472
431473 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
490532
491533 #endif
492534
493- reflectedLight.directSpecular += irradiance * BRDF_GGX ( directLight.direction, geometryViewDir, geometryNormal, material );
535+ reflectedLight.directSpecular += irradiance * BRDF_GGX_Multiscatter ( directLight.direction, geometryViewDir, geometryNormal, material );
494536
495537 reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );
496538}
0 commit comments