UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

740 lines (684 loc) 25.4 kB
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;