Explorar o código

Shaders: Fix multi-scattering energy compensation, remove BRDF_GGX_Multiscatter. (#33983)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
mrdoob hai 1 semana
pai
achega
23e9b0abd1

+ 0 - 52
src/nodes/functions/BSDF/BRDF_GGX_Multiscatter.js

@@ -1,52 +0,0 @@
-import BRDF_GGX from './BRDF_GGX.js';
-import DFGLUT from './DFGLUT.js';
-import { normalView } from '../../accessors/Normal.js';
-import { positionViewDirection } from '../../accessors/Position.js';
-import { EPSILON } from '../../math/MathNode.js';
-import { Fn, float } from '../../tsl/TSLBase.js';
-
-// GGX BRDF with multi-scattering energy compensation for direct lighting
-// This provides more accurate energy conservation, especially for rough materials
-// Based on "Practical Multiple Scattering Compensation for Microfacet Models"
-// https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf
-const BRDF_GGX_Multiscatter = /*@__PURE__*/ Fn( ( { lightDirection, viewDirection = positionViewDirection, f0, f90, roughness: _roughness, f, USE_IRIDESCENCE, USE_ANISOTROPY } ) => {
-
-	// Single-scattering BRDF (standard GGX)
-	const singleScatter = BRDF_GGX( { lightDirection, viewDirection, f0, f90, roughness: _roughness, f, USE_IRIDESCENCE, USE_ANISOTROPY } );
-
-	// Multi-scattering compensation
-	const dotNL = normalView.dot( lightDirection ).clamp();
-	const dotNV = normalView.dot( viewDirection ).clamp();
-
-	// Precomputed DFG values for view and light directions
-	const dfgV = DFGLUT( { roughness: _roughness, dotNV } );
-	const dfgL = DFGLUT( { roughness: _roughness, dotNV: dotNL } );
-
-	// Single-scattering energy for view and light
-	const FssEss_V = f0.mul( dfgV.x ).add( f90.mul( dfgV.y ) );
-	const FssEss_L = f0.mul( dfgL.x ).add( f90.mul( dfgL.y ) );
-
-	const Ess_V = dfgV.x.add( dfgV.y );
-	const Ess_L = dfgL.x.add( dfgL.y );
-
-	// Energy lost to multiple scattering
-	const Ems_V = float( 1.0 ).sub( Ess_V );
-	const Ems_L = float( 1.0 ).sub( Ess_L );
-
-	// Average Fresnel reflectance
-	const Favg = f0.add( f0.oneMinus().mul( 0.047619 ) ); // 1/21
-
-	// Multiple scattering contribution
-	// Uses geometric mean of view and light contributions for better energy distribution
-	const Fms = FssEss_V.mul( FssEss_L ).mul( Favg ).div( float( 1.0 ).sub( Ems_V.mul( Ems_L ).mul( Favg ).mul( Favg ) ).add( EPSILON ) );
-
-	// Energy compensation factor
-	const compensationFactor = Ems_V.mul( Ems_L );
-
-	const multiScatter = Fms.mul( compensationFactor );
-
-	return singleScatter.add( multiScatter );
-
-} );
-
-export default BRDF_GGX_Multiscatter;

+ 15 - 4
src/nodes/functions/PhysicalLightingModel.js

@@ -1,6 +1,5 @@
 import BRDF_Lambert from './BSDF/BRDF_Lambert.js';
 import BRDF_GGX from './BSDF/BRDF_GGX.js';
-import BRDF_GGX_Multiscatter from './BSDF/BRDF_GGX_Multiscatter.js';
 import DFGLUT from './BSDF/DFGLUT.js';
 import EnvironmentBRDF from './BSDF/EnvironmentBRDF.js';
 import F_Schlick from './BSDF/F_Schlick.js';
@@ -632,20 +631,32 @@ class PhysicalLightingModel extends LightingModel {
 
 		reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseContribution } ) ) );
 
