Pārlūkot izejas kodu

WebGPURenderer: Anisotropy (#28106)

* Node: Added .onReference()

* TSL: Anisotropy properties

* TSL: transformedBentNormalView

* TSL: `defined()`

* PhysicalLightingModel: Anisotropy

* Added `webgpu_loader_gltf_anisotropy` example
sunag 1 gadu atpakaļ
vecāks
revīzija
d369861653

+ 1 - 0
examples/files.json

@@ -347,6 +347,7 @@
 		"webgpu_lights_selective",
 		"webgpu_lines_fat",
 		"webgpu_loader_gltf",
+		"webgpu_loader_gltf_anisotropy",
 		"webgpu_loader_gltf_compressed",
 		"webgpu_loader_gltf_iridescence",
 		"webgpu_loader_gltf_sheen",

+ 3 - 3
examples/jsm/nodes/Nodes.js

@@ -26,7 +26,7 @@ export { default as NodeUniform } from './core/NodeUniform.js';
 export { default as NodeVar } from './core/NodeVar.js';
 export { default as NodeVarying } from './core/NodeVarying.js';
 export { default as ParameterNode, parameter } from './core/ParameterNode.js';
-export { default as PropertyNode, property, varyingProperty, output, diffuseColor, roughness, metalness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, specularColor, shininess, dashSize, gapSize, pointWidth } from './core/PropertyNode.js';
+export { default as PropertyNode, property, varyingProperty, output, diffuseColor, roughness, metalness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, specularColor, shininess, dashSize, gapSize, pointWidth, alphaT, anisotropy, anisotropyB, anisotropyT } from './core/PropertyNode.js';
 export { default as StackNode, stack } from './core/StackNode.js';
 export { default as TempNode } from './core/TempNode.js';
 export { default as UniformGroupNode, uniformGroup, objectGroup, renderGroup, frameGroup } from './core/UniformGroupNode.js';
@@ -75,7 +75,7 @@ export { default as ReflectorNode, reflector } from './utils/ReflectorNode.js';
 export * from './shadernode/ShaderNode.js';
 
 // accessors
-export { TBNViewMatrix, parallaxDirection, parallaxUV } from './accessors/AccessorsUtils.js';
+export { TBNViewMatrix, parallaxDirection, parallaxUV, transformedBentNormalView } from './accessors/AccessorsUtils.js';
 export { default as UniformsNode, uniforms } from './accessors/UniformsNode.js';
 export { default as BitangentNode, bitangentGeometry, bitangentLocal, bitangentView, bitangentWorld, transformedBitangentView, transformedBitangentWorld } from './accessors/BitangentNode.js';
 export { default as BufferAttributeNode, bufferAttribute, dynamicBufferAttribute, instancedBufferAttribute, instancedDynamicBufferAttribute } from './accessors/BufferAttributeNode.js';
@@ -85,7 +85,7 @@ export { default as VertexColorNode, vertexColor } from './accessors/VertexColor
 export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js';
 export { default as InstanceNode, instance } from './accessors/InstanceNode.js';
 export { default as BatchNode, batch } from './accessors/BatchNode.js';
-export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth } from './accessors/MaterialNode.js';
+export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth, materialAnisotropy, materialAnisotropyVector } from './accessors/MaterialNode.js';
 export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js';
 export { default as RendererReferenceNode, rendererReference } from './accessors/RendererReferenceNode.js';
 export { default as MorphNode, morphReference } from './accessors/MorphNode.js';

+ 16 - 1
examples/jsm/nodes/accessors/AccessorsUtils.js

@@ -1,10 +1,25 @@
 import { bitangentView } from './BitangentNode.js';
-import { normalView } from './NormalNode.js';
+import { normalView, transformedNormalView } from './NormalNode.js';
 import { tangentView } from './TangentNode.js';
 import { mat3 } from '../shadernode/ShaderNode.js';
+import { mix } from '../math/MathNode.js';
+import { anisotropy, anisotropyB, roughness } from '../core/PropertyNode.js';
 import { positionViewDirection } from './PositionNode.js';
 
 export const TBNViewMatrix = mat3( tangentView, bitangentView, normalView );
 
 export const parallaxDirection = positionViewDirection.mul( TBNViewMatrix )/*.normalize()*/;
 export const parallaxUV = ( uv, scale ) => uv.sub( parallaxDirection.mul( scale ) );
+
+export const transformedBentNormalView = ( () => {
+
+	// https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/anisotropy
+
+	let bentNormal = anisotropyB.cross( positionViewDirection );
+	bentNormal = bentNormal.cross( anisotropyB ).normalize();
+	bentNormal = mix( bentNormal, transformedNormalView, anisotropy.mul( roughness.oneMinus() ).oneMinus().pow2().pow2() ).normalize();
+
+	return bentNormal;
+
+
+} )();

