From 4e8486fb1118a46446fd2526ea809478d109df6a Mon Sep 17 00:00:00 2001 From: Jordan Santell Date: Wed, 23 Jan 2019 20:24:33 -0800 Subject: [PATCH] =?UTF-8?q?Restore=20energy=20loss=20in=20indirect=20specu?= =?UTF-8?q?lar=20lighting=20by=20approximating=20multiscattering=20via=20F?= =?UTF-8?q?dez-Ag=C3=BCera's=20"A=20Multiple-Scattering=20Microfacet=20Mod?= =?UTF-8?q?el=20for=20Real-Time=20Image-based=20Lighting".?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/files.js | 1 + examples/webgl_furnace_test.html | 179 ++++++++++++++++++ src/materials/MeshPhysicalMaterial.js | 2 + .../shaders/ShaderChunk/bsdfs.glsl.js | 49 ++++- .../ShaderChunk/lights_fragment_end.glsl.js | 2 +- .../lights_physical_pars_fragment.glsl.js | 35 +++- 6 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 examples/webgl_furnace_test.html diff --git a/examples/files.js b/examples/files.js index c6588e734e2bff..d328fe51634577 100644 --- a/examples/files.js +++ b/examples/files.js @@ -19,6 +19,7 @@ var files = { "webgl_effects_peppersghost", "webgl_effects_stereo", "webgl_framebuffer_texture", + "webgl_furnace_test", "webgl_geometries", "webgl_geometries_parametric", "webgl_geometry_colors", diff --git a/examples/webgl_furnace_test.html b/examples/webgl_furnace_test.html new file mode 100644 index 00000000000000..8f57330f8329d3 --- /dev/null +++ b/examples/webgl_furnace_test.html @@ -0,0 +1,179 @@ + + + + three.js white furnace test + + + + + + + + + + + + + +
+
+ three.js - + White Furnace energy conservation test.
+ There are 11 fully metal spheres with full white specular color and increasing roughness values rendered here. A metal object, no matter how rough, fully reflects all energy. If uniformly lit, the spheres should be indistinguishable from the environment. If spheres are visible, then some energy has been lost or gained after reflection.
+ by Jordan Santell

+
+ + + + diff --git a/src/materials/MeshPhysicalMaterial.js b/src/materials/MeshPhysicalMaterial.js index 24ee7f9e90e095..a4ea18c03cddbb 100644 --- a/src/materials/MeshPhysicalMaterial.js +++ b/src/materials/MeshPhysicalMaterial.js @@ -5,6 +5,8 @@ import { MeshStandardMaterial } from './MeshStandardMaterial.js'; * * parameters = { * reflectivity: + * clearCoat: + * clearCoatRoughness: * } */ diff --git a/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js b/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js index c238cebec49a65..94841f049aca51 100644 --- a/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js +++ b/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js @@ -1,4 +1,22 @@ -export default /* glsl */` +export default /* glsl */ ` + +// Analytical approximation of the DFG LUT, one half of the +// split-sum approximation used in indirect specular lighting. +// via 'environmentBRDF' from "Physically Based Shading on Mobile" +// https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile +vec2 integrateSpecularBRDF( const in float dotNV, const in float roughness ) { + const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 ); + + const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 ); + + vec4 r = roughness * c0 + c1; + + float a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y; + + return vec2( -1.04, 1.04 ) * a004 + r.zw; + +} + float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) { #if defined ( PHYSICALLY_CORRECT_LIGHTS ) @@ -239,20 +257,35 @@ vec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) ); - const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 ); + vec2 brdf = integrateSpecularBRDF( dotNV, roughness ); - const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 ); + return specularColor * brdf.x + brdf.y; - vec4 r = roughness * c0 + c1; +} // validated - float a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y; +// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting" +// Approximates multiscattering in order to preserve energy. +// http://www.jcgt.org/published/0008/01/03/ +void BRDF_Specular_Multiscattering_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) { - vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw; + float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) ); - return specularColor * AB.x + AB.y; + vec3 F = F_Schlick( specularColor, dotNV ); + vec2 brdf = integrateSpecularBRDF( dotNV, roughness ); + vec3 FssEss = F * brdf.x + brdf.y; -} // validated + float Ess = brdf.x + brdf.y; + float Ems = 1.0 - Ess; + + // Paper incorrect indicates coefficient is PI/21, and will + // be corrected to 1/21 in future updates. + vec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619; // 1/21 + vec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg ); + singleScatter += FssEss; + multiScatter += Fms * Ems; + +} float G_BlinnPhong_Implicit( /* const in float dotNL, const in float dotNV */ ) { diff --git a/src/renderers/shaders/ShaderChunk/lights_fragment_end.glsl.js b/src/renderers/shaders/ShaderChunk/lights_fragment_end.glsl.js index f9f8dc08642160..5405840e2d9583 100644 --- a/src/renderers/shaders/ShaderChunk/lights_fragment_end.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_fragment_end.glsl.js @@ -7,7 +7,7 @@ export default /* glsl */` #if defined( RE_IndirectSpecular ) - RE_IndirectSpecular( radiance, clearCoatRadiance, geometry, material, reflectedLight ); + RE_IndirectSpecular( radiance, irradiance, clearCoatRadiance, geometry, material, reflectedLight ); #endif `; 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 dda0f3db872c68..ba2bed2623f978 100644 --- a/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js @@ -96,11 +96,17 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) { - reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor ); + // Defer to the IndirectSpecular function to compute + // the indirectDiffuse if energy preservation is enabled. + #ifndef ENVMAP_TYPE_CUBE_UV + + reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor ); + + #endif } -void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) { +void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) { #ifndef STANDARD float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) ); @@ -110,14 +116,35 @@ void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCo float clearCoatDHR = 0.0; #endif - reflectedLight.indirectSpecular += ( 1.0 - clearCoatDHR ) * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness ); + float clearCoatInv = 1.0 - clearCoatDHR; + + // Both indirect specular and diffuse light accumulate here + // if energy preservation enabled, and PMREM provided. + #if defined( ENVMAP_TYPE_CUBE_UV ) + + vec3 singleScattering = vec3( 0.0 ); + vec3 multiScattering = vec3( 0.0 ); + vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI; + + BRDF_Specular_Multiscattering_Environment( geometry, material.specularColor, material.specularRoughness, singleScattering, multiScattering ); + + vec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) ); + + reflectedLight.indirectSpecular += clearCoatInv * radiance * singleScattering; + reflectedLight.indirectDiffuse += multiScattering * cosineWeightedIrradiance; + reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance; + + #else + + reflectedLight.indirectSpecular += clearCoatInv * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness ); + + #endif #ifndef STANDARD reflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness ); #endif - } #define RE_Direct RE_Direct_Physical