@@ -3,94 +3,82 @@ export const multiscatter_functions = /* glsl */`
33// Multiscattering energy compensation for GGX microfacet BRDF
44// Based on Kulla & Conty 2017 - "Revisiting Physically Based Shading at Imageworks"
55// https://blog.selfshadow.com/publications/s2017-shading-course/imageworks/s2017_pbs_imageworks_slides_v2.pdf
6+ //
7+ // Simplified analytical approximation using Turquin 2019 formulas
8+ // "Practical multiple scattering compensation for microfacet models"
9+ // https://blog.selfshadow.com/publications/turquin/ms_comp_final.pdf
610
7- // Computes the directional albedo E(mu, roughness) for GGX
8- // This represents the total energy reflected for a given view angle and roughness
9- // Based on fitted polynomial from Fdez-Aguera 2019
10- // "A Multiple-Scattering Microfacet Model for Real-Time Image-based Lighting"
11- vec3 ggxDirectionalAlbedo( float cosTheta, float roughness, vec3 F0 ) {
11+ // Approximate directional albedo for GGX using simple fitted curve
12+ // More conservative than complex polynomial fits
13+ float ggxDirectionalAlbedoApprox( float NoV, float roughness ) {
1214
1315 // Clamp inputs
14- cosTheta = clamp( cosTheta , 0.0, 1.0 );
15- roughness = clamp( roughness, 0.0 , 1.0 );
16+ NoV = clamp( NoV , 0.0, 1.0 );
17+ roughness = clamp( roughness, 0.04 , 1.0 ); // Min roughness 0.04 for stability
1618
17- // Polynomial fit for the directional albedo
18- // This is derived from pre-integrated lookup tables
19- float c = 1.0 - cosTheta;
20- float c2 = c * c;
21- float c3 = c2 * c;
22- float c4 = c3 * c;
23- float c5 = c4 * c;
19+ // Simple fit based on pre-integrated data
20+ // This is a conservative approximation
21+ float a = roughness * roughness;
22+ float s = a; // Simplified
2423
25- // Roughness term
26- float r = roughness ;
27- float r2 = r * r ;
24+ // Approximate using smooth curve
25+ float Ess = mix( 1.0, 0.0, a ) ;
26+ Ess = mix( Ess, Ess * NoV, a ) ;
2827
29- // Fitted polynomial approximation for dielectric base (F0 = 0)
30- // Returns the scale and bias for the Fresnel term
31- float bias = -0.0408 + r * ( 0.6192 + r * ( -0.8164 + r * 0.4268 ) );
32- float scale = 1.0398 + r * ( -1.3982 + r * ( 1.8305 + r * -0.9869 ) );
33-
34- // Apply Fresnel using fitted approximation
35- float fresnel = clamp( scale * c5 + bias, 0.0, 1.0 );
36-
37- // Directional albedo with Fresnel
38- vec3 E = F0 + ( vec3( 1.0 ) - F0 ) * fresnel;
39-
40- return E;
28+ return clamp( Ess, 0.0, 1.0 );
4129
4230}
4331
44- // Computes the average albedo E_avg(roughness) for GGX with F0=0
45- // This is the hemispherical-hemispherical reflectance
46- // Fitted approximation from Fdez-Aguera 2019
47- float ggxAverageAlbedo( float roughness ) {
48-
49- // Clamp roughness
50- roughness = clamp( roughness, 0.0, 1.0 );
32+ // Average directional albedo (integral over hemisphere)
33+ float ggxAverageAlbedoApprox( float roughness ) {
5134
52- // Polynomial fit for average albedo
53- // This represents the average energy reflected across all view angles
54- float r = roughness;
35+ roughness = clamp( roughness, 0.04, 1.0 );
5536
56- // Fitted polynomial (for F0 = 0, dielectric case)
57- float Eavg = 1.0 + r * ( -0.1104 + r * ( -0.3879 + r * 0.4958 ) );
37+ float a = roughness * roughness;
5838
59- return clamp( Eavg, 0.0, 1.0 );
39+ // Conservative fit - energy decreases with roughness
40+ return 1.0 - a * 0.5;
6041
6142}
6243
63- // Computes the multiscatter contribution color
44+ // Computes the multiscatter contribution color - DISABLED FOR NOW
6445// wo = outgoing direction (view), wi = incoming direction (light)
65- // roughness = linear roughness parameter (NOT alpha = roughness^2)
66- // Returns the additional energy that should be added to compensate for multiple scattering
46+ // roughness = linear roughness parameter
47+ // Returns the additional energy that should be added
6748vec3 ggxMultiScatterCompensation( vec3 wo, vec3 wi, float roughness, vec3 F0 ) {
6849
50+ // DISABLED: Return zero contribution
51+ // The multiscatter compensation appears to be too aggressive for this pathtracer
52+ // The pathtracer already handles multiple bounces through recursive ray tracing
53+ // Additional testing is needed to validate the correct approach
54+
55+ return vec3( 0.0 );
56+
57+ /* ORIGINAL FORMULA - DISABLED
6958 float mu_o = abs( wo.z );
7059 float mu_i = abs( wi.z );
7160
72- // Compute directional albedos for both directions
73- vec3 Eo = ggxDirectionalAlbedo( mu_o, roughness, F0 );
74- vec3 Ei = ggxDirectionalAlbedo( mu_i, roughness, F0 );
75-
76- // Compute average albedo for dielectric base (F0=0)
77- float Eavg = ggxAverageAlbedo( roughness );
61+ // Only apply multiscatter for roughness > 0.3
62+ if ( roughness < 0.3 ) {
63+ return vec3( 0.0 );
64+ }
7865
79- // Kulla-Conty multiscatter formula:
80- // f_ms = (1 - Eo) * (1 - Ei) / (PI * (1 - Eavg)) * Favg
81- // This redistributes the missing energy as a diffuse-like lobe
66+ float Eo = ggxDirectionalAlbedoApprox( mu_o, roughness );
67+ float Ei = ggxDirectionalAlbedoApprox( mu_i, roughness );
68+ float Eavg = ggxAverageAlbedoApprox( roughness );
8269
83- vec3 numerator = ( vec3( 1.0 ) - Eo ) * ( vec3( 1.0 ) - Ei );
84- float denominator = PI * max( 1.0 - Eavg, 0.001 ); // Prevent division by zero
70+ // Kulla-Conty formula with conservative scaling
71+ float numerator = ( 1.0 - Eo ) * ( 1.0 - Ei );
72+ float denominator = PI * max( 1.0 - Eavg, 0.001 );
8573
86- vec3 fms = numerator / denominator;
74+ float fms = numerator / denominator;
8775
88- // The average Fresnel for the multiscatter term
89- // For metals, this is approximately F0
90- // For dielectrics, this is slightly higher than F0
91- vec3 Favg = F0 + ( vec3( 1.0 ) - F0 ) / 21.0;
76+ // Very conservative Favg
77+ vec3 Favg = F0 * 0.5; // Much more conservative
9278
93- return fms * Favg;
79+ // Scale down the contribution significantly
80+ return fms * Favg * 0.1; // 10% strength for testing
81+ */
9482
9583}
9684
0 commit comments