Kaynağa Gözat

WebGPURenderer: Iridescence (#26489)

* LightingModel: New class struct

* cleanup

* cleanup

* AONode: Fix float value

* SpriteNodeMaterial: Force float type in rotation

* PhysicalLightingModel: Add iridescence

* Add webgpu_loader_gltf_iridescence example
sunag 2 yıl önce
ebeveyn
işleme
ffd9884ecd

+ 1 - 0
examples/files.json

@@ -325,6 +325,7 @@
 		"webgpu_lights_selective",
 		"webgpu_lights_selective",
 		"webgpu_loader_gltf",
 		"webgpu_loader_gltf",
 		"webgpu_loader_gltf_compressed",
 		"webgpu_loader_gltf_compressed",
+		"webgpu_loader_gltf_iridescence",
 		"webgpu_loader_gltf_sheen",
 		"webgpu_loader_gltf_sheen",
 		"webgpu_materials",
 		"webgpu_materials",
 		"webgpu_materials_video",
 		"webgpu_materials_video",

+ 27 - 41
examples/jsm/nodes/accessors/MaterialNode.js

@@ -1,4 +1,5 @@
 import Node, { addNodeClass } from '../core/Node.js';
 import Node, { addNodeClass } from '../core/Node.js';
+import { reference } from './ReferenceNode.js';
 import { materialReference } from './MaterialReferenceNode.js';
 import { materialReference } from './MaterialReferenceNode.js';
 import { nodeImmutable, float } from '../shadernode/ShaderNode.js';
 import { nodeImmutable, float } from '../shadernode/ShaderNode.js';
 
 
@@ -12,31 +13,6 @@ class MaterialNode extends Node {
 
 
 	}
 	}
 
 