-		let specularBRDF = BRDF_GGX_Multiscatter( { lightDirection, f0: specularColorBlended, f90: 1, roughness, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } );
+		let specularBRDF = BRDF_GGX( { lightDirection, f0: specularColorBlended, f90: 1, roughness, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } );
 
 		if ( this.retroreflective === true ) {
 
 			// Minimal Retroreflective Microfacet Model:
 			// https://jcgt.org/published/0015/01/04/
 			const retroViewDirection = positionViewDirection.negate().reflect( normalView );
-			const retroSpecularBRDF = BRDF_GGX_Multiscatter( { lightDirection, viewDirection: retroViewDirection, f0: specularColorBlended, f90: 1, roughness, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } );
+			const retroSpecularBRDF = BRDF_GGX( { lightDirection, viewDirection: retroViewDirection, f0: specularColorBlended, f90: 1, roughness, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } );
 
 			specularBRDF = mix( specularBRDF, retroSpecularBRDF, retroreflective.clamp() );
 
 		}
 
-		reflectedLight.directSpecular.addAssign( irradiance.mul( specularBRDF ) );
+		// 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
+		const dotNV = normalView.dot( positionViewDirection ).clamp();
+		const fab = DFGLUT( { roughness, dotNV } );
+
+		// Energy of the single-scattering lobe in a white furnace ( F0 = F90 = 1 )
+		const Ess = fab.x.add( fab.y );
+
+		// Compensate for the energy lost to multiple scattering, tinting the added term by F0 ( equation 16 )
+		const energyCompensation = specularColorBlended.mul( Ess.reciprocal().sub( 1.0 ) ).add( 1.0 );
+
+		reflectedLight.directSpecular.addAssign( irradiance.mul( specularBRDF ).mul( energyCompensation ) );
 
 	}
 

+ 16 - 45
src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js

@@ -422,48 +422,6 @@ 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 = texture2D( dfgLUT, vec2( material.roughness, dotNV ) ).rg;
-	vec2 dfgL = texture2D( dfgLUT, vec2( material.roughness, dotNL ) ).rg;
-
-	// Single-scattering energy for view and light
-	vec3 FssEss_V = material.specularColorBlended * dfgV.x + material.specularF90 * dfgV.y;
-	vec3 FssEss_L = material.specularColorBlended * 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.specularColorBlended + ( 1.0 - material.specularColorBlended ) * 0.047619; // 1/21
-
-	// Multiple scattering contribution
-	vec3 Fms = FssEss_V * FssEss_L * Favg / ( 1.0 - Ems_V * Ems_L * 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 ) {
@@ -557,20 +515,33 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geome
  
  	#endif
 
-	vec3 specularBRDF = BRDF_GGX_Multiscatter( directLight.direction, geometryViewDir, geometryNormal, material );
+	vec3 specularBRDF = BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );
 
 	#ifdef USE_RETROREFLECTIVE
 
 		// Minimal Retroreflective Microfacet Model:
 		// https://jcgt.org/published/0015/01/04/
 		vec3 retroViewDir = reflect( - geometryViewDir, geometryNormal );
-		vec3 retroSpecularBRDF = BRDF_GGX_Multiscatter( directLight.direction, retroViewDir, geometryNormal, material );
+		vec3 retroSpecularBRDF = BRDF_GGX( directLight.direction, retroViewDir, geometryNormal, material );
 
 		specularBRDF = mix( specularBRDF, retroSpecularBRDF, saturate( material.retroreflective ) );
 
 	#endif
 
-	reflectedLight.directSpecular += irradiance * specularBRDF;
+	// 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
+	float dotNV = saturate( dot( geometryNormal, geometryViewDir ) );
+
+	vec2 fab = texture2D( dfgLUT, vec2( material.roughness, dotNV ) ).rg;
+
+	// Energy of the single-scattering lobe in a white furnace ( F0 = F90 = 1 )
+	float Ess = fab.x + fab.y;
+
+	// Compensate for the energy lost to multiple scattering, tinting the added term by F0 ( equation 16 )
+	vec3 energyCompensation = 1.0 + material.specularColorBlended * ( 1.0 / Ess - 1.0 );
+
+	reflectedLight.directSpecular += irradiance * specularBRDF * energyCompensation;
 
 	reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseContribution );
 }

粤ICP备19079148号