+ 29 - 1
examples/jsm/nodes/accessors/MaterialNode.js

@@ -2,7 +2,9 @@ import Node, { addNodeClass } from '../core/Node.js';
 import { reference } from './ReferenceNode.js';
 import { materialReference } from './MaterialReferenceNode.js';
 import { normalView } from './NormalNode.js';
-import { nodeImmutable, float } from '../shadernode/ShaderNode.js';
+import { nodeImmutable, float, vec2, mat2 } from '../shadernode/ShaderNode.js';
+import { uniform } from '../core/UniformNode.js';
+import { Vector2 } from 'three';
 
 const _propertyCache = new Map();
 
@@ -225,6 +227,21 @@ class MaterialNode extends Node {
 
 			node = node.clamp( 0.07, 1.0 );
 
+		} else if ( scope === MaterialNode.ANISOTROPY ) {
+
+			if ( material.anisotropyMap && material.anisotropyMap.isTexture === true ) {
+
+				const anisotropyPolar = this.getTexture( scope );
+				const anisotropyMat = mat2( materialAnisotropyVector.x, materialAnisotropyVector.y, materialAnisotropyVector.y.negate(), materialAnisotropyVector.x );
+
+				node = anisotropyMat.mul( anisotropyPolar.rg.mul( 2.0 ).sub( vec2( 1.0 ) ).normalize().mul( anisotropyPolar.b ) );
+
+			} else {
+
+				node = materialAnisotropyVector;
+
+			}
+
 		} else if ( scope === MaterialNode.IRIDESCENCE_THICKNESS ) {
 
 			const iridescenceThicknessMaximum = reference( '1', 'float', material.iridescenceThicknessRange );
@@ -272,6 +289,7 @@ MaterialNode.EMISSIVE = 'emissive';
 MaterialNode.ROTATION = 'rotation';
 MaterialNode.SHEEN = 'sheen';
 MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness';
+MaterialNode.ANISOTROPY = 'anisotropy';
 MaterialNode.IRIDESCENCE = 'iridescence';
 MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR';
 MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness';
@@ -301,6 +319,7 @@ export const materialClearcoatNormal = nodeImmutable( MaterialNode, MaterialNode
 export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
 export const materialSheen = nodeImmutable( MaterialNode, MaterialNode.SHEEN );
 export const materialSheenRoughness = nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS );
+export const materialAnisotropy = nodeImmutable( MaterialNode, MaterialNode.ANISOTROPY );
 export const materialIridescence = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE );
 export const materialIridescenceIOR = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR );
 export const materialIridescenceThickness = nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS );
@@ -310,5 +329,14 @@ export const materialLineGapSize = nodeImmutable( MaterialNode, MaterialNode.LIN
 export const materialLineWidth = nodeImmutable( MaterialNode, MaterialNode.LINE_WIDTH );
 export const materialLineDashOffset = nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET );
 export const materialPointWidth = nodeImmutable( MaterialNode, MaterialNode.POINT_WIDTH );
+export const materialAnisotropyVector = uniform( new Vector2() ).onReference( function ( frame ) {
+
+	return frame.material;
+
+} ).onRenderUpdate( function ( { material } ) {
+
+	this.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) );
+
+} );
 
 addNodeClass( 'MaterialNode', MaterialNode );

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

