📄 PhysicalLightingModel.js
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 12 |
🧱 Classes | 1 |
📦 Imports | 60 |
📊 Variables & Constants | 9 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 src/nodes/functions/PhysicalLightingModel.js
📦 Imports¶
Name | Source |
---|---|
BRDF_Lambert |
./BSDF/BRDF_Lambert.js |
BRDF_GGX |
./BSDF/BRDF_GGX.js |
DFGApprox |
./BSDF/DFGApprox.js |
EnvironmentBRDF |
./BSDF/EnvironmentBRDF.js |
F_Schlick |
./BSDF/F_Schlick.js |
Schlick_to_F0 |
./BSDF/Schlick_to_F0.js |
BRDF_Sheen |
./BSDF/BRDF_Sheen.js |
LTC_Evaluate |
./BSDF/LTC.js |
LTC_Uv |
./BSDF/LTC.js |
LightingModel |
../core/LightingModel.js |
diffuseColor |
../core/PropertyNode.js |
specularColor |
../core/PropertyNode.js |
specularF90 |
../core/PropertyNode.js |
roughness |
../core/PropertyNode.js |
clearcoat |
../core/PropertyNode.js |
clearcoatRoughness |
../core/PropertyNode.js |
sheen |
../core/PropertyNode.js |
sheenRoughness |
../core/PropertyNode.js |
iridescence |
../core/PropertyNode.js |
iridescenceIOR |
../core/PropertyNode.js |
iridescenceThickness |
../core/PropertyNode.js |
ior |
../core/PropertyNode.js |
thickness |
../core/PropertyNode.js |
transmission |
../core/PropertyNode.js |
attenuationDistance |
../core/PropertyNode.js |
attenuationColor |
../core/PropertyNode.js |
dispersion |
../core/PropertyNode.js |
normalView |
../accessors/Normal.js |
clearcoatNormalView |
../accessors/Normal.js |
normalWorld |
../accessors/Normal.js |
positionViewDirection |
../accessors/Position.js |
positionView |
../accessors/Position.js |
positionWorld |
../accessors/Position.js |
Fn |
../tsl/TSLBase.js |
float |
../tsl/TSLBase.js |
vec2 |
../tsl/TSLBase.js |
vec3 |
../tsl/TSLBase.js |
vec4 |
../tsl/TSLBase.js |
mat3 |
../tsl/TSLBase.js |
If |
../tsl/TSLBase.js |
select |
../math/ConditionalNode.js |
mix |
../math/MathNode.js |
normalize |
../math/MathNode.js |
refract |
../math/MathNode.js |
length |
../math/MathNode.js |
clamp |
../math/MathNode.js |
log2 |
../math/MathNode.js |
log |
../math/MathNode.js |
exp |
../math/MathNode.js |
smoothstep |
../math/MathNode.js |
div |
../math/OperatorNode.js |
cameraPosition |
../accessors/Camera.js |
cameraProjectionMatrix |
../accessors/Camera.js |
cameraViewMatrix |
../accessors/Camera.js |
modelWorldMatrix |
../accessors/ModelNode.js |
screenSize |
../display/ScreenNode.js |
viewportMipTexture |
../display/ViewportTextureNode.js |
textureBicubicLevel |
../accessors/TextureBicubic.js |
Loop |
../utils/LoopNode.js |
BackSide |
../../constants.js |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
vTexture |
any |
let/var | material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture |
✗ |
transmittedLight |
any |
let/var | *not shown* |
✗ |
transmittance |
any |
let/var | *not shown* |
✗ |
position |
VaryingNode<vec3> |
let/var | positionWorld |
✗ |
n |
any |
let/var | normalWorld |
✗ |
context |
any |
let/var | builder.context |
✗ |
Fr |
any |
let/var | this.iridescenceF0 ? iridescence.mix( specularColor, this.iridescenceF0 ) : s... |
✗ |
N |
any |
let/var | normalView |
✗ |
V |
VaryingNode<vec3> |
let/var | positionViewDirection |
✗ |
Functions¶
Fresnel0ToIor(fresnel0: any): any
¶
Parameters:
fresnel0
any
Returns: any
Calls:
fresnel0.sqrt
vec3( 1.0 ).add( sqrtF0 ).div
vec3( 1.0 ).sub
Code
IorToFresnel0(transmittedIor: any, incidentIor: any): any
¶
Parameters:
transmittedIor
any
incidentIor
any
Returns: any
Calls:
transmittedIor.sub( incidentIor ).div( transmittedIor.add( incidentIor ) ).pow2
Code
evalSensitivity(OPD: any, shift: any): any
¶
Parameters:
OPD
any
shift
any
Returns: any
Calls:
OPD.mul
vec3 (from ../tsl/TSLBase.js)
float( 9.7470e-14 * Math.sqrt( 2.0 * Math.PI * 4.5282e+09 ) ).mul( phase.mul( 2.2399e+06 ).add( shift.x ).cos() ).mul
phase.pow2().mul( - 4.5282e+09 ).exp
val.mul( VAR.mul( 2.0 * Math.PI ).sqrt() ).mul( pos.mul( phase ).add( shift ).cos() ).mul
phase.pow2().negate().mul( VAR ).exp
vec3( xyz.x.add( x ), xyz.y, xyz.z ).div
XYZ_TO_REC709.mul
Code
( OPD, shift ) => {
const phase = OPD.mul( 2.0 * Math.PI * 1.0e-9 );
const val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );
const pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );
const VAR = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );
const x = float( 9.7470e-14 * Math.sqrt( 2.0 * Math.PI * 4.5282e+09 ) ).mul( phase.mul( 2.2399e+06 ).add( shift.x ).cos() ).mul( phase.pow2().mul( - 4.5282e+09 ).exp() );
let xyz = val.mul( VAR.mul( 2.0 * Math.PI ).sqrt() ).mul( pos.mul( phase ).add( shift ).cos() ).mul( phase.pow2().negate().mul( VAR ).exp() );
xyz = vec3( xyz.x.add( x ), xyz.y, xyz.z ).div( 1.0685e-7 );
const rgb = XYZ_TO_REC709.mul( xyz );
return rgb;
}
PhysicalLightingModel.start(builder: NodeBuilder): void
¶
JSDoc:
/**
* Depending on what features are requested, the method prepares certain node variables
* which are later used for lighting computations.
*
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
builder
NodeBuilder
Returns: void
Calls:
vec3().toVar
normalView.dot( positionViewDirection ).clamp
evalIridescence
float (from ../tsl/TSLBase.js)
Schlick_to_F0 (from ./BSDF/Schlick_to_F0.js)
cameraPosition.sub( positionWorld ).normalize
getIBLVolumeRefraction
diffuseColor.a.mulAssign
mix (from ../math/MathNode.js)
super.start
Code
start( builder ) {
if ( this.clearcoat === true ) {
this.clearcoatRadiance = vec3().toVar( 'clearcoatRadiance' );
this.clearcoatSpecularDirect = vec3().toVar( 'clearcoatSpecularDirect' );
this.clearcoatSpecularIndirect = vec3().toVar( 'clearcoatSpecularIndirect' );
}
if ( this.sheen === true ) {
this.sheenSpecularDirect = vec3().toVar( 'sheenSpecularDirect' );
this.sheenSpecularIndirect = vec3().toVar( 'sheenSpecularIndirect' );
}
if ( this.iridescence === true ) {
const dotNVi = normalView.dot( positionViewDirection ).clamp();
this.iridescenceFresnel = evalIridescence( {
outsideIOR: float( 1.0 ),
eta2: iridescenceIOR,
cosTheta1: dotNVi,
thinFilmThickness: iridescenceThickness,
baseF0: specularColor
} );
this.iridescenceF0 = Schlick_to_F0( { f: this.iridescenceFresnel, f90: 1.0, dotVH: dotNVi } );
}
if ( this.transmission === true ) {
const position = positionWorld;
const v = cameraPosition.sub( positionWorld ).normalize(); // TODO: Create Node for this, same issue in MaterialX
const n = normalWorld;
const context = builder.context;
context.backdrop = getIBLVolumeRefraction(
n,
v,
roughness,
diffuseColor,
specularColor,
specularF90, // specularF90
position, // positionWorld
modelWorldMatrix, // modelMatrix
cameraViewMatrix, // viewMatrix
cameraProjectionMatrix, // projMatrix
ior,
thickness,
attenuationColor,
attenuationDistance,
this.dispersion ? dispersion : null
);
context.backdropAlpha = transmission;
diffuseColor.a.mulAssign( mix( 1, context.backdrop.a, transmission ) );
}
super.start( builder );
}
PhysicalLightingModel.computeMultiscattering(singleScatter: any, multiScatter: any, specularF90: any): void
¶
Parameters:
singleScatter
any
multiScatter
any
specularF90
any
Returns: void
Calls:
normalView.dot( positionViewDirection ).clamp
DFGApprox (from ./BSDF/DFGApprox.js)
iridescence.mix
Fr.mul( fab.x ).add
specularF90.mul
fab.x.add
Ess.oneMinus
specularColor.add
specularColor.oneMinus().mul
FssEss.mul( Favg ).div
Ems.mul( Favg ).oneMinus
singleScatter.addAssign
multiScatter.addAssign
Fms.mul
Code
computeMultiscattering( singleScatter, multiScatter, specularF90 ) {
const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
const fab = DFGApprox( { roughness, dotNV } );
const Fr = this.iridescenceF0 ? iridescence.mix( specularColor, this.iridescenceF0 ) : specularColor;
const FssEss = Fr.mul( fab.x ).add( specularF90.mul( fab.y ) );
const Ess = fab.x.add( fab.y );
const Ems = Ess.oneMinus();
const Favg = specularColor.add( specularColor.oneMinus().mul( 0.047619 ) ); // 1/21
const Fms = FssEss.mul( Favg ).div( Ems.mul( Favg ).oneMinus() );
singleScatter.addAssign( FssEss );
multiScatter.addAssign( Fms.mul( Ems ) );
}
PhysicalLightingModel.direct({ lightDirection, lightColor, reflectedLight }: any): void
¶
JSDoc:
/**
* Implements the direct light.
*
* @param {Object} lightData - The light data.
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
{ lightDirection, lightColor, reflectedLight }
any
Returns: void
Calls:
normalView.dot( lightDirection ).clamp
dotNL.mul
this.sheenSpecularDirect.addAssign
irradiance.mul
BRDF_Sheen (from ./BSDF/BRDF_Sheen.js)
clearcoatNormalView.dot( lightDirection ).clamp
dotNLcc.mul
this.clearcoatSpecularDirect.addAssign
ccIrradiance.mul
BRDF_GGX (from ./BSDF/BRDF_GGX.js)
reflectedLight.directDiffuse.addAssign
BRDF_Lambert (from ./BSDF/BRDF_Lambert.js)
reflectedLight.directSpecular.addAssign
Code
direct( { lightDirection, lightColor, reflectedLight }, /* builder */ ) {
const dotNL = normalView.dot( lightDirection ).clamp();
const irradiance = dotNL.mul( lightColor );
if ( this.sheen === true ) {
this.sheenSpecularDirect.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) );
}
if ( this.clearcoat === true ) {
const dotNLcc = clearcoatNormalView.dot( lightDirection ).clamp();
const ccIrradiance = dotNLcc.mul( lightColor );
this.clearcoatSpecularDirect.addAssign( ccIrradiance.mul( BRDF_GGX( { lightDirection, f0: clearcoatF0, f90: clearcoatF90, roughness: clearcoatRoughness, normalView: clearcoatNormalView } ) ) );
}
reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness, iridescence: this.iridescence, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ) ) );
}
PhysicalLightingModel.directRectArea({ lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 }: any): void
¶
JSDoc:
/**
* This method is intended for implementing the direct light term for
* rect area light nodes.
*
* @param {Object} input - The input data.
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
{ lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 }
any
Returns: void
Calls:
lightPosition.add( halfWidth ).sub
lightPosition.sub( halfWidth ).sub
lightPosition.sub( halfWidth ).add
lightPosition.add( halfWidth ).add
positionView.toVar
LTC_Uv (from ./BSDF/LTC.js)
ltc_1.sample( uv ).toVar
ltc_2.sample( uv ).toVar
mat3( vec3( t1.x, 0, t1.y ), vec3( 0, 1, 0 ), vec3( t1.z, 0, t1.w ) ).toVar
specularColor.mul( t2.x ).add( specularColor.oneMinus().mul( t2.y ) ).toVar
reflectedLight.directSpecular.addAssign
lightColor.mul( fresnel ).mul
LTC_Evaluate (from ./BSDF/LTC.js)
reflectedLight.directDiffuse.addAssign
lightColor.mul( diffuseColor ).mul
mat3 (from ../tsl/TSLBase.js)
Internal Comments:
// LTC Fresnel Approximation by Stephen Hill (x2)
// http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf (x2)
Code
directRectArea( { lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 }, /* builder */ ) {
const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction
const p1 = lightPosition.sub( halfWidth ).sub( halfHeight );
const p2 = lightPosition.sub( halfWidth ).add( halfHeight );
const p3 = lightPosition.add( halfWidth ).add( halfHeight );
const N = normalView;
const V = positionViewDirection;
const P = positionView.toVar();
const uv = LTC_Uv( { N, V, roughness } );
const t1 = ltc_1.sample( uv ).toVar();
const t2 = ltc_2.sample( uv ).toVar();
const mInv = mat3(
vec3( t1.x, 0, t1.y ),
vec3( 0, 1, 0 ),
vec3( t1.z, 0, t1.w )
).toVar();
// LTC Fresnel Approximation by Stephen Hill
// http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf
const fresnel = specularColor.mul( t2.x ).add( specularColor.oneMinus().mul( t2.y ) ).toVar();
reflectedLight.directSpecular.addAssign( lightColor.mul( fresnel ).mul( LTC_Evaluate( { N, V, P, mInv, p0, p1, p2, p3 } ) ) );
reflectedLight.directDiffuse.addAssign( lightColor.mul( diffuseColor ).mul( LTC_Evaluate( { N, V, P, mInv: mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), p0, p1, p2, p3 } ) ) );
}
PhysicalLightingModel.indirect(builder: NodeBuilder): void
¶
JSDoc:
/**
* Implements the indirect lighting.
*
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
builder
NodeBuilder
Returns: void
Calls:
this.indirectDiffuse
this.indirectSpecular
this.ambientOcclusion
Code
PhysicalLightingModel.indirectDiffuse(builder: NodeBuilder): void
¶
JSDoc:
/**
* Implements the indirect diffuse term.
*
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
builder
NodeBuilder
Returns: void
Calls:
reflectedLight.indirectDiffuse.addAssign
irradiance.mul
BRDF_Lambert (from ./BSDF/BRDF_Lambert.js)
Code
PhysicalLightingModel.indirectSpecular(builder: NodeBuilder): void
¶
JSDoc:
/**
* Implements the indirect specular term.
*
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
builder
NodeBuilder
Returns: void
Calls:
this.sheenSpecularIndirect.addAssign
iblIrradiance.mul
IBLSheenBRDF
clearcoatNormalView.dot( positionViewDirection ).clamp
EnvironmentBRDF (from ./BSDF/EnvironmentBRDF.js)
this.clearcoatSpecularIndirect.addAssign
this.clearcoatRadiance.mul
vec3().toVar
this.computeMultiscattering
singleScattering.add
diffuseColor.mul
totalScattering.r.max( totalScattering.g ).max( totalScattering.b ).oneMinus
reflectedLight.indirectSpecular.addAssign
radiance.mul
multiScattering.mul
reflectedLight.indirectDiffuse.addAssign
diffuse.mul
Internal Comments:
Code
indirectSpecular( builder ) {
const { radiance, iblIrradiance, reflectedLight } = builder.context;
if ( this.sheen === true ) {
this.sheenSpecularIndirect.addAssign( iblIrradiance.mul(
sheen,
IBLSheenBRDF( {
normal: normalView,
viewDir: positionViewDirection,
roughness: sheenRoughness
} )
) );
}
if ( this.clearcoat === true ) {
const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
const clearcoatEnv = EnvironmentBRDF( {
dotNV: dotNVcc,
specularColor: clearcoatF0,
specularF90: clearcoatF90,
roughness: clearcoatRoughness
} );
this.clearcoatSpecularIndirect.addAssign( this.clearcoatRadiance.mul( clearcoatEnv ) );
}
// Both indirect specular and indirect diffuse light accumulate here
const singleScattering = vec3().toVar( 'singleScattering' );
const multiScattering = vec3().toVar( 'multiScattering' );
const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );
this.computeMultiscattering( singleScattering, multiScattering, specularF90 );
const totalScattering = singleScattering.add( multiScattering );
const diffuse = diffuseColor.mul( totalScattering.r.max( totalScattering.g ).max( totalScattering.b ).oneMinus() );
reflectedLight.indirectSpecular.addAssign( radiance.mul( singleScattering ) );
reflectedLight.indirectSpecular.addAssign( multiScattering.mul( cosineWeightedIrradiance ) );
reflectedLight.indirectDiffuse.addAssign( diffuse.mul( cosineWeightedIrradiance ) );
}
PhysicalLightingModel.ambientOcclusion(builder: NodeBuilder): void
¶
JSDoc:
/**
* Implements the ambient occlusion term.
*
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
builder
NodeBuilder
Returns: void
Calls:
normalView.dot( positionViewDirection ).clamp
dotNV.add
roughness.mul( - 16.0 ).oneMinus().negate().exp2
ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp
this.clearcoatSpecularIndirect.mulAssign
this.sheenSpecularIndirect.mulAssign
reflectedLight.indirectDiffuse.mulAssign
reflectedLight.indirectSpecular.mulAssign
Code
ambientOcclusion( builder ) {
const { ambientOcclusion, reflectedLight } = builder.context;
const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
const aoNV = dotNV.add( ambientOcclusion );
const aoExp = roughness.mul( - 16.0 ).oneMinus().negate().exp2();
const aoNode = ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp();
if ( this.clearcoat === true ) {
this.clearcoatSpecularIndirect.mulAssign( ambientOcclusion );
}
if ( this.sheen === true ) {
this.sheenSpecularIndirect.mulAssign( ambientOcclusion );
}
reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
reflectedLight.indirectSpecular.mulAssign( aoNode );
}
PhysicalLightingModel.finish({ context }: any): void
¶
JSDoc:
/**
* Used for final lighting accumulations depending on the requested features.
*
* @param {NodeBuilder} builder - The current node builder.
*/
Parameters:
{ context }
any
Returns: void
Calls:
clearcoatNormalView.dot( positionViewDirection ).clamp
F_Schlick (from ./BSDF/F_Schlick.js)
outgoingLight.mul( clearcoat.mul( Fcc ).oneMinus() ).add
this.clearcoatSpecularDirect.add( this.clearcoatSpecularIndirect ).mul
outgoingLight.assign
sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus
outgoingLight.mul( sheenEnergyComp ).add
Code
finish( { context } ) {
const { outgoingLight } = context;
if ( this.clearcoat === true ) {
const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
const Fcc = F_Schlick( {
dotVH: dotNVcc,
f0: clearcoatF0,
f90: clearcoatF90
} );
const clearcoatLight = outgoingLight.mul( clearcoat.mul( Fcc ).oneMinus() ).add( this.clearcoatSpecularDirect.add( this.clearcoatSpecularIndirect ).mul( clearcoat ) );
outgoingLight.assign( clearcoatLight );
}
if ( this.sheen === true ) {
const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus();
const sheenLight = outgoingLight.mul( sheenEnergyComp ).add( this.sheenSpecularDirect, this.sheenSpecularIndirect );
outgoingLight.assign( sheenLight );
}
}
Classes¶
PhysicalLightingModel
¶
Class Code
class PhysicalLightingModel extends LightingModel {
/**
* Constructs a new physical lighting model.
*
* @param {boolean} [clearcoat=false] - Whether clearcoat is supported or not.
* @param {boolean} [sheen=false] - Whether sheen is supported or not.
* @param {boolean} [iridescence=false] - Whether iridescence is supported or not.
* @param {boolean} [anisotropy=false] - Whether anisotropy is supported or not.
* @param {boolean} [transmission=false] - Whether transmission is supported or not.
* @param {boolean} [dispersion=false] - Whether dispersion is supported or not.
*/
constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false ) {
super();
/**
* Whether clearcoat is supported or not.
*
* @type {boolean}
* @default false
*/
this.clearcoat = clearcoat;
/**
* Whether sheen is supported or not.
*
* @type {boolean}
* @default false
*/
this.sheen = sheen;
/**
* Whether iridescence is supported or not.
*
* @type {boolean}
* @default false
*/
this.iridescence = iridescence;
/**
* Whether anisotropy is supported or not.
*
* @type {boolean}
* @default false
*/
this.anisotropy = anisotropy;
/**
* Whether transmission is supported or not.
*
* @type {boolean}
* @default false
*/
this.transmission = transmission;
/**
* Whether dispersion is supported or not.
*
* @type {boolean}
* @default false
*/
this.dispersion = dispersion;
/**
* The clear coat radiance.
*
* @type {?Node}
* @default null
*/
this.clearcoatRadiance = null;
/**
* The clear coat specular direct.
*
* @type {?Node}
* @default null
*/
this.clearcoatSpecularDirect = null;
/**
* The clear coat specular indirect.
*
* @type {?Node}
* @default null
*/
this.clearcoatSpecularIndirect = null;
/**
* The sheen specular direct.
*
* @type {?Node}
* @default null
*/
this.sheenSpecularDirect = null;
/**
* The sheen specular indirect.
*
* @type {?Node}
* @default null
*/
this.sheenSpecularIndirect = null;
/**
* The iridescence Fresnel.
*
* @type {?Node}
* @default null
*/
this.iridescenceFresnel = null;
/**
* The iridescence F0.
*
* @type {?Node}
* @default null
*/
this.iridescenceF0 = null;
}
/**
* Depending on what features are requested, the method prepares certain node variables
* which are later used for lighting computations.
*
* @param {NodeBuilder} builder - The current node builder.
*/
start( builder ) {
if ( this.clearcoat === true ) {
this.clearcoatRadiance = vec3().toVar( 'clearcoatRadiance' );
this.clearcoatSpecularDirect = vec3().toVar( 'clearcoatSpecularDirect' );
this.clearcoatSpecularIndirect = vec3().toVar( 'clearcoatSpecularIndirect' );
}
if ( this.sheen === true ) {
this.sheenSpecularDirect = vec3().toVar( 'sheenSpecularDirect' );
this.sheenSpecularIndirect = vec3().toVar( 'sheenSpecularIndirect' );
}
if ( this.iridescence === true ) {
const dotNVi = normalView.dot( positionViewDirection ).clamp();
this.iridescenceFresnel = evalIridescence( {
outsideIOR: float( 1.0 ),
eta2: iridescenceIOR,
cosTheta1: dotNVi,
thinFilmThickness: iridescenceThickness,
baseF0: specularColor
} );
this.iridescenceF0 = Schlick_to_F0( { f: this.iridescenceFresnel, f90: 1.0, dotVH: dotNVi } );
}
if ( this.transmission === true ) {
const position = positionWorld;
const v = cameraPosition.sub( positionWorld ).normalize(); // TODO: Create Node for this, same issue in MaterialX
const n = normalWorld;
const context = builder.context;
context.backdrop = getIBLVolumeRefraction(
n,
v,
roughness,
diffuseColor,
specularColor,
specularF90, // specularF90
position, // positionWorld
modelWorldMatrix, // modelMatrix
cameraViewMatrix, // viewMatrix
cameraProjectionMatrix, // projMatrix
ior,
thickness,
attenuationColor,
attenuationDistance,
this.dispersion ? dispersion : null
);
context.backdropAlpha = transmission;
diffuseColor.a.mulAssign( mix( 1, context.backdrop.a, transmission ) );
}
super.start( builder );
}
// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
// Approximates multi-scattering in order to preserve energy.
// http://www.jcgt.org/published/0008/01/03/
computeMultiscattering( singleScatter, multiScatter, specularF90 ) {
const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
const fab = DFGApprox( { roughness, dotNV } );
const Fr = this.iridescenceF0 ? iridescence.mix( specularColor, this.iridescenceF0 ) : specularColor;
const FssEss = Fr.mul( fab.x ).add( specularF90.mul( fab.y ) );
const Ess = fab.x.add( fab.y );
const Ems = Ess.oneMinus();
const Favg = specularColor.add( specularColor.oneMinus().mul( 0.047619 ) ); // 1/21
const Fms = FssEss.mul( Favg ).div( Ems.mul( Favg ).oneMinus() );
singleScatter.addAssign( FssEss );
multiScatter.addAssign( Fms.mul( Ems ) );
}
/**
* Implements the direct light.
*
* @param {Object} lightData - The light data.
* @param {NodeBuilder} builder - The current node builder.
*/
direct( { lightDirection, lightColor, reflectedLight }, /* builder */ ) {
const dotNL = normalView.dot( lightDirection ).clamp();
const irradiance = dotNL.mul( lightColor );
if ( this.sheen === true ) {
this.sheenSpecularDirect.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) );
}
if ( this.clearcoat === true ) {
const dotNLcc = clearcoatNormalView.dot( lightDirection ).clamp();
const ccIrradiance = dotNLcc.mul( lightColor );
this.clearcoatSpecularDirect.addAssign( ccIrradiance.mul( BRDF_GGX( { lightDirection, f0: clearcoatF0, f90: clearcoatF90, roughness: clearcoatRoughness, normalView: clearcoatNormalView } ) ) );
}
reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness, iridescence: this.iridescence, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ) ) );
}
/**
* This method is intended for implementing the direct light term for
* rect area light nodes.
*
* @param {Object} input - The input data.
* @param {NodeBuilder} builder - The current node builder.
*/
directRectArea( { lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 }, /* builder */ ) {
const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction
const p1 = lightPosition.sub( halfWidth ).sub( halfHeight );
const p2 = lightPosition.sub( halfWidth ).add( halfHeight );
const p3 = lightPosition.add( halfWidth ).add( halfHeight );
const N = normalView;
const V = positionViewDirection;
const P = positionView.toVar();
const uv = LTC_Uv( { N, V, roughness } );
const t1 = ltc_1.sample( uv ).toVar();
const t2 = ltc_2.sample( uv ).toVar();
const mInv = mat3(
vec3( t1.x, 0, t1.y ),
vec3( 0, 1, 0 ),
vec3( t1.z, 0, t1.w )
).toVar();
// LTC Fresnel Approximation by Stephen Hill
// http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf
const fresnel = specularColor.mul( t2.x ).add( specularColor.oneMinus().mul( t2.y ) ).toVar();
reflectedLight.directSpecular.addAssign( lightColor.mul( fresnel ).mul( LTC_Evaluate( { N, V, P, mInv, p0, p1, p2, p3 } ) ) );
reflectedLight.directDiffuse.addAssign( lightColor.mul( diffuseColor ).mul( LTC_Evaluate( { N, V, P, mInv: mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), p0, p1, p2, p3 } ) ) );
}
/**
* Implements the indirect lighting.
*
* @param {NodeBuilder} builder - The current node builder.
*/
indirect( builder ) {
this.indirectDiffuse( builder );
this.indirectSpecular( builder );
this.ambientOcclusion( builder );
}
/**
* Implements the indirect diffuse term.
*
* @param {NodeBuilder} builder - The current node builder.
*/
indirectDiffuse( builder ) {
const { irradiance, reflectedLight } = builder.context;
reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );
}
/**
* Implements the indirect specular term.
*
* @param {NodeBuilder} builder - The current node builder.
*/
indirectSpecular( builder ) {
const { radiance, iblIrradiance, reflectedLight } = builder.context;
if ( this.sheen === true ) {
this.sheenSpecularIndirect.addAssign( iblIrradiance.mul(
sheen,
IBLSheenBRDF( {
normal: normalView,
viewDir: positionViewDirection,
roughness: sheenRoughness
} )
) );
}
if ( this.clearcoat === true ) {
const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
const clearcoatEnv = EnvironmentBRDF( {
dotNV: dotNVcc,
specularColor: clearcoatF0,
specularF90: clearcoatF90,
roughness: clearcoatRoughness
} );
this.clearcoatSpecularIndirect.addAssign( this.clearcoatRadiance.mul( clearcoatEnv ) );
}
// Both indirect specular and indirect diffuse light accumulate here
const singleScattering = vec3().toVar( 'singleScattering' );
const multiScattering = vec3().toVar( 'multiScattering' );
const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );
this.computeMultiscattering( singleScattering, multiScattering, specularF90 );
const totalScattering = singleScattering.add( multiScattering );
const diffuse = diffuseColor.mul( totalScattering.r.max( totalScattering.g ).max( totalScattering.b ).oneMinus() );
reflectedLight.indirectSpecular.addAssign( radiance.mul( singleScattering ) );
reflectedLight.indirectSpecular.addAssign( multiScattering.mul( cosineWeightedIrradiance ) );
reflectedLight.indirectDiffuse.addAssign( diffuse.mul( cosineWeightedIrradiance ) );
}
/**
* Implements the ambient occlusion term.
*
* @param {NodeBuilder} builder - The current node builder.
*/
ambientOcclusion( builder ) {
const { ambientOcclusion, reflectedLight } = builder.context;
const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
const aoNV = dotNV.add( ambientOcclusion );
const aoExp = roughness.mul( - 16.0 ).oneMinus().negate().exp2();
const aoNode = ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp();
if ( this.clearcoat === true ) {
this.clearcoatSpecularIndirect.mulAssign( ambientOcclusion );
}
if ( this.sheen === true ) {
this.sheenSpecularIndirect.mulAssign( ambientOcclusion );
}
reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
reflectedLight.indirectSpecular.mulAssign( aoNode );
}
/**
* Used for final lighting accumulations depending on the requested features.
*
* @param {NodeBuilder} builder - The current node builder.
*/
finish( { context } ) {
const { outgoingLight } = context;
if ( this.clearcoat === true ) {
const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
const Fcc = F_Schlick( {
dotVH: dotNVcc,
f0: clearcoatF0,
f90: clearcoatF90
} );
const clearcoatLight = outgoingLight.mul( clearcoat.mul( Fcc ).oneMinus() ).add( this.clearcoatSpecularDirect.add( this.clearcoatSpecularIndirect ).mul( clearcoat ) );
outgoingLight.assign( clearcoatLight );
}
if ( this.sheen === true ) {
const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus();
const sheenLight = outgoingLight.mul( sheenEnergyComp ).add( this.sheenSpecularDirect, this.sheenSpecularIndirect );
outgoingLight.assign( sheenLight );
}
}
}
Methods¶
start(builder: NodeBuilder): void
¶
Code
start( builder ) {
if ( this.clearcoat === true ) {
this.clearcoatRadiance = vec3().toVar( 'clearcoatRadiance' );
this.clearcoatSpecularDirect = vec3().toVar( 'clearcoatSpecularDirect' );
this.clearcoatSpecularIndirect = vec3().toVar( 'clearcoatSpecularIndirect' );
}
if ( this.sheen === true ) {
this.sheenSpecularDirect = vec3().toVar( 'sheenSpecularDirect' );
this.sheenSpecularIndirect = vec3().toVar( 'sheenSpecularIndirect' );
}
if ( this.iridescence === true ) {
const dotNVi = normalView.dot( positionViewDirection ).clamp();
this.iridescenceFresnel = evalIridescence( {
outsideIOR: float( 1.0 ),
eta2: iridescenceIOR,
cosTheta1: dotNVi,
thinFilmThickness: iridescenceThickness,
baseF0: specularColor
} );
this.iridescenceF0 = Schlick_to_F0( { f: this.iridescenceFresnel, f90: 1.0, dotVH: dotNVi } );
}
if ( this.transmission === true ) {
const position = positionWorld;
const v = cameraPosition.sub( positionWorld ).normalize(); // TODO: Create Node for this, same issue in MaterialX
const n = normalWorld;
const context = builder.context;
context.backdrop = getIBLVolumeRefraction(
n,
v,
roughness,
diffuseColor,
specularColor,
specularF90, // specularF90
position, // positionWorld
modelWorldMatrix, // modelMatrix
cameraViewMatrix, // viewMatrix
cameraProjectionMatrix, // projMatrix
ior,
thickness,
attenuationColor,
attenuationDistance,
this.dispersion ? dispersion : null
);
context.backdropAlpha = transmission;
diffuseColor.a.mulAssign( mix( 1, context.backdrop.a, transmission ) );
}
super.start( builder );
}
computeMultiscattering(singleScatter: any, multiScatter: any, specularF90: any): void
¶
Code
computeMultiscattering( singleScatter, multiScatter, specularF90 ) {
const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
const fab = DFGApprox( { roughness, dotNV } );
const Fr = this.iridescenceF0 ? iridescence.mix( specularColor, this.iridescenceF0 ) : specularColor;
const FssEss = Fr.mul( fab.x ).add( specularF90.mul( fab.y ) );
const Ess = fab.x.add( fab.y );
const Ems = Ess.oneMinus();
const Favg = specularColor.add( specularColor.oneMinus().mul( 0.047619 ) ); // 1/21
const Fms = FssEss.mul( Favg ).div( Ems.mul( Favg ).oneMinus() );
singleScatter.addAssign( FssEss );
multiScatter.addAssign( Fms.mul( Ems ) );
}
direct({ lightDirection, lightColor, reflectedLight }: any): void
¶
Code
direct( { lightDirection, lightColor, reflectedLight }, /* builder */ ) {
const dotNL = normalView.dot( lightDirection ).clamp();
const irradiance = dotNL.mul( lightColor );
if ( this.sheen === true ) {
this.sheenSpecularDirect.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) );
}
if ( this.clearcoat === true ) {
const dotNLcc = clearcoatNormalView.dot( lightDirection ).clamp();
const ccIrradiance = dotNLcc.mul( lightColor );
this.clearcoatSpecularDirect.addAssign( ccIrradiance.mul( BRDF_GGX( { lightDirection, f0: clearcoatF0, f90: clearcoatF90, roughness: clearcoatRoughness, normalView: clearcoatNormalView } ) ) );
}
reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness, iridescence: this.iridescence, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ) ) );
}
directRectArea({ lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 }: any): void
¶
Code
directRectArea( { lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 }, /* builder */ ) {
const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction
const p1 = lightPosition.sub( halfWidth ).sub( halfHeight );
const p2 = lightPosition.sub( halfWidth ).add( halfHeight );
const p3 = lightPosition.add( halfWidth ).add( halfHeight );
const N = normalView;
const V = positionViewDirection;
const P = positionView.toVar();
const uv = LTC_Uv( { N, V, roughness } );
const t1 = ltc_1.sample( uv ).toVar();
const t2 = ltc_2.sample( uv ).toVar();
const mInv = mat3(
vec3( t1.x, 0, t1.y ),
vec3( 0, 1, 0 ),
vec3( t1.z, 0, t1.w )
).toVar();
// LTC Fresnel Approximation by Stephen Hill
// http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf
const fresnel = specularColor.mul( t2.x ).add( specularColor.oneMinus().mul( t2.y ) ).toVar();
reflectedLight.directSpecular.addAssign( lightColor.mul( fresnel ).mul( LTC_Evaluate( { N, V, P, mInv, p0, p1, p2, p3 } ) ) );
reflectedLight.directDiffuse.addAssign( lightColor.mul( diffuseColor ).mul( LTC_Evaluate( { N, V, P, mInv: mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), p0, p1, p2, p3 } ) ) );
}
indirect(builder: NodeBuilder): void
¶
Code
indirectDiffuse(builder: NodeBuilder): void
¶
Code
indirectSpecular(builder: NodeBuilder): void
¶
Code
indirectSpecular( builder ) {
const { radiance, iblIrradiance, reflectedLight } = builder.context;
if ( this.sheen === true ) {
this.sheenSpecularIndirect.addAssign( iblIrradiance.mul(
sheen,
IBLSheenBRDF( {
normal: normalView,
viewDir: positionViewDirection,
roughness: sheenRoughness
} )
) );
}
if ( this.clearcoat === true ) {
const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
const clearcoatEnv = EnvironmentBRDF( {
dotNV: dotNVcc,
specularColor: clearcoatF0,
specularF90: clearcoatF90,
roughness: clearcoatRoughness
} );
this.clearcoatSpecularIndirect.addAssign( this.clearcoatRadiance.mul( clearcoatEnv ) );
}
// Both indirect specular and indirect diffuse light accumulate here
const singleScattering = vec3().toVar( 'singleScattering' );
const multiScattering = vec3().toVar( 'multiScattering' );
const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );
this.computeMultiscattering( singleScattering, multiScattering, specularF90 );
const totalScattering = singleScattering.add( multiScattering );
const diffuse = diffuseColor.mul( totalScattering.r.max( totalScattering.g ).max( totalScattering.b ).oneMinus() );
reflectedLight.indirectSpecular.addAssign( radiance.mul( singleScattering ) );
reflectedLight.indirectSpecular.addAssign( multiScattering.mul( cosineWeightedIrradiance ) );
reflectedLight.indirectDiffuse.addAssign( diffuse.mul( cosineWeightedIrradiance ) );
}
ambientOcclusion(builder: NodeBuilder): void
¶
Code
ambientOcclusion( builder ) {
const { ambientOcclusion, reflectedLight } = builder.context;
const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV
const aoNV = dotNV.add( ambientOcclusion );
const aoExp = roughness.mul( - 16.0 ).oneMinus().negate().exp2();
const aoNode = ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp();
if ( this.clearcoat === true ) {
this.clearcoatSpecularIndirect.mulAssign( ambientOcclusion );
}
if ( this.sheen === true ) {
this.sheenSpecularIndirect.mulAssign( ambientOcclusion );
}
reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion );
reflectedLight.indirectSpecular.mulAssign( aoNode );
}
finish({ context }: any): void
¶
Code
finish( { context } ) {
const { outgoingLight } = context;
if ( this.clearcoat === true ) {
const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp();
const Fcc = F_Schlick( {
dotVH: dotNVcc,
f0: clearcoatF0,
f90: clearcoatF90
} );
const clearcoatLight = outgoingLight.mul( clearcoat.mul( Fcc ).oneMinus() ).add( this.clearcoatSpecularDirect.add( this.clearcoatSpecularIndirect ).mul( clearcoat ) );
outgoingLight.assign( clearcoatLight );
}
if ( this.sheen === true ) {
const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus();
const sheenLight = outgoingLight.mul( sheenEnergyComp ).add( this.sheenSpecularDirect, this.sheenSpecularIndirect );
outgoingLight.assign( sheenLight );
}
}