-	getNodeType( builder ) {
-
-		const scope = this.scope;
-		const material = builder.context.material;
-
-		if ( scope === MaterialNode.COLOR ) {
-
-			return material.map !== null ? 'vec4' : 'vec3';
-
-		} else if ( scope === MaterialNode.OPACITY || scope === MaterialNode.ROTATION ) {
-
-			return 'float';
-
-		} else if ( scope === MaterialNode.EMISSIVE || scope === MaterialNode.SHEEN ) {
-
-			return 'vec3';
-
-		} else if ( scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS || scope === MaterialNode.SPECULAR || scope === MaterialNode.SHININESS || scope === MaterialNode.CLEARCOAT_ROUGHNESS || scope === MaterialNode.SHEEN_ROUGHNESS ) {
-
-			return 'float';
-
-		}
-
-	}
-
 	getFloat( property ) {
 	getFloat( property ) {
 
 
 		//@TODO: Check if it can be cached by property name.
 		//@TODO: Check if it can be cached by property name.
@@ -70,9 +46,13 @@ class MaterialNode extends Node {
 
 
 		let node = null;
 		let node = null;
 
 
-		if ( scope === MaterialNode.ALPHA_TEST ) {
+		if ( scope === MaterialNode.ALPHA_TEST || scope === MaterialNode.SHININESS || scope === MaterialNode.REFLECTIVITY || scope === MaterialNode.ROTATION || scope === MaterialNode.IRIDESCENCE || scope === MaterialNode.IRIDESCENCE_IOR ) {
+
+			node = this.getFloat( scope );
+
+		} else if ( scope === MaterialNode.SPECULAR_COLOR ) {
 
 
-			node = this.getFloat( 'alphaTest' );
+			node = this.getColor( 'specular' );
 
 
 		} else if ( scope === MaterialNode.COLOR ) {
 		} else if ( scope === MaterialNode.COLOR ) {
 
 
@@ -102,14 +82,6 @@ class MaterialNode extends Node {
 
 
 			}
 			}
 
 
-		} else if ( scope === MaterialNode.SHININESS ) {
-
-			node = this.getFloat( 'shininess' );
-
-		} else if ( scope === MaterialNode.SPECULAR_COLOR ) {
-
-			node = this.getColor( 'specular' );
-
 		} else if ( scope === MaterialNode.SPECULAR_STRENGTH ) {
 		} else if ( scope === MaterialNode.SPECULAR_STRENGTH ) {
 
 
 			if ( material.specularMap && material.specularMap.isTexture === true ) {
 			if ( material.specularMap && material.specularMap.isTexture === true ) {
@@ -122,10 +94,6 @@ class MaterialNode extends Node {
 
 
 			}
 			}
 
 
-		} else if ( scope === MaterialNode.REFLECTIVITY ) {
-
-			node = this.getFloat( 'reflectivity' );
-
 		} else if ( scope === MaterialNode.ROUGHNESS ) {
 		} else if ( scope === MaterialNode.ROUGHNESS ) {
 
 
 			const roughnessNode = this.getFloat( 'roughness' );
 			const roughnessNode = this.getFloat( 'roughness' );
@@ -226,9 +194,21 @@ class MaterialNode extends Node {
 
 
 			node = node.clamp( 0.07, 1.0 );
 			node = node.clamp( 0.07, 1.0 );
 
 
-		} else if ( scope === MaterialNode.ROTATION ) {
+		} else if ( scope === MaterialNode.IRIDESCENCE_THICKNESS ) {
+
+			const iridescenceThicknessMaximum = reference( 1, 'float', material.iridescenceThicknessRange );
+
+			if ( material.iridescenceThicknessMap ) {
+
+				const iridescenceThicknessMinimum = reference( 0, 'float', material.iridescenceThicknessRange );
+
+				node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( 'iridescenceThicknessMap' ).g ).add( iridescenceThicknessMinimum );
 
 
-			node = this.getFloat( 'rotation' );
+			} else {
+
+				node = iridescenceThicknessMaximum;
+
+			}
 
 
 		} else {
 		} else {
 
 
@@ -259,6 +239,9 @@ MaterialNode.EMISSIVE = 'emissive';
 MaterialNode.ROTATION = 'rotation';
 MaterialNode.ROTATION = 'rotation';
 MaterialNode.SHEEN = 'sheen';
 MaterialNode.SHEEN = 'sheen';
 MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness';
 MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness';
+MaterialNode.IRIDESCENCE = 'iridescence';
+MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR';
+MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness';
 
 
 export default MaterialNode;
 export default MaterialNode;
 
 
@@ -277,5 +260,8 @@ export const materialClearcoatRoughness = nodeImmutable( MaterialNode, MaterialN
 export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
 export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
 export const materialSheen = nodeImmutable( MaterialNode, MaterialNode.SHEEN );
 export const materialSheen = nodeImmutable( MaterialNode, MaterialNode.SHEEN );
 export const materialSheenRoughness = nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS );
 export const materialSheenRoughness = nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS );
+export const materialIridescence = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE );
+export const materialIridescenceIOR = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR );
+export const materialIridescenceThickness = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS );
 
 
 addNodeClass( MaterialNode );
 addNodeClass( MaterialNode );

+ 3 - 0
examples/jsm/nodes/core/PropertyNode.js

@@ -51,6 +51,9 @@ export const clearcoat = nodeImmutable( PropertyNode, 'float', 'Clearcoat' );
 export const clearcoatRoughness = nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' );
 export const clearcoatRoughness = nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' );
 export const sheen = nodeImmutable( PropertyNode, 'vec3', 'Sheen' );
 export const sheen = nodeImmutable( PropertyNode, 'vec3', 'Sheen' );
 export const sheenRoughness = nodeImmutable( PropertyNode, 'float', 'SheenRoughness' );
 export const sheenRoughness = nodeImmutable( PropertyNode, 'float', 'SheenRoughness' );
+export const iridescence = nodeImmutable( PropertyNode, 'float', 'Iridescence' );
+export const iridescenceIOR = nodeImmutable( PropertyNode, 'float', 'IridescenceIOR' );
+export const iridescenceThickness = nodeImmutable( PropertyNode, 'float', 'IridescenceThickness' );
 export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
 export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
 export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' );
 export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' );
 export const output = nodeImmutable( PropertyNode, 'vec4', 'Output' );
 export const output = nodeImmutable( PropertyNode, 'vec4', 'Output' );

+ 10 - 2
examples/jsm/nodes/functions/BSDF/BRDF_GGX.js

@@ -3,12 +3,13 @@ import V_GGX_SmithCorrelated from './V_GGX_SmithCorrelated.js';
 import D_GGX from './D_GGX.js';
 import D_GGX from './D_GGX.js';
 import { transformedNormalView } from '../../accessors/NormalNode.js';
 import { transformedNormalView } from '../../accessors/NormalNode.js';
 import { positionViewDirection } from '../../accessors/PositionNode.js';
 import { positionViewDirection } from '../../accessors/PositionNode.js';
+import { iridescence } from '../../core/PropertyNode.js';
 import { tslFn } from '../../shadernode/ShaderNode.js';
 import { tslFn } from '../../shadernode/ShaderNode.js';
 
 
 // GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
 // GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
 const BRDF_GGX = tslFn( ( inputs ) => {
 const BRDF_GGX = tslFn( ( inputs ) => {
 
 
-	const { lightDirection, f0, f90, roughness } = inputs;
+	const { lightDirection, f0, f90, roughness, iridescenceFresnel } = inputs;
 
 
 	const normalView = inputs.normalView || transformedNormalView;
 	const normalView = inputs.normalView || transformedNormalView;
 
 
@@ -21,7 +22,14 @@ const BRDF_GGX = tslFn( ( inputs ) => {
 	const dotNH = normalView.dot( halfDir ).clamp();
 	const dotNH = normalView.dot( halfDir ).clamp();
 	const dotVH = positionViewDirection.dot( halfDir ).clamp();
 	const dotVH = positionViewDirection.dot( halfDir ).clamp();
 
 
-	const F = F_Schlick( { f0, f90, dotVH } );
+	let F = F_Schlick( { f0, f90, dotVH } );
+
+	if ( iridescenceFresnel ) {
+
+		F = iridescence.mix( F, iridescenceFresnel );
+
+	}
+
 	const V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } );
 	const V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } );
 	const D = D_GGX( { alpha, dotNH } );
 	const D = D_GGX( { alpha, dotNH } );
 
 

+ 13 - 0
examples/jsm/nodes/functions/BSDF/Schlick_to_F0.js

@@ -0,0 +1,13 @@
+import { tslFn, vec3 } from '../../shadernode/ShaderNode.js';
+
+const Schlick_to_F0 = tslFn( ( { f, f90, dotVH } ) => {
+
+	const x = dotVH.oneMinus().saturate();
+	const x2 = x.mul( x );
+	const x5 = x.mul( x2, x2 ).clamp( 0, .9999 );
+
+	return f.sub( vec3( f90 ).mul( x5 ) ).div( x5.oneMinus() );
+
+} );
+
+export default Schlick_to_F0;

+ 157 - 26
examples/jsm/nodes/functions/PhysicalLightingModel.js

@@ -3,16 +3,129 @@ import BRDF_GGX from './BSDF/BRDF_GGX.js';
 import DFGApprox from './BSDF/DFGApprox.js';
 import DFGApprox from './BSDF/DFGApprox.js';
 import EnvironmentBRDF from './BSDF/EnvironmentBRDF.js';
 import EnvironmentBRDF from './BSDF/EnvironmentBRDF.js';
 import F_Schlick from './BSDF/F_Schlick.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 BRDF_Sheen from './BSDF/BRDF_Sheen.js';
 import LightingModel from '../core/LightingModel.js';
 import LightingModel from '../core/LightingModel.js';
-import { diffuseColor, specularColor, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness } from '../core/PropertyNode.js';
+import { diffuseColor, specularColor, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness } from '../core/PropertyNode.js';
 import { transformedNormalView, transformedClearcoatNormalView } from '../accessors/NormalNode.js';
 import { transformedNormalView, transformedClearcoatNormalView } from '../accessors/NormalNode.js';
 import { positionViewDirection } from '../accessors/PositionNode.js';
 import { positionViewDirection } from '../accessors/PositionNode.js';
-import { float, vec3 } from '../shadernode/ShaderNode.js';
+import { float, vec3, mat3 } from '../shadernode/ShaderNode.js';
 import { cond } from '../math/CondNode.js';
 import { cond } from '../math/CondNode.js';
+import { mix, smoothstep } from '../math/MathNode.js';
 
 
-const clearcoatF0 = vec3( 0.04 );
-const clearcoatF90 = vec3( 1 );
+//
+// Iridescence
+//
+
+// XYZ to linear-sRGB color space
+const XYZ_TO_REC709 = 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 = ( 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( float( 1 ).sub( cosTheta1.pow2() ) );
+
+	// Handle TIR:
+	const cosTheta2Sq = float( 1 ).sub( sinTheta2Sq );
+	/*if ( cosTheta2Sq < 0.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 ).cond( 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.vec3() );
+	const R23 = F_Schlick( { f0: R1, f90: 1.0, dotVH: cosTheta2 } );
+	const phi23 = vec3(
+		baseIOR.x.lessThan( iridescenceIOR ).cond( Math.PI, 0.0 ),
+		baseIOR.y.lessThan( iridescenceIOR ).cond( Math.PI, 0.0 ),
+		baseIOR.z.lessThan( iridescenceIOR ).cond( 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 );
+	let I = C0;
+
+	// Reflectance term for m > 0 (pairs of diracs)
+	let Cm = Rs.sub( T121 );
+	for ( let m = 1; m <= 2; ++ m ) {
+
+		Cm = Cm.mul( r123 );
+		const Sm = evalSensitivity( float( m ).mul( OPD ), float( m ).mul( phi ) ).mul( 2.0 );
+		I = I.add( Cm.mul( Sm ) );
+
+	}
+
+	// Since out of gamut colors might be produced, negative color values are clamped to 0.
+	return I.max( vec3( 0.0 ) );
+
+};
+
+//
+//	Sheen
+//
 
 
 // This is a curve-fit approxmation to the "Charlie sheen" BRDF integrated over the hemisphere from
 // This is a curve-fit approxmation to the "Charlie sheen" BRDF integrated over the hemisphere from
 // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found
 // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found
@@ -41,40 +154,26 @@ const IBLSheenBRDF = ( normal, viewDir, roughness ) => {
 
 
 };
 };
 
 
-// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
-// Approximates multiscattering in order to preserve energy.
-// http://www.jcgt.org/published/0008/01/03/
-const computeMultiscattering = ( singleScatter, multiScatter, specularF90 = float( 1 ) ) => {
-
-	const fab = DFGApprox( { roughness } );
-
-	const FssEss = specularColor.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 ) );
-
-};
+const clearcoatF0 = vec3( 0.04 );
+const clearcoatF90 = vec3( 1 );
 
 
 //
 //
 
 
 class PhysicalLightingModel extends LightingModel {
 class PhysicalLightingModel extends LightingModel {
 
 
-	constructor( clearcoat = true, sheen = true ) {
+	constructor( clearcoat = true, sheen = true, iridescence = true ) {
 
 
 		super();
 		super();
 
 
 		this.clearcoat = clearcoat;
 		this.clearcoat = clearcoat;
 		this.sheen = sheen;
 		this.sheen = sheen;
+		this.iridescence = iridescence;
 
 
 		this.clearcoatRadiance = null;
 		this.clearcoatRadiance = null;
 		this.clearcoatSpecular = null;
 		this.clearcoatSpecular = null;
 		this.sheenSpecular = null;
 		this.sheenSpecular = null;
+		this.iridescenceFresnel = null;
+		this.iridescenceF0 = null;
 
 
 	}
 	}
 
 
@@ -113,6 +212,38 @@ class PhysicalLightingModel extends LightingModel {
 
 
 		}
 		}
 
 
+		if ( this.iridescence === true ) {
+
+			const dotNVi = transformedNormalView.dot( positionViewDirection ).clamp();
+
+			this.iridescenceFresnel = evalIridescence( float( 1.0 ), iridescenceIOR, dotNVi, iridescenceThickness, specularColor );
+			this.iridescenceF0 = Schlick_to_F0( { f: this.iridescenceFresnel, f90: 1.0, dotVH: dotNVi } );
+
+		}
+
+	}
+
+	// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
+	// Approximates multiscattering in order to preserve energy.
+	// http://www.jcgt.org/published/0008/01/03/
+
+	computeMultiscattering( singleScatter, multiScatter, specularF90 = float( 1 ) ) {
+
+		const fab = DFGApprox( { roughness } );
+
+		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 } ) {
 	direct( { lightDirection, lightColor, reflectedLight } ) {
@@ -137,7 +268,7 @@ class PhysicalLightingModel extends LightingModel {
 
 
 		reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
 		reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
 
 
-		reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness } ) ) );
+		reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness, iridescence: this.iridescence, iridescenceFresnel: this.iridescenceFresnel } ) ) );
 
 
 	}
 	}
 
 
@@ -179,7 +310,7 @@ class PhysicalLightingModel extends LightingModel {
 		const multiScattering = vec3().temp();
 		const multiScattering = vec3().temp();
 		const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );
 		const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI );
 
 
-		computeMultiscattering( singleScattering, multiScattering );
+		this.computeMultiscattering( singleScattering, multiScattering );
 
 
 		const totalScattering = singleScattering.add( multiScattering );
 		const totalScattering = singleScattering.add( multiScattering );
 
 

+ 1 - 1
examples/jsm/nodes/lighting/AONode.js

@@ -14,7 +14,7 @@ class AONode extends LightingNode {
 	construct( builder ) {
 	construct( builder ) {
 
 
 		const aoIntensity = 1;
 		const aoIntensity = 1;
-		const aoNode = this.aoNode.sub( 1.0 ).mul( aoIntensity ).add( 1.0 );
+		const aoNode = this.aoNode.x.sub( 1.0 ).mul( aoIntensity ).add( 1.0 );
 
 
 		builder.context.ambientOcclusion.mulAssign( aoNode );
 		builder.context.ambientOcclusion.mulAssign( aoNode );
 
 

+ 13 - 3
examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js

@@ -1,8 +1,8 @@
 import { addNodeMaterial } from './NodeMaterial.js';
 import { addNodeMaterial } from './NodeMaterial.js';
 import { transformedClearcoatNormalView } from '../accessors/NormalNode.js';
 import { transformedClearcoatNormalView } from '../accessors/NormalNode.js';
-import { clearcoat, clearcoatRoughness, sheen, sheenRoughness } from '../core/PropertyNode.js';
+import { clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness } from '../core/PropertyNode.js';
 import { materialClearcoatNormal } from '../accessors/ExtendedMaterialNode.js';
 import { materialClearcoatNormal } from '../accessors/ExtendedMaterialNode.js';
-import { materialClearcoat, materialClearcoatRoughness, materialSheen, materialSheenRoughness } from '../accessors/MaterialNode.js';
+import { materialClearcoat, materialClearcoatRoughness, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness } from '../accessors/MaterialNode.js';
 import { float, vec3 } from '../shadernode/ShaderNode.js';
 import { float, vec3 } from '../shadernode/ShaderNode.js';
 import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
 import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
@@ -46,7 +46,7 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 
 
 	constructLightingModel( /*builder*/ ) {
 	constructLightingModel( /*builder*/ ) {
 
 
-		return new PhysicalLightingModel();
+		return new PhysicalLightingModel(); // @TODO: Optimize shader using parameters.
 
 
 	}
 	}
 
 
@@ -72,6 +72,16 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 		stack.assign( sheen, sheenNode );
 		stack.assign( sheen, sheenNode );
 		stack.assign( sheenRoughness, sheenRoughnessNode );
 		stack.assign( sheenRoughness, sheenRoughnessNode );
 
 
+		// IRIDESCENCE
+
+		const iridescenceNode = this.iridescenceNode ? float( this.iridescenceNode ) : materialIridescence;
+		const iridescenceIORNode = this.iridescenceIORNode ? float( this.iridescenceIORNode ) : materialIridescenceIOR;
+		const iridescenceThicknessNode = this.iridescenceThicknessNode ? float( this.iridescenceThicknessNode ) : materialIridescenceThickness;
+
+		stack.assign( iridescence, iridescenceNode );
+		stack.assign( iridescenceIOR, iridescenceIORNode );
+		stack.assign( iridescenceThickness, iridescenceThicknessNode );
+
 	}
 	}
 
 
 	constructNormal( builder ) {
 	constructNormal( builder ) {

+ 2 - 2
examples/jsm/nodes/materials/SpriteNodeMaterial.js

@@ -4,7 +4,7 @@ import { cameraProjectionMatrix } from '../accessors/CameraNode.js';
 import { materialRotation } from '../accessors/MaterialNode.js';
 import { materialRotation } from '../accessors/MaterialNode.js';
 import { modelViewMatrix, modelWorldMatrix } from '../accessors/ModelNode.js';
 import { modelViewMatrix, modelWorldMatrix } from '../accessors/ModelNode.js';
 import { positionLocal } from '../accessors/PositionNode.js';
 import { positionLocal } from '../accessors/PositionNode.js';
-import { vec2, vec3, vec4 } from '../shadernode/ShaderNode.js';
+import { float, vec2, vec3, vec4 } from '../shadernode/ShaderNode.js';
 
 
 import { SpriteMaterial } from 'three';
 import { SpriteMaterial } from 'three';
 
 
@@ -66,7 +66,7 @@ class SpriteNodeMaterial extends NodeMaterial {
 
 
 		alignedPosition = alignedPosition.mul( scale );
 		alignedPosition = alignedPosition.mul( scale );
 
 
-		const rotation = rotationNode || materialRotation;
+		const rotation = float( rotationNode || materialRotation );
 
 
 		const cosAngle = rotation.cos();
 		const cosAngle = rotation.cos();
 		const sinAngle = rotation.sin();
 		const sinAngle = rotation.sin();

BIN
examples/screenshots/webgpu_loader_gltf_iridescence.jpg


+ 126 - 0
examples/webgpu_loader_gltf_iridescence.html

@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - GLTFloader + Iridescence</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - GLTFLoader + <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence" target="_blank" rel="noopener">KHR_materials_iridescence</a><br />
+			Iridescence Lamp from <a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/IridescenceLamp" target="_blank" rel="noopener">glTF-Sample-Models</a><br />
+			<a href="https://hdrihaven.com/hdri/?h=venice_sunset" target="_blank" rel="noopener">Venice Sunset</a> from <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+			let renderer, scene, camera, controls;
+
+			init().catch( function ( err ) {
+
+				console.error( err );
+
+			} );
+
+			async function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setAnimationLoop( render );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				document.body.appendChild( renderer.domElement );
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.05, 20 );
+				camera.position.set( 0.35, 0.05, 0.35 );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.autoRotate = true;
+				controls.autoRotateSpeed = - 0.5;
+				controls.target.set( 0, 0.2, 0 );
+				controls.update();
+
+				const rgbeLoader = new RGBELoader()
+					.setPath( 'textures/equirectangular/' );
+
+				const gltfLoader = new GLTFLoader().setPath( 'models/gltf/' );
+
+				const [ texture, gltf ] = await Promise.all( [
+					rgbeLoader.loadAsync( 'venice_sunset_1k.hdr' ),
+					gltfLoader.loadAsync( 'IridescenceLamp.glb' ),
+				] );
+
+				// environment
+
+				texture.mapping = THREE.EquirectangularReflectionMapping;
+
+				scene.background = texture;
+				scene.environment = texture;
+
+				// model
+
+				scene.add( gltf.scene );
+
+				render();
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			function render() {
+
+				controls.update();
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 2 - 1
test/e2e/puppeteer.js

@@ -122,6 +122,7 @@ const exceptionList = [
 	'webgpu_lights_selective',
 	'webgpu_lights_selective',
 	'webgpu_loader_gltf',
 	'webgpu_loader_gltf',
 	'webgpu_loader_gltf_compressed',
 	'webgpu_loader_gltf_compressed',
+	'webgpu_loader_gltf_iridescence',
 	'webgpu_loader_gltf_sheen',
 	'webgpu_loader_gltf_sheen',
 	'webgpu_materials',
 	'webgpu_materials',
 	'webgpu_materials_video',
 	'webgpu_materials_video',
@@ -598,7 +599,7 @@ async function makeAttempt( pages, failedScreenshots, cleanPage, isMakeScreensho
 
 
 		}
 		}
 
 
-	} catch ( e ) { 
+	} catch ( e ) {
 
 
 		if ( attemptID === numAttempts - 1 ) {
 		if ( attemptID === numAttempts - 1 ) {