@@ -62,6 +62,10 @@ export const sheenRoughness = nodeImmutable( PropertyNode, 'float', 'SheenRoughn
 export const iridescence = nodeImmutable( PropertyNode, 'float', 'Iridescence' );
 export const iridescenceIOR = nodeImmutable( PropertyNode, 'float', 'IridescenceIOR' );
 export const iridescenceThickness = nodeImmutable( PropertyNode, 'float', 'IridescenceThickness' );
+export const alphaT = nodeImmutable( PropertyNode, 'float', 'AlphaT' );
+export const anisotropy = nodeImmutable( PropertyNode, 'float', 'Anisotropy' );
+export const anisotropyT = nodeImmutable( PropertyNode, 'vec3', 'AnisotropyT' );
+export const anisotropyB = nodeImmutable( PropertyNode, 'vec3', 'AnisotropyB' );
 export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
 export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' );
 export const output = nodeImmutable( PropertyNode, 'vec4', 'Output' );

+ 26 - 7
examples/jsm/nodes/functions/BSDF/BRDF_GGX.js

@@ -1,15 +1,17 @@
 import F_Schlick from './F_Schlick.js';
 import V_GGX_SmithCorrelated from './V_GGX_SmithCorrelated.js';
+import V_GGX_SmithCorrelated_Anisotropic from './V_GGX_SmithCorrelated_Anisotropic.js';
 import D_GGX from './D_GGX.js';
+import D_GGX_Anisotropic from './D_GGX_Anisotropic.js';
 import { transformedNormalView } from '../../accessors/NormalNode.js';
 import { positionViewDirection } from '../../accessors/PositionNode.js';
-import { iridescence } from '../../core/PropertyNode.js';
-import { tslFn } from '../../shadernode/ShaderNode.js';
+import { iridescence, alphaT, anisotropyT, anisotropyB } from '../../core/PropertyNode.js';
+import { tslFn, defined } from '../../shadernode/ShaderNode.js';
 
 // GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
 const BRDF_GGX = tslFn( ( inputs ) => {
 
-	const { lightDirection, f0, f90, roughness, iridescenceFresnel } = inputs;
+	const { lightDirection, f0, f90, roughness, f, USE_IRIDESCENCE, USE_ANISOTROPY } = inputs;
 
 	const normalView = inputs.normalView || transformedNormalView;
 
@@ -23,15 +25,32 @@ const BRDF_GGX = tslFn( ( inputs ) => {
 	const dotVH = positionViewDirection.dot( halfDir ).clamp();
 
 	let F = F_Schlick( { f0, f90, dotVH } );
+	let V, D;
 
-	if ( iridescenceFresnel ) {
+	if ( defined( USE_IRIDESCENCE ) ) {
 
-		F = iridescence.mix( F, iridescenceFresnel );
+		F = iridescence.mix( F, f );
 
 	}
 
-	const V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } );
-	const D = D_GGX( { alpha, dotNH } );
+	if ( defined( USE_ANISOTROPY ) ) {
+
+		const dotTL = anisotropyT.dot( lightDirection );
+		const dotTV = anisotropyT.dot( positionViewDirection );
+		const dotTH = anisotropyT.dot( halfDir );
+		const dotBL = anisotropyB.dot( lightDirection );
+		const dotBV = anisotropyB.dot( positionViewDirection );
+		const dotBH = anisotropyB.dot( halfDir );
+
+		V = V_GGX_SmithCorrelated_Anisotropic( { alphaT, alphaB: alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } );
+		D = D_GGX_Anisotropic( { alphaT, alphaB: alpha, dotNH, dotTH, dotBH } );
+
+	} else {
+
+		V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } );
+		D = D_GGX( { alpha, dotNH } );
+
+	}
 
 	return F.mul( V ).mul( D );
 

+ 28 - 0
examples/jsm/nodes/functions/BSDF/D_GGX_Anisotropic.js

@@ -0,0 +1,28 @@
+import { tslFn, float, vec3 } from '../../shadernode/ShaderNode.js';
+
+const RECIPROCAL_PI = float( 1 / Math.PI );
+
+// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf
+
+const D_GGX_Anisotropic = tslFn( ( { alphaT, alphaB, dotNH, dotTH, dotBH } ) => {
+
+	const a2 = alphaT.mul( alphaB );
+	const v = vec3( alphaB.mul( dotTH ), alphaT.mul( dotBH ), a2.mul( dotNH ) );
+	const v2 = v.dot( v );
+	const w2 = a2.div( v2 );
+
+	return RECIPROCAL_PI.mul( a2.mul( w2.pow2() ) );
+
+} ).setLayout( {
+	name: 'D_GGX_Anisotropic',
+	type: 'float',
+	inputs: [
+		{ name: 'alphaT', type: 'float', qualifier: 'in' },
+		{ name: 'alphaB', type: 'float', qualifier: 'in' },
+		{ name: 'dotNH', type: 'float', qualifier: 'in' },
+		{ name: 'dotTH', type: 'float', qualifier: 'in' },
+		{ name: 'dotBH', type: 'float', qualifier: 'in' }
+	]
+} );
+
+export default D_GGX_Anisotropic;

+ 1 - 3
examples/jsm/nodes/functions/BSDF/V_GGX_SmithCorrelated.js

@@ -4,9 +4,7 @@ import { tslFn } from '../../shadernode/ShaderNode.js';
 
 // Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
 // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
-const V_GGX_SmithCorrelated = tslFn( ( inputs ) => {
-
-	const { alpha, dotNL, dotNV } = inputs;
+const V_GGX_SmithCorrelated = tslFn( ( { alpha, dotNL, dotNV } ) => {
 
 	const a2 = alpha.pow2();
 

+ 29 - 0
examples/jsm/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js

@@ -0,0 +1,29 @@
+import { div } from '../../math/OperatorNode.js';
+import { tslFn, vec3 } from '../../shadernode/ShaderNode.js';
+
+// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf
+
+const V_GGX_SmithCorrelated_Anisotropic = tslFn( ( { alphaT, alphaB, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } ) => {
+
+	const gv = dotNL.mul( vec3( alphaT.mul( dotTV ), alphaB.mul( dotBV ), dotNV ).length() );
+	const gl = dotNV.mul( vec3( alphaT.mul( dotTL ), alphaB.mul( dotBL ), dotNL ).length() );
+	const v = div( 0.5, gv.add( gl ) );
+
+	return v.saturate();
+
+} ).setLayout( {
+	name: 'V_GGX_SmithCorrelated_Anisotropic',
+	type: 'float',
+	inputs: [
+		{ name: 'alphaT', type: 'float', qualifier: 'in' },
+		{ name: 'alphaB', type: 'float', qualifier: 'in' },
+		{ name: 'dotTV', type: 'float', qualifier: 'in' },
+		{ name: 'dotBV', type: 'float', qualifier: 'in' },
+		{ name: 'dotTL', type: 'float', qualifier: 'in' },
+		{ name: 'dotBL', type: 'float', qualifier: 'in' },
+		{ name: 'dotNV', type: 'float', qualifier: 'in' },
+		{ name: 'dotNL', type: 'float', qualifier: 'in' }
+	]
+} );
+
+export default V_GGX_SmithCorrelated_Anisotropic;

+ 3 - 2
examples/jsm/nodes/functions/PhysicalLightingModel.js

@@ -172,13 +172,14 @@ const clearcoatF90 = vec3( 1 );
 
 class PhysicalLightingModel extends LightingModel {
 
-	constructor( clearcoat = false, sheen = false, iridescence = false ) {
+	constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false ) {
 
 		super();
 
 		this.clearcoat = clearcoat;
 		this.sheen = sheen;
 		this.iridescence = iridescence;
+		this.anisotropy = anisotropy;
 
 		this.clearcoatRadiance = null;
 		this.clearcoatSpecularDirect = null;
@@ -272,7 +273,7 @@ class PhysicalLightingModel extends LightingModel {
 
 		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, iridescenceFresnel: this.iridescenceFresnel } ) ) );
+		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 } ) ) );
 
 	}
 

