diff --git a/src/nodes/functions/PhysicalLightingModel.js b/src/nodes/functions/PhysicalLightingModel.js index 4f1e5e459a0353..55a6922a93514e 100644 --- a/src/nodes/functions/PhysicalLightingModel.js +++ b/src/nodes/functions/PhysicalLightingModel.js @@ -473,6 +473,14 @@ class PhysicalLightingModel extends LightingModel { */ this.iridescenceF0Metallic = null; + /** + * The sampled DFG LUT value, shared by the direct and indirect lighting paths. + * + * @type {?Node} + * @default null + */ + this.dfg = null; + /** * The multi-scattering energy compensation for direct lighting. * @@ -565,14 +573,17 @@ class PhysicalLightingModel extends LightingModel { } + // + + const dotNV = normalView.dot( positionViewDirection ).clamp(); + this.dfg = DFGLUT( { roughness, dotNV } ).toConst( 'dfg' ); + // 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 ); + const Ess = this.dfg.x.add( this.dfg.y ); // Compensate for the energy lost to multiple scattering, tinting the added term by F0 ( equation 16 ) this.multiScatteringCompensation = specularColorBlended.mul( Ess.reciprocal().sub( 1.0 ) ).add( 1.0 ).toConst( 'multiScatteringCompensation' ); @@ -587,9 +598,7 @@ class PhysicalLightingModel extends LightingModel { computeMultiscattering( singleScatter, multiScatter, specularF90, f0, iridescenceF0 = null ) { - const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV - - const fab = DFGLUT( { roughness, dotNV } ); + const fab = this.dfg; const Fr = iridescenceF0 ? iridescence.mix( f0, iridescenceF0 ) : f0; @@ -642,9 +651,7 @@ class PhysicalLightingModel extends LightingModel { // Light reflected by the specular interface is not available to the diffuse layer ( glTF fresnel_mix ) const halfDir = lightDirection.add( positionViewDirection ).normalize(); const dotVH = positionViewDirection.dot( halfDir ).clamp(); - const F = F_Schlick( { f0: specularColor, f90: specularF90, dotVH } ); - - reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseContribution } ) ).mul( F.oneMinus() ) ); + let F = F_Schlick( { f0: specularColor, f90: specularF90, dotVH } ); let specularBRDF = BRDF_GGX( { lightDirection, f0: specularColorBlended, f90: 1, roughness, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ); @@ -653,12 +660,18 @@ class PhysicalLightingModel extends LightingModel { // Minimal Retroreflective Microfacet Model: // https://jcgt.org/published/0015/01/04/ const retroViewDirection = positionViewDirection.negate().reflect( normalView ); + const retroHalfDir = lightDirection.add( retroViewDirection ).normalize(); + const dotRetroVH = retroViewDirection.dot( retroHalfDir ).clamp(); + const retroF = F_Schlick( { f0: specularColor, f90: specularF90, dotVH: dotRetroVH } ); const retroSpecularBRDF = BRDF_GGX( { lightDirection, viewDirection: retroViewDirection, f0: specularColorBlended, f90: 1, roughness, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ); + F = mix( F, retroF, retroreflective.clamp() ); specularBRDF = mix( specularBRDF, retroSpecularBRDF, retroreflective.clamp() ); } + reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseContribution } ) ).mul( F.oneMinus() ) ); + reflectedLight.directSpecular.addAssign( irradiance.mul( specularBRDF ).mul( this.multiScatteringCompensation ) ); } diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 021801fdba75a2..b3c14b136900cf 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -2415,11 +2415,11 @@ class WebGLRenderer { needsProgramChange = true; - } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object.colorTexture === null ) { + } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object._colorsTexture === null ) { needsProgramChange = true; - } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object.colorTexture !== null ) { + } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object._colorsTexture !== null ) { needsProgramChange = true; diff --git a/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js b/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js index bfa4529431474d..f9af80b4e6692a 100644 --- a/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js @@ -54,20 +54,25 @@ vec3 geometryClearcoatNormal = vec3( 0.0 ); #endif -#if defined( STANDARD ) && ( NUM_DIR_LIGHTS > 0 || NUM_POINT_LIGHTS > 0 || NUM_SPOT_LIGHTS > 0 ) +#ifdef STANDARD - // 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 dotNVms = saturate( dot( geometryNormal, geometryViewDir ) ); - vec2 fabMs = texture2D( dfgLUT, vec2( material.roughness, dotNVms ) ).rg; + material.dfg = texture2D( dfgLUT, vec2( material.roughness, dotNVms ) ).rg; - // Energy of the single-scattering lobe in a white furnace ( F0 = F90 = 1 ) - float EssMs = fabMs.x + fabMs.y; + #if ( NUM_DIR_LIGHTS > 0 || NUM_POINT_LIGHTS > 0 || NUM_SPOT_LIGHTS > 0 ) - // Compensate for the energy lost to multiple scattering, tinting the added term by F0 ( equation 16 ) - material.multiScatteringCompensation = 1.0 + material.specularColorBlended * ( 1.0 / EssMs - 1.0 ); + // 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 + + // Energy of the single-scattering lobe in a white furnace ( F0 = F90 = 1 ) + float EssMs = material.dfg.x + material.dfg.y; + + // Compensate for the energy lost to multiple scattering, tinting the added term by F0 ( equation 16 ) + material.multiScatteringCompensation = 1.0 + material.specularColorBlended * ( 1.0 / EssMs - 1.0 ); + + #endif #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 92b5c00c882a81..374e17ef5392f4 100644 --- a/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js @@ -13,6 +13,7 @@ struct PhysicalMaterial { float metalness; float specularF90; float dispersion; + vec2 dfg; vec3 multiScatteringCompensation; #ifdef USE_RETROREFLECTIVE @@ -391,14 +392,11 @@ vec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 // Approximates multiscattering in order to preserve energy. // http://www.jcgt.org/published/0008/01/03/ #ifdef USE_IRIDESCENCE -void computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) { +void computeMultiscatteringIridescence( const in vec2 fab, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, inout vec3 singleScatter, inout vec3 multiScatter ) { #else -void computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) { +void computeMultiscattering( const in vec2 fab, const in vec3 specularColor, const in float specularF90, inout vec3 singleScatter, inout vec3 multiScatter ) { #endif - float dotNV = saturate( dot( normal, viewDir ) ); - vec2 fab = texture2D( dfgLUT, vec2( roughness, dotNV ) ).rg; - #ifdef USE_IRIDESCENCE vec3 Fr = mix( specularColor, iridescenceF0, iridescence ); @@ -535,6 +533,16 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geome float dotVH = saturate( dot( geometryViewDir, halfDir ) ); vec3 F = F_Schlick( material.specularColor, material.specularF90, dotVH ); + #ifdef USE_RETROREFLECTIVE + + vec3 retroHalfDir = normalize( directLight.direction + retroViewDir ); + float dotRetroVH = saturate( dot( retroViewDir, retroHalfDir ) ); + vec3 retroF = F_Schlick( material.specularColor, material.specularF90, dotRetroVH ); + + F = mix( F, retroF, saturate( material.retroreflective ) ); + + #endif + reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseContribution ) * ( 1.0 - F ); } @@ -546,11 +554,11 @@ void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geomet #ifdef USE_IRIDESCENCE - computeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceF0Dielectric, material.roughness, singleScattering, multiScattering ); + computeMultiscatteringIridescence( material.dfg, material.specularColor, material.specularF90, material.iridescence, material.iridescenceF0Dielectric, singleScattering, multiScattering ); #else - computeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering ); + computeMultiscattering( material.dfg, material.specularColor, material.specularF90, singleScattering, multiScattering ); #endif @@ -597,13 +605,13 @@ void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradia #ifdef USE_IRIDESCENCE - computeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceF0Dielectric, material.roughness, singleScatteringDielectric, multiScatteringDielectric ); - computeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.diffuseColor, material.specularF90, material.iridescence, material.iridescenceF0Metallic, material.roughness, singleScatteringMetallic, multiScatteringMetallic ); + computeMultiscatteringIridescence( material.dfg, material.specularColor, material.specularF90, material.iridescence, material.iridescenceF0Dielectric, singleScatteringDielectric, multiScatteringDielectric ); + computeMultiscatteringIridescence( material.dfg, material.diffuseColor, material.specularF90, material.iridescence, material.iridescenceF0Metallic, singleScatteringMetallic, multiScatteringMetallic ); #else - computeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScatteringDielectric, multiScatteringDielectric ); - computeMultiscattering( geometryNormal, geometryViewDir, material.diffuseColor, material.specularF90, material.roughness, singleScatteringMetallic, multiScatteringMetallic ); + computeMultiscattering( material.dfg, material.specularColor, material.specularF90, singleScatteringDielectric, multiScatteringDielectric ); + computeMultiscattering( material.dfg, material.diffuseColor, material.specularF90, singleScatteringMetallic, multiScatteringMetallic ); #endif