Skip to content

Commit 395f44f

Browse files
authored
Improve Sheen energy conservation and analytic approximation (#32356)
1 parent a21b419 commit 395f44f

File tree

8 files changed

+106
-51
lines changed

8 files changed

+106
-51
lines changed
-800 Bytes
Loading
-800 Bytes
Loading
-1008 Bytes
Loading

src/nodes/accessors/MaterialNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ class MaterialNode extends Node {
321321

322322
}
323323

324-
node = node.clamp( 0.07, 1.0 );
324+
node = node.clamp( 0.0001, 1.0 );
325325

326326
} else if ( scope === MaterialNode.ANISOTROPY ) {
327327

src/nodes/functions/PhysicalLightingModel.js

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import { diffuseColor, diffuseContribution, specularColor, specularColorBlended,
1212
import { normalView, clearcoatNormalView, normalWorld } from '../accessors/Normal.js';
1313
import { positionViewDirection, positionView, positionWorld } from '../accessors/Position.js';
1414
import { Fn, float, vec2, vec3, vec4, mat3, If } from '../tsl/TSLBase.js';
15-
import { select } from '../math/ConditionalNode.js';
16-
import { mix, normalize, refract, length, clamp, log2, log, exp, smoothstep } from '../math/MathNode.js';
15+
import { mix, normalize, refract, length, clamp, log2, log, exp, smoothstep, inverseSqrt } from '../math/MathNode.js';
1716
import { div } from '../math/OperatorNode.js';
1817
import { cameraPosition, cameraProjectionMatrix, cameraViewMatrix } from '../accessors/Camera.js';
1918
import { modelWorldMatrix } from '../accessors/ModelNode.js';
@@ -313,29 +312,26 @@ const evalIridescence = /*@__PURE__*/ Fn( ( { outsideIOR, eta2, cosTheta1, thinF
313312
//
314313

315314
// This is a curve-fit approximation to the "Charlie sheen" BRDF integrated over the hemisphere from
316-
// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found
317-
// in the Sheen section of https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing
315+
// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF".
316+
// The low roughness fit (< 0.25) uses an inversesqrt/log model to accurately capture the sharp peak.
318317
const IBLSheenBRDF = /*@__PURE__*/ Fn( ( { normal, viewDir, roughness } ) => {
319318

320319
const dotNV = normal.dot( viewDir ).saturate();
320+
const r2 = roughness.mul( roughness );
321321

322-
const r2 = roughness.pow2();
323-
324-
const a = select(
325-
roughness.lessThan( 0.25 ),
326-
float( - 339.2 ).mul( r2 ).add( float( 161.4 ).mul( roughness ) ).sub( 25.9 ),
327-
float( - 8.48 ).mul( r2 ).add( float( 14.3 ).mul( roughness ) ).sub( 9.95 )
322+
const a = roughness.lessThan( 0.25 ).select(
323+
inverseSqrt( roughness ).mul( - 1.57 ),
324+
r2.mul( - 3.33 ).add( roughness.mul( 6.27 ) ).sub( 4.40 )
328325
);
329326

330-
const b = select(
331-
roughness.lessThan( 0.25 ),
332-
float( 44.0 ).mul( r2 ).sub( float( 23.7 ).mul( roughness ) ).add( 3.26 ),
333-
float( 1.97 ).mul( r2 ).sub( float( 3.27 ).mul( roughness ) ).add( 0.72 )
327+
const b = roughness.lessThan( 0.25 ).select(
328+
log( roughness ).mul( - 0.46 ).sub( 0.64 ),
329+
r2.mul( 0.92 ).sub( roughness.mul( 1.79 ) ).add( 0.35 )
334330
);
335331

336-
const DG = select( roughness.lessThan( 0.25 ), 0.0, float( 0.1 ).mul( roughness ).sub( 0.025 ) ).add( a.mul( dotNV ).add( b ).exp() );
332+
const DG = a.mul( dotNV ).add( b ).exp();
337333

338-
return DG.mul( 1.0 / Math.PI ).saturate();
334+
return DG.saturate();
339335

340336
} );
341337

@@ -608,12 +604,19 @@ class PhysicalLightingModel extends LightingModel {
608604
direct( { lightDirection, lightColor, reflectedLight }, /* builder */ ) {
609605

610606
const dotNL = normalView.dot( lightDirection ).clamp();
611-
const irradiance = dotNL.mul( lightColor );
607+
const irradiance = dotNL.mul( lightColor ).toVar();
612608

613609
if ( this.sheen === true ) {
614610

615611
this.sheenSpecularDirect.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) );
616612

613+
const sheenAlbedoV = IBLSheenBRDF( { normal: normalView, viewDir: positionViewDirection, roughness: sheenRoughness } );
614+
const sheenAlbedoL = IBLSheenBRDF( { normal: normalView, viewDir: lightDirection, roughness: sheenRoughness } );
615+
616+
const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( sheenAlbedoV.max( sheenAlbedoL ) ).oneMinus();
617+
618+
irradiance.mulAssign( sheenEnergyComp );
619+
617620
}
618621

619622
if ( this.clearcoat === true ) {
@@ -692,7 +695,19 @@ class PhysicalLightingModel extends LightingModel {
692695

693696
const { irradiance, reflectedLight } = builder.context;
694697

695-
reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseContribution } ) ) );
698+
const diffuse = irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseContribution } ) ).toVar();
699+
700+
if ( this.sheen === true ) {
701+
702+
const sheenAlbedo = IBLSheenBRDF( { normal: normalView, viewDir: positionViewDirection, roughness: sheenRoughness } );
703+
704+
const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( sheenAlbedo ).oneMinus();
705+
706+
diffuse.mulAssign( sheenEnergyComp );
707+
708+
}
709+
710+
reflectedLight.indirectDiffuse.addAssign( diffuse );
696711

697712
}
698713

@@ -755,10 +770,23 @@ class PhysicalLightingModel extends LightingModel {
755770

756771
const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );
757772

758-
reflectedLight.indirectSpecular.addAssign( radiance.mul( singleScattering ) );
759-
reflectedLight.indirectSpecular.addAssign( multiScattering.mul( cosineWeightedIrradiance ) );
773+
const indirectSpecular = radiance.mul( singleScattering ).add( multiScattering.mul( cosineWeightedIrradiance ) ).toVar();
774+
const indirectDiffuse = diffuse.mul( cosineWeightedIrradiance ).toVar();
775+
776+
if ( this.sheen === true ) {
777+
778+
const sheenAlbedo = IBLSheenBRDF( { normal: normalView, viewDir: positionViewDirection, roughness: sheenRoughness } );
779+
780+
const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( sheenAlbedo ).oneMinus();
781+
782+
indirectSpecular.mulAssign( sheenEnergyComp );
783+
indirectDiffuse.mulAssign( sheenEnergyComp );
784+
785+
}
786+
787+
reflectedLight.indirectSpecular.addAssign( indirectSpecular );
760788

761-
reflectedLight.indirectDiffuse.addAssign( diffuse.mul( cosineWeightedIrradiance ) );
789+
reflectedLight.indirectDiffuse.addAssign( indirectDiffuse );
762790

763791
}
764792

