Buckets:
| import BRDF_Lambert from './BSDF/BRDF_Lambert.js'; | |
| import BRDF_GGX from './BSDF/BRDF_GGX.js'; | |
| import DFGApprox from './BSDF/DFGApprox.js'; | |
| import EnvironmentBRDF from './BSDF/EnvironmentBRDF.js'; | |
| import F_Schlick from './BSDF/F_Schlick.js'; | |
| import Schlick_to_F0 from './BSDF/Schlick_to_F0.js'; | |
| import BRDF_Sheen from './BSDF/BRDF_Sheen.js'; | |
| import { LTC_Evaluate, LTC_Uv } from './BSDF/LTC.js'; | |
| import LightingModel from '../core/LightingModel.js'; | |
| import { diffuseColor, specularColor, specularF90, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, ior, thickness, transmission, attenuationDistance, attenuationColor, dispersion } from '../core/PropertyNode.js'; | |
| import { transformedNormalView, transformedClearcoatNormalView, transformedNormalWorld } from '../accessors/Normal.js'; | |
| import { positionViewDirection, positionView, positionWorld } from '../accessors/Position.js'; | |
| import { Fn, float, vec2, vec3, vec4, mat3, If } from '../tsl/TSLBase.js'; | |
| import { select } from '../math/ConditionalNode.js'; | |
| import { mix, normalize, refract, length, clamp, log2, log, exp, smoothstep } from '../math/MathNode.js'; | |
| import { div } from '../math/OperatorNode.js'; | |
| import { cameraPosition, cameraProjectionMatrix, cameraViewMatrix } from '../accessors/Camera.js'; | |
| import { modelWorldMatrix } from '../accessors/ModelNode.js'; | |
| import { screenSize } from '../display/ScreenNode.js'; | |
| import { viewportMipTexture } from '../display/ViewportTextureNode.js'; | |
| import { textureBicubic } from '../accessors/TextureBicubic.js'; | |
| import { Loop } from '../utils/LoopNode.js'; | |
| import { BackSide } from '../../constants.js'; | |
| // | |
| // Transmission | |
| // | |
| const getVolumeTransmissionRay = /*@__PURE__*/ Fn( ( [ n, v, thickness, ior, modelMatrix ] ) => { | |
| // Direction of refracted light. | |
| const refractionVector = vec3( refract( v.negate(), normalize( n ), div( 1.0, ior ) ) ); | |
| // Compute rotation-independent scaling of the model matrix. | |
| const modelScale = vec3( | |
| length( modelMatrix[ 0 ].xyz ), | |
| length( modelMatrix[ 1 ].xyz ), | |
| length( modelMatrix[ 2 ].xyz ) | |
| ); | |
| // The thickness is specified in local space. | |
| return normalize( refractionVector ).mul( thickness.mul( modelScale ) ); | |
| } ).setLayout( { | |
| name: 'getVolumeTransmissionRay', | |
| type: 'vec3', | |
| inputs: [ | |
| { name: 'n', type: 'vec3' }, | |
| { name: 'v', type: 'vec3' }, | |
| { name: 'thickness', type: 'float' }, | |
| { name: 'ior', type: 'float' }, | |
| { name: 'modelMatrix', type: 'mat4' } | |
| ] | |
| } ); | |
| const applyIorToRoughness = /*@__PURE__*/ Fn( ( [ roughness, ior ] ) => { | |
| // Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and | |
| // an IOR of 1.5 results in the default amount of microfacet refraction. | |
| return roughness.mul( clamp( ior.mul( 2.0 ).sub( 2.0 ), 0.0, 1.0 ) ); | |
| } ).setLayout( { | |
| name: 'applyIorToRoughness', | |
| type: 'float', | |
| inputs: [ | |
| { name: 'roughness', type: 'float' }, | |
| { name: 'ior', type: 'float' } | |
| ] | |
| } ); | |
| const viewportBackSideTexture = /*@__PURE__*/ viewportMipTexture(); | |
| const viewportFrontSideTexture = /*@__PURE__*/ viewportMipTexture(); | |
| const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { material } ) => { | |
| const vTexture = material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture; | |
| const transmissionSample = vTexture.sample( fragCoord ); | |
| //const transmissionSample = viewportMipTexture( fragCoord ); | |
| const lod = log2( screenSize.x ).mul( applyIorToRoughness( roughness, ior ) ); | |
| return textureBicubic( transmissionSample, lod ); | |
| } ); | |
| const volumeAttenuation = /*@__PURE__*/ Fn( ( [ transmissionDistance, attenuationColor, attenuationDistance ] ) => { | |
| If( attenuationDistance.notEqual( 0 ), () => { | |
| // Compute light attenuation using Beer's law. | |
| const attenuationCoefficient = log( attenuationColor ).negate().div( attenuationDistance ); | |
| const transmittance = exp( attenuationCoefficient.negate().mul( transmissionDistance ) ); | |
| return transmittance; | |
| } ); | |
| // Attenuation distance is +∞, i.e. the transmitted color is not attenuated at all. | |
| return vec3( 1.0 ); | |
| } ).setLayout( { | |
| name: 'volumeAttenuation', | |
| type: 'vec3', | |
| inputs: [ | |
| { name: 'transmissionDistance', type: 'float' }, | |
| { name: 'attenuationColor', type: 'vec3' }, | |
| { name: 'attenuationDistance', type: 'float' } | |
| ] | |
| } ); | |
| const getIBLVolumeRefraction = /*@__PURE__*/ Fn( ( [ n, v, roughness, diffuseColor, specularColor, specularF90, position, modelMatrix, viewMatrix, projMatrix, ior, thickness, attenuationColor, attenuationDistance, dispersion ] ) => { | |
| let transmittedLight, transmittance; | |
| if ( dispersion ) { | |
| transmittedLight = vec4().toVar(); | |
| transmittance = vec3().toVar(); | |
| const halfSpread = ior.sub( 1.0 ).mul( dispersion.mul( 0.025 ) ); | |
| const iors = vec3( ior.sub( halfSpread ), ior, ior.add( halfSpread ) ); | |
| Loop( { start: 0, end: 3 }, ( { i } ) => { | |
| const ior = iors.element( i ); | |
| const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); | |
| const refractedRayExit = position.add( transmissionRay ); | |
| // Project refracted vector on the framebuffer, while mapping to normalized device coordinates. | |
| const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) ); | |
| const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar(); | |
| refractionCoords.addAssign( 1.0 ); | |
| refractionCoords.divAssign( 2.0 ); | |
| refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu | |
| // Sample framebuffer to get pixel the refracted ray hits. | |
| const transmissionSample = getTransmissionSample( refractionCoords, roughness, ior ); | |
| transmittedLight.element( i ).assign( transmissionSample.element( i ) ); | |
| transmittedLight.a.addAssign( transmissionSample.a ); | |
| transmittance.element( i ).assign( diffuseColor.element( i ).mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ).element( i ) ) ); | |
| } ); | |
| transmittedLight.a.divAssign( 3.0 ); | |
| } else { | |
| const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); | |
| const refractedRayExit = position.add( transmissionRay ); | |
| // Project refracted vector on the framebuffer, while mapping to normalized device coordinates. | |
| const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) ); | |
| const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar(); | |
| refractionCoords.addAssign( 1.0 ); | |
| refractionCoords.divAssign( 2.0 ); | |
| refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu | |
| // Sample framebuffer to get pixel the refracted ray hits. | |
| transmittedLight = getTransmissionSample( refractionCoords, roughness, ior ); | |
| transmittance = diffuseColor.mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ) ); | |
| } | |
| const attenuatedColor = transmittance.rgb.mul( transmittedLight.rgb ); | |
| const dotNV = n.dot( v ).clamp(); | |
| // Get the specular component. | |
| const F = vec3( EnvironmentBRDF( { // n, v, specularColor, specularF90, roughness | |
| dotNV, | |
| specularColor, | |
| specularF90, | |
| roughness | |
| } ) ); | |
| // As less light is transmitted, the opacity should be increased. This simple approximation does a decent job | |
| // of modulating a CSS background, and has no effect when the buffer is opaque, due to a solid object or clear color. | |
| const transmittanceFactor = transmittance.r.add( transmittance.g, transmittance.b ).div( 3.0 ); | |
| return vec4( F.oneMinus().mul( attenuatedColor ), transmittedLight.a.oneMinus().mul( transmittanceFactor ).oneMinus() ); | |
| } ); | |
| // | |
| // Iridescence | |
| // | |
| // XYZ to linear-sRGB color space | |
| const XYZ_TO_REC709 = /*@__PURE__*/ mat3( | |
| 3.2404542, - 0.9692660, 0.0556434, | |
| - 1.5371385, 1.8760108, - 0.2040259, | |
| - 0.4985314, 0.0415560, 1.0572252 | |
| ); | |
| // Assume air interface for top | |
| // Note: We don't handle the case fresnel0 == 1 | |
| const Fresnel0ToIor = ( fresnel0 ) => { | |
| const sqrtF0 = fresnel0.sqrt(); | |
| return vec3( 1.0 ).add( sqrtF0 ).div( vec3( 1.0 ).sub( sqrtF0 ) ); | |
| }; | |
| // ior is a value between 1.0 and 3.0. 1.0 is air interface | |
| const IorToFresnel0 = ( transmittedIor, incidentIor ) => { | |
| return transmittedIor.sub( incidentIor ).div( transmittedIor.add( incidentIor ) ).pow2(); | |
| }; | |
| // Fresnel equations for dielectric/dielectric interfaces. | |
| // Ref: https://belcour.github.io/blog/research/2017/05/01/brdf-thin-film.html | |
| // Evaluation XYZ sensitivity curves in Fourier space | |
| const evalSensitivity = ( 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; | |
| }; | |
| const evalIridescence = /*@__PURE__*/ Fn( ( { outsideIOR, eta2, cosTheta1, thinFilmThickness, baseF0 } ) => { | |
| // Force iridescenceIOR -> outsideIOR when thinFilmThickness -> 0.0 | |
| const iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) ); | |
| // Evaluate the cosTheta on the base layer (Snell law) | |
| const sinTheta2Sq = outsideIOR.div( iridescenceIOR ).pow2().mul( cosTheta1.pow2().oneMinus() ); | |
| // Handle TIR: | |
| const cosTheta2Sq = sinTheta2Sq.oneMinus(); | |
| If( cosTheta2Sq.lessThan( 0 ), () => { | |
| return vec3( 1.0 ); | |
| } ); | |
| const cosTheta2 = cosTheta2Sq.sqrt(); | |
| // First interface | |
| const R0 = IorToFresnel0( iridescenceIOR, outsideIOR ); | |
| const R12 = F_Schlick( { f0: R0, f90: 1.0, dotVH: cosTheta1 } ); | |
| //const R21 = R12; | |
| const T121 = R12.oneMinus(); | |
| const phi12 = iridescenceIOR.lessThan( outsideIOR ).select( Math.PI, 0.0 ); | |
| const phi21 = float( Math.PI ).sub( phi12 ); | |
| // Second interface | |
| const baseIOR = Fresnel0ToIor( baseF0.clamp( 0.0, 0.9999 ) ); // guard against 1.0 | |
| const R1 = IorToFresnel0( baseIOR, iridescenceIOR.toVec3() ); | |
| const R23 = F_Schlick( { f0: R1, f90: 1.0, dotVH: cosTheta2 } ); | |
| const phi23 = vec3( | |
| baseIOR.x.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ), | |
| baseIOR.y.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ), | |
| baseIOR.z.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ) | |
| ); | |
| // Phase shift | |
| const OPD = iridescenceIOR.mul( thinFilmThickness, cosTheta2, 2.0 ); | |
| const phi = vec3( phi21 ).add( phi23 ); | |
| // Compound terms | |
| const R123 = R12.mul( R23 ).clamp( 1e-5, 0.9999 ); | |
| const r123 = R123.sqrt(); | |
| const Rs = T121.pow2().mul( R23 ).div( vec3( 1.0 ).sub( R123 ) ); | |
| // Reflectance term for m = 0 (DC term amplitude) | |
| const C0 = R12.add( Rs ); | |
| const I = C0.toVar(); | |
| // Reflectance term for m > 0 (pairs of diracs) | |
| const Cm = Rs.sub( T121 ).toVar(); | |
| Loop( { start: 1, end: 2, condition: '<=', name: 'm' }, ( { m } ) => { | |
| Cm.mulAssign( r123 ); | |
| const Sm = evalSensitivity( float( m ).mul( OPD ), float( m ).mul( phi ) ).mul( 2.0 ); | |
| I.addAssign( Cm.mul( Sm ) ); | |
| } ); | |
| // Since out of gamut colors might be produced, negative color values are clamped to 0. | |
| return I.max( vec3( 0.0 ) ); | |
| } ).setLayout( { | |
| name: 'evalIridescence', | |
| type: 'vec3', | |
| inputs: [ | |
| { name: 'outsideIOR', type: 'float' }, | |
| { name: 'eta2', type: 'float' }, | |
| { name: 'cosTheta1', type: 'float' }, | |
| { name: 'thinFilmThickness', type: 'float' }, | |
| { name: 'baseF0', type: 'vec3' } | |
| ] | |
| } ); | |
| // | |
| // Sheen | |
| // | |
| // This is a curve-fit approximation to the "Charlie sheen" BRDF integrated over the hemisphere from | |
| // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found | |
| // in the Sheen section of https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing | |
| const IBLSheenBRDF = /*@__PURE__*/ Fn( ( { normal, viewDir, roughness } ) => { | |
| const dotNV = normal.dot( viewDir ).saturate(); | |
| const r2 = roughness.pow2(); | |
| const a = select( | |
| roughness.lessThan( 0.25 ), | |
| float( - 339.2 ).mul( r2 ).add( float( 161.4 ).mul( roughness ) ).sub( 25.9 ), | |
| float( - 8.48 ).mul( r2 ).add( float( 14.3 ).mul( roughness ) ).sub( 9.95 ) | |
| ); | |
| const b = select( | |
| roughness.lessThan( 0.25 ), | |
| float( 44.0 ).mul( r2 ).sub( float( 23.7 ).mul( roughness ) ).add( 3.26 ), | |
| float( 1.97 ).mul( r2 ).sub( float( 3.27 ).mul( roughness ) ).add( 0.72 ) | |
| ); | |
| const DG = select( roughness.lessThan( 0.25 ), 0.0, float( 0.1 ).mul( roughness ).sub( 0.025 ) ).add( a.mul( dotNV ).add( b ).exp() ); | |
| return DG.mul( 1.0 / Math.PI ).saturate(); | |
| } ); | |
| const clearcoatF0 = vec3( 0.04 ); | |
| const clearcoatF90 = float( 1 ); | |
| /** | |
| * Represents the lighting model for a PBR material. | |
| * | |
| * @augments LightingModel | |
| */ | |
| 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 = transformedNormalView.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 = transformedNormalWorld; | |
| 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 = transformedNormalView.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 } ) { | |
| const dotNL = transformedNormalView.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 = transformedClearcoatNormalView.dot( lightDirection ).clamp(); | |
| const ccIrradiance = dotNLcc.mul( lightColor ); | |
| this.clearcoatSpecularDirect.addAssign( ccIrradiance.mul( BRDF_GGX( { lightDirection, f0: clearcoatF0, f90: clearcoatF90, roughness: clearcoatRoughness, normalView: transformedClearcoatNormalView } ) ) ); | |
| } | |
| 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 } ) { | |
| 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 = transformedNormalView; | |
| 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: transformedNormalView, | |
| viewDir: positionViewDirection, | |
| roughness: sheenRoughness | |
| } ) | |
| ) ); | |
| } | |
| if ( this.clearcoat === true ) { | |
| const dotNVcc = transformedClearcoatNormalView.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 = transformedNormalView.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 = transformedClearcoatNormalView.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 ); | |
| } | |
| } | |
| } | |
| export default PhysicalLightingModel; | |
Xet Storage Details
- Size:
- 25 kB
- Xet hash:
- 185b853fc32d7c225ad254859bf0df7c376f27c558542bbd57a8908c966a1482
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.