+ 11 - 2
examples/jsm/nodes/lighting/EnvironmentNode.js

@@ -8,6 +8,7 @@ import { positionViewDirection } from '../accessors/PositionNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { float } from '../shadernode/ShaderNode.js';
 import { reference } from '../accessors/ReferenceNode.js';
+import { transformedBentNormalView } from '../accessors/AccessorsUtils.js';
 import { pmremTexture } from '../pmrem/PMREMNode.js';
 
 const envNodeCache = new WeakMap();
@@ -44,10 +45,15 @@ class EnvironmentNode extends LightingNode {
 
 		//
 
-		const envMap = builder.material.envMap;
+		const { material } = builder;
+
+		const envMap = material.envMap;
 		const intensity = envMap ? reference( 'envMapIntensity', 'float', builder.material ) : reference( 'environmentIntensity', 'float', builder.scene ); // @TODO: Add materialEnvIntensity in MaterialNode
 
-		const radiance = context( envNode, createRadianceContext( roughness, transformedNormalView ) ).mul( intensity );
+		const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0;
+		const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView;
+
+		const radiance = context( envNode, createRadianceContext( roughness, radianceNormalView ) ).mul( intensity );
 		const irradiance = context( envNode, createIrradianceContext( transformedNormalWorld ) ).mul( Math.PI ).mul( intensity );
 
 		const isolateRadiance = cache( radiance );
@@ -85,7 +91,10 @@ const createRadianceContext = ( roughnessNode, normalViewNode ) => {
 			if ( reflectVec === null ) {
 
 				reflectVec = positionViewDirection.negate().reflect( normalViewNode );
+
+				// Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane.
 				reflectVec = roughnessNode.mul( roughnessNode ).mix( reflectVec, normalViewNode ).normalize();
+
 				reflectVec = reflectVec.transformDirection( cameraViewMatrix );
 
 			}

+ 42 - 4
examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js

@@ -1,8 +1,9 @@
 import { addNodeMaterial } from './NodeMaterial.js';
 import { transformedClearcoatNormalView } from '../accessors/NormalNode.js';
-import { clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness } from '../core/PropertyNode.js';
-import { materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness } from '../accessors/MaterialNode.js';
-import { float, vec3 } from '../shadernode/ShaderNode.js';
+import { roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, anisotropy, alphaT, anisotropyT, anisotropyB } from '../core/PropertyNode.js';
+import { materialAnisotropy, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness } from '../accessors/MaterialNode.js';
+import { float, vec2, vec3, If } from '../shadernode/ShaderNode.js';
+import { TBNViewMatrix } from '../accessors/AccessorsUtils.js';
 import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
 
@@ -37,6 +38,8 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 		this.attenuationDistanceNode = null;
 		this.attenuationColorNode = null;
 
+		this.anisotropyNode = null;
+
 		this.setDefaultValues( defaultValues );
 
 		this.setValues( parameters );
@@ -61,9 +64,15 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 
 	}
 
+	get useAnisotropy() {
+
+		return this.anisotropy > 0 || this.anisotropyNode !== null;
+
+	}
+
 	setupLightingModel( /*builder*/ ) {
 
-		return new PhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence );
+		return new PhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy );
 
 	}
 