@@ -822,10 +850,9 @@ class PhysicalLightingModel extends LightingModel {
822850

823851
if ( this.sheen === true ) {
824852

825-
const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus();
826-
const sheenLight = outgoingLight.mul( sheenEnergyComp ).add( this.sheenSpecularDirect, this.sheenSpecularIndirect );
853+
const sheenLight = outgoingLight.add( this.sheenSpecularDirect, this.sheenSpecularIndirect.mul( 1.0 / Math.PI ) );
827854

828-
outgoingLight.assign( sheenLight );
855+
outgoingLight.assign( sheenLight );
829856

830857
}
831858

src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ material.roughness = min( material.roughness, 1.0 );
118118
119119
#endif
120120
121-
material.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );
121+
material.sheenRoughness = clamp( sheenRoughness, 0.0001, 1.0 );
122122
123123
#ifdef USE_SHEEN_ROUGHNESSMAP
124124

src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -358,21 +358,20 @@ vec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 no
358358
#endif
359359
360360
// This is a curve-fit approximation to the "Charlie sheen" BRDF integrated over the hemisphere from
361-
// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found
362-
// in the Sheen section of https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing
361+
// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF".
362+
// The low roughness fit (< 0.25) uses an inversesqrt/log model to accurately capture the sharp peak.
363363
float IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {
364364
365365
float dotNV = saturate( dot( normal, viewDir ) );
366366
367367
float r2 = roughness * roughness;
368368
369-
float a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;
369+
float a = roughness < 0.25 ? - 1.57 * inversesqrt( roughness ) : - 3.33 * r2 + 6.27 * roughness - 4.40;
370+
float b = roughness < 0.25 ? - 0.46 * log( roughness ) - 0.64 : 0.92 * r2 - 1.79 * roughness + 0.35;
370371
371-
float b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;
372+
float DG = exp( a * dotNV + b );
372373
373-
float DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );
374-
375-
return saturate( DG * RECIPROCAL_PI );
374+
return saturate( DG );
376375
377376
}
378377
@@ -531,10 +530,17 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geome
531530
#endif
532531
533532
#ifdef USE_SHEEN
534-
535-
sheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );
536-
537-
#endif
533+
534+
sheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );
535+
536+
float sheenAlbedoV = IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );
537+
float sheenAlbedoL = IBLSheenBRDF( geometryNormal, directLight.direction, material.sheenRoughness );
538+
539+
float sheenEnergyComp = 1.0 - max3( material.sheenColor ) * max( sheenAlbedoV, sheenAlbedoL );
540+
541+
irradiance *= sheenEnergyComp;
542+
543+
#endif
538544
539545
reflectedLight.directSpecular += irradiance * BRDF_GGX_Multiscatter( directLight.direction, geometryViewDir, geometryNormal, material );
540546
@@ -543,7 +549,19 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geome
543549
544550
void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {
545551
546-
reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseContribution );
552+
vec3 diffuse = irradiance * BRDF_Lambert( material.diffuseContribution );
553+
554+
#ifdef USE_SHEEN
555+
556+
float sheenAlbedo = IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );
557+
558+
float sheenEnergyComp = 1.0 - max3( material.sheenColor ) * sheenAlbedo;
559+
560+
diffuse *= sheenEnergyComp;
561+
562+
#endif
563+
564+
reflectedLight.indirectDiffuse += diffuse;
547565
548566
}
549567
@@ -557,9 +575,9 @@ void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradia
557575
558576
#ifdef USE_SHEEN
559577
560-
sheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );
578+
sheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness ) * RECIPROCAL_PI;
561579
562-
#endif
580+
#endif
563581
564582
// Both indirect specular and indirect diffuse light accumulate here
565583
// Compute multiscattering separately for dielectric and metallic, then mix
@@ -592,10 +610,24 @@ void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradia
592610
593611
vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;
594612
595-
reflectedLight.indirectSpecular += radiance * singleScattering;
596-
reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;
613+
vec3 indirectSpecular = radiance * singleScattering;
614+
indirectSpecular += multiScattering * cosineWeightedIrradiance;
615+
616+
vec3 indirectDiffuse = diffuse * cosineWeightedIrradiance;
617+
618+
#ifdef USE_SHEEN
619+
620+
float sheenAlbedo = IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );
621+
622+
float sheenEnergyComp = 1.0 - max3( material.sheenColor ) * sheenAlbedo;
623+
624+
indirectSpecular *= sheenEnergyComp;
625+
indirectDiffuse *= sheenEnergyComp;
626+
627+
#endif
597628
598-
reflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;
629+
reflectedLight.indirectSpecular += indirectSpecular;
630+
reflectedLight.indirectDiffuse += indirectDiffuse;
599631
600632
}
601633

src/renderers/shaders/ShaderLib/meshphysical.glsl.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,10 @@ void main() {
198198
vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;
199199
200200
#ifdef USE_SHEEN
201-
202-
// Sheen energy compensation approximation calculation can be found at the end of
203-
// https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing
204-
float sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );
205-
206-
outgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;
207-
208-
#endif
201+
202+
outgoingLight = outgoingLight + sheenSpecularDirect + sheenSpecularIndirect;
203+
204+
#endif
209205
210206
#ifdef USE_CLEARCOAT
211207

0 commit comments

Comments
 (0)