@@ -109,6 +118,33 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 
 		}
 
+		// ANISOTROPY
+
+		if ( this.useAnisotropy ) {
+
+			const anisotropyV = ( this.anisotropyNode ? vec2( this.anisotropyNode ) : materialAnisotropy ).toVar();
+
+			anisotropy.assign( anisotropyV.length() );
+
+			If( anisotropy.equal( 0.0 ), () => {
+
+				anisotropyV.assign( vec2( 1.0, 0.0 ) );
+
+			} ).else( () => {
+
+				anisotropyV.divAssign( anisotropy );
+				anisotropy.assign( anisotropy.saturate() );
+
+			} );
+
+			// Roughness along the anisotropy bitangent is the material roughness, while the tangent roughness increases with anisotropy.
+			alphaT.assign( anisotropy.pow2().mix( roughness.pow2(), 1.0 ) );
+
+			anisotropyT.assign( TBNViewMatrix[ 0 ].mul( anisotropyV.x ).add( TBNViewMatrix[ 1 ].mul( anisotropyV.y ) ) );
+			anisotropyB.assign( TBNViewMatrix[ 1 ].mul( anisotropyV.x ).sub( TBNViewMatrix[ 0 ].mul( anisotropyV.y ) ) );
+
+		}
+
 	}
 
 	setupNormal( builder ) {
@@ -144,6 +180,8 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 		this.attenuationDistanceNode = source.attenuationDistanceNode;
 		this.attenuationColorNode = source.attenuationColorNode;
 
+		this.anisotropyNode = source.anisotropyNode;
+
 		return super.copy( source );
 
 	}

+ 2 - 0
examples/jsm/nodes/shadernode/ShaderNode.js

@@ -456,6 +456,8 @@ const ConvertType = function ( type, cacheMap = null ) {
 
 // exports
 
+export const defined = ( value ) => value && value.value;
+
 // utils
 
 export const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null;

BIN
examples/screenshots/webgpu_loader_gltf_anisotropy.jpg


+ 108 - 0
examples/webgpu_loader_gltf_anisotropy.html

@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - GLTFloader + Anisotropy</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 webgpu</a> - GLTFLoader + <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy" target="_blank" rel="noopener">KHR_materials_anisotropy</a><br />
+			Anisotropy Barn Lamp from <a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/AnisotropyBarnLamp" target="_blank" rel="noopener">glTF-Sample-Models</a><br />
+			<a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> from <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			let renderer, scene, camera, controls;
+
+			init();
+
+			async function init() {
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 1.35;
+				document.body.appendChild( renderer.domElement );
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.01, 10 );
+				camera.position.set( - 0.35, - 0.2, 0.35 );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, - 0.08, 0.11 );
+				controls.minDistance = 0.1;
+				controls.maxDistance = 2;
+				controls.addEventListener( 'change', render );
+				controls.update();
+
+				const rgbeLoader = new RGBELoader().setPath( 'textures/equirectangular/' );
+				const gltfLoader = new GLTFLoader().setPath( 'models/gltf/' );
+
+				const [ texture, gltf ] = await Promise.all( [
+					rgbeLoader.loadAsync( 'royal_esplanade_1k.hdr' ),
+					gltfLoader.loadAsync( 'AnisotropyBarnLamp.glb' ),
+				] );
+
+				// environment
+
+				texture.mapping = THREE.EquirectangularReflectionMapping;
+
+				scene.background = texture;
+				scene.backgroundBlurriness = 0.5;
+				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() {
+
+				renderer.renderAsync( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>