ソースを参照

WebGPU: Supports to IBL + PBR (#24038)

* add Node.getReference() used to for share a similar node

* Remove SPECULAR for now and add EMISSIVE

* improve Object3DNode.update() implementation

* Nodes: move ./light/ to ./lighting/

* Move to LightingContextNode

* fix viewMatrix and update names

* update names

* Support to RGBMLoader

* Rename TextureBias to TextureLevel

* NodeMaterial: Improve support to IBL + PBR

* add webgpu_loader_gltf example

* update examples

* cleanup

* Delete webgpu_lights_hemisphere.html

* cleanup

* cleanup imports
sunag 3 年 前
コミット
acda8183d0
42 ファイル変更858 行追加326 行削除
  1. 1 0
      examples/files.json
  2. 25 13
      examples/jsm/nodes/Nodes.js
  3. 11 8
      examples/jsm/nodes/accessors/CubeTextureNode.js
  4. 43 10
      examples/jsm/nodes/accessors/MaterialNode.js
  5. 8 0
      examples/jsm/nodes/accessors/ModelNode.js
  6. 5 2
      examples/jsm/nodes/accessors/Object3DNode.js
  7. 6 3
      examples/jsm/nodes/accessors/ReflectNode.js
  8. 6 6
      examples/jsm/nodes/accessors/TextureNode.js
  9. 15 8
      examples/jsm/nodes/core/Node.js
  10. 2 2
      examples/jsm/nodes/core/NodeBuilder.js
  11. 2 2
      examples/jsm/nodes/core/TempNode.js
  12. 2 2
      examples/jsm/nodes/core/VarNode.js
  13. 27 0
      examples/jsm/nodes/functions/BSDF/DFGApprox.js
  14. 74 7
      examples/jsm/nodes/functions/PhysicalLightingModel.js
  15. 25 0
      examples/jsm/nodes/lighting/AONode.js
  16. 37 0
      examples/jsm/nodes/lighting/AnalyticLightNode.js
  17. 70 0
      examples/jsm/nodes/lighting/EnvironmentLightNode.js
  18. 50 0
      examples/jsm/nodes/lighting/HemisphereLightNode.js
  19. 62 0
      examples/jsm/nodes/lighting/LightingContextNode.js
  20. 19 0
      examples/jsm/nodes/lighting/LightingNode.js
  21. 18 9
      examples/jsm/nodes/lighting/LightsNode.js
  22. 68 0
      examples/jsm/nodes/lighting/PunctualLightNode.js
  23. 0 42
      examples/jsm/nodes/lights/LightContextNode.js
  24. 0 82
      examples/jsm/nodes/lights/LightNode.js
  25. 0 32
      examples/jsm/nodes/lights/ReflectedLightNode.js
  26. 50 18
      examples/jsm/nodes/materials/MeshStandardNodeMaterial.js
  27. 7 11
      examples/jsm/nodes/materials/NodeMaterial.js
  28. 1 1
      examples/jsm/nodes/math/CondNode.js
  29. 12 14
      examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js
  30. 11 9
      examples/jsm/nodes/shadernode/ShaderNodeElements.js
  31. 4 4
      examples/jsm/nodes/utils/MaxMipLevelNode.js
  32. 2 2
      examples/jsm/renderers/webgpu/WebGPURenderStates.js
  33. 7 7
      examples/jsm/renderers/webgpu/WebGPURenderer.js
  34. 22 9
      examples/jsm/renderers/webgpu/WebGPUTextures.js
  35. 6 9
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  36. 5 4
      examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js
  37. BIN
      examples/screenshots/webgpu_loader_gltf.jpg
  38. 2 2
      examples/webgpu_compute.html
  39. 4 2
      examples/webgpu_lights_custom.html
  40. 4 4
      examples/webgpu_lights_selective.html
  41. 145 0
      examples/webgpu_loader_gltf.html
  42. 0 2
      examples/webgpu_skinning.html

+ 1 - 0
examples/files.json

@@ -313,6 +313,7 @@
 		"webgpu_instance_uniform",
 		"webgpu_lights_custom",
 		"webgpu_lights_selective",
+		"webgpu_loader_gltf",
 		"webgpu_materials",
 		"webgpu_nodes_playground",
 		"webgpu_rtt",

+ 25 - 13
examples/jsm/nodes/Nodes.js

@@ -58,11 +58,15 @@ import MathNode from './math/MathNode.js';
 import OperatorNode from './math/OperatorNode.js';
 import CondNode from './math/CondNode.js';
 
-// lights
-import LightContextNode from './lights/LightContextNode.js';
-import LightNode from './lights/LightNode.js';
-import LightsNode from './lights/LightsNode.js';
-import ReflectedLightNode from './lights/ReflectedLightNode.js';
+// lighting
+import PunctualLightNode from './lighting/PunctualLightNode.js';
+import LightsNode from './lighting/LightsNode.js';
+import LightingNode from './lighting/LightingNode.js';
+import LightingContextNode from './lighting/LightingContextNode.js';
+import HemisphereLightNode from './lighting/HemisphereLightNode.js';
+import EnvironmentLightNode from './lighting/EnvironmentLightNode.js';
+import AONode from './lighting/AONode.js';
+import AnalyticLightNode from './lighting/AnalyticLightNode.js';
 
 // utils
 import ArrayElementNode from './utils/ArrayElementNode.js';
@@ -157,11 +161,15 @@ const nodeLib = {
 	OperatorNode,
 	CondNode,
 
-	// lights
-	LightContextNode,
-	LightNode,
+	// lighting
+	PunctualLightNode,
 	LightsNode,
-	ReflectedLightNode,
+	LightingNode,
+	LightingContextNode,
+	HemisphereLightNode,
+	EnvironmentLightNode,
+	AONode,
+	AnalyticLightNode,
 
 	// utils
 	ArrayElementNode,
@@ -255,11 +263,15 @@ export {
 	OperatorNode,
 	CondNode,
 
-	// lights
-	LightContextNode,
-	LightNode,
+	// lighting
+	PunctualLightNode,
 	LightsNode,
-	ReflectedLightNode,
+	LightingNode,
+	LightingContextNode,
+	HemisphereLightNode,
+	EnvironmentLightNode,
+	AONode,
+	AnalyticLightNode,
 
 	// utils
 	ArrayElementNode,

+ 11 - 8
examples/jsm/nodes/accessors/CubeTextureNode.js

@@ -4,9 +4,9 @@ import ReflectNode from './ReflectNode.js';
 
 class CubeTextureNode extends TextureNode {
 
-	constructor( value, uvNode = new ReflectNode(), biasNode = null ) {
+	constructor( value, uvNode = null, levelNode = null ) {
 
-		super( value, uvNode, biasNode );
+		super( value, uvNode, levelNode );
 
 	}
 
@@ -19,6 +19,7 @@ class CubeTextureNode extends TextureNode {
 	generate( builder, output ) {
 
 		const texture = this.value;
+		const uvNode = this.uvNode || builder.context.uvNode || new ReflectNode();
 
 		if ( ! texture || texture.isCubeTexture !== true ) {
 
@@ -42,16 +43,18 @@ class CubeTextureNode extends TextureNode {
 
 			let snippet = nodeData.snippet;
 
-			if ( snippet === undefined ) {
+			if ( builder.context.tempRead === false || snippet === undefined ) {
 
-				const uvSnippet = this.uvNode.build( builder, 'vec3' );
-				const biasNode = this.biasNode;
+				const uvSnippet = uvNode.build( builder, 'vec3' );
+				const levelNode = this.levelNode || builder.context.levelNode;
 
-				if ( biasNode !== null ) {
+				if ( levelNode?.isNode === true ) {
 
-					const biasSnippet = biasNode.build( builder, 'float' );
+					const levelOutNode = builder.context.levelShaderNode ? builder.context.levelShaderNode.call( { texture, levelNode }, builder ) : levelNode;
 
-					snippet = builder.getCubeTextureBias( textureProperty, uvSnippet, biasSnippet );
+					const levelSnippet = levelOutNode.build( builder, 'float' );
+
+					snippet = builder.getCubeTextureLevel( textureProperty, uvSnippet, levelSnippet );
 
 				} else {
 

+ 43 - 10
examples/jsm/nodes/accessors/MaterialNode.js

@@ -1,15 +1,17 @@
 import Node from '../core/Node.js';
 import OperatorNode from '../math/OperatorNode.js';
 import MaterialReferenceNode from './MaterialReferenceNode.js';
+import TextureNode from './TextureNode.js';
+import SplitNode from '../utils/SplitNode.js';
 
 class MaterialNode extends Node {
 
 	static ALPHA_TEST = 'alphaTest';
 	static COLOR = 'color';
 	static OPACITY = 'opacity';
-	static SPECULAR = 'specular';
 	static ROUGHNESS = 'roughness';
 	static METALNESS = 'metalness';
+	static EMISSIVE = 'emissive';
 
 	constructor( scope = MaterialNode.COLOR ) {
 
@@ -32,7 +34,7 @@ class MaterialNode extends Node {
 
 			return 'float';
 
-		} else if ( scope === MaterialNode.SPECULAR ) {
+		} else if ( scope === MaterialNode.EMISSIVE ) {
 
 			return 'vec3';
 
@@ -59,9 +61,12 @@ class MaterialNode extends Node {
 
 			const colorNode = new MaterialReferenceNode( 'color', 'color' );
 
-			if ( material.map !== null && material.map !== undefined && material.map.isTexture === true ) {
+			if ( material.map?.isTexture === true ) {
 
-				node = new OperatorNode( '*', colorNode, new MaterialReferenceNode( 'map', 'texture' ) );
+				//new MaterialReferenceNode( 'map', 'texture' )
+				const map = new TextureNode( material.map );
+
+				node = new OperatorNode( '*', colorNode, map );
 
 			} else {
 
@@ -73,7 +78,7 @@ class MaterialNode extends Node {
 
 			const opacityNode = new MaterialReferenceNode( 'opacity', 'float' );
 
-			if ( material.alphaMap !== null && material.alphaMap !== undefined && material.alphaMap.isTexture === true ) {
+			if ( material.alphaMap?.isTexture === true ) {
 
 				node = new OperatorNode( '*', opacityNode, new MaterialReferenceNode( 'alphaMap', 'texture' ) );
 
@@ -83,17 +88,45 @@ class MaterialNode extends Node {
 
 			}
 
-		} else if ( scope === MaterialNode.SPECULAR ) {
+		} else if ( scope === MaterialNode.ROUGHNESS ) {
+
+			const roughnessNode = new MaterialReferenceNode( 'roughness', 'float' );
+
+			if ( material.roughnessMap?.isTexture === true ) {
+
+				node = new OperatorNode( '*', roughnessNode, new SplitNode( new TextureNode( material.roughnessMap ), 'g' ) );
+
+			} else {
+
+				node = roughnessNode;
+
+			}
+
+		} else if ( scope === MaterialNode.METALNESS ) {
+
+			const metalnessNode = new MaterialReferenceNode( 'metalness', 'float' );
+
+			if ( material.metalnessMap?.isTexture === true ) {
+
+				node = new OperatorNode( '*', metalnessNode, new SplitNode( new TextureNode( material.metalnessMap ), 'b' ) );
+
+			} else {
+
+				node = metalnessNode;
+
+			}
+
+		} else if ( scope === MaterialNode.EMISSIVE ) {
 
-			const specularColorNode = new MaterialReferenceNode( 'specularColor', 'color' );
+			const emissiveNode = new MaterialReferenceNode( 'emissive', 'color' );
 
-			if ( material.specularColorMap !== null && material.specularColorMap !== undefined && material.specularColorMap.isTexture === true ) {
+			if ( material.emissiveMap?.isTexture === true ) {
 
-				node = new OperatorNode( '*', specularColorNode, new MaterialReferenceNode( 'specularColorMap', 'texture' ) );
+				node = new OperatorNode( '*', emissiveNode, new TextureNode( material.emissiveMap ) );
 
 			} else {
 
-				node = specularColorNode;
+				node = emissiveNode;
 
 			}
 

+ 8 - 0
examples/jsm/nodes/accessors/ModelNode.js

@@ -8,6 +8,14 @@ class ModelNode extends Object3DNode {
 
 	}
 
+	update( frame ) {
+
+		this.object3d = frame.object;
+
+		super.update( frame );
+
+	}
+
 }
 
 export default ModelNode;

+ 5 - 2
examples/jsm/nodes/accessors/Object3DNode.js

@@ -46,9 +46,8 @@ class Object3DNode extends Node {
 
 	update( frame ) {
 
-		const object = this.object3d !== null ? this.object3d : frame.object;
+		const object = this.object3d;
 		const uniformNode = this._uniformNode;
-		const camera = frame.camera;
 		const scope = this.scope;
 
 		if ( scope === Object3DNode.VIEW_MATRIX ) {
@@ -69,10 +68,14 @@ class Object3DNode extends Node {
 
 		} else if ( scope === Object3DNode.VIEW_POSITION ) {
 
+			const camera = frame.camera;
+
 			uniformNode.value.setFromMatrixPosition( object.matrixWorld );
 
 			uniformNode.value.applyMatrix4( camera.matrixWorldInverse );
 
+			//uniformNode.value.setFromMatrixPosition( object.modelViewMatrix );
+
 		}
 
 	}

+ 6 - 3
examples/jsm/nodes/accessors/ReflectNode.js

@@ -1,5 +1,8 @@
 import Node from '../core/Node.js';
-import { nodeObject, normalWorld, positionWorld, cameraPosition, sub, normalize, vec3, negate, reflect } from '../shadernode/ShaderNodeBaseElements.js';
+import {
+	nodeObject, transformedNormalView, positionViewDirection,
+	transformDirection, negate, reflect, vec3, cameraViewMatrix
+} from '../shadernode/ShaderNodeBaseElements.js';
 
 class ReflectNode extends Node {
 
@@ -26,8 +29,8 @@ class ReflectNode extends Node {
 
 		if ( scope === ReflectNode.VECTOR ) {
 
-			const cameraToFrag = normalize( sub( positionWorld, cameraPosition ) );
-			const reflectVec = reflect( cameraToFrag, normalWorld );
+			const reflectView = reflect( negate( positionViewDirection ), transformedNormalView );
+			const reflectVec = transformDirection( reflectView, cameraViewMatrix );
 
 			return reflectVec.build( builder );
 

+ 6 - 6
examples/jsm/nodes/accessors/TextureNode.js

@@ -3,12 +3,12 @@ import UVNode from './UVNode.js';
 
 class TextureNode extends UniformNode {
 
-	constructor( value, uvNode = new UVNode(), biasNode = null ) {
+	constructor( value, uvNode = new UVNode(), levelNode = null ) {
 
 		super( value, 'vec4' );
 
 		this.uvNode = uvNode;
-		this.biasNode = biasNode;
+		this.levelNode = levelNode;
 
 	}
 
@@ -53,13 +53,13 @@ class TextureNode extends UniformNode {
 			if ( snippet === undefined ) {
 
 				const uvSnippet = this.uvNode.build( builder, 'vec2' );
-				const biasNode = this.biasNode;
+				const levelNode = this.levelNode;
 
-				if ( biasNode !== null ) {
+				if ( levelNode !== null ) {
 
-					const biasSnippet = biasNode.build( builder, 'float' );
+					const levelSnippet = levelNode.build( builder, 'float' );
 
-					snippet = builder.getTextureBias( textureProperty, uvSnippet, biasSnippet );
+					snippet = builder.getTextureLevel( textureProperty, uvSnippet, levelSnippet );
 
 				} else {
 

+ 15 - 8
examples/jsm/nodes/core/Node.js

@@ -42,6 +42,15 @@ class Node {
 
 	}
 
+	getReference( builder ) {
+
+		const hash = this.getHash( builder );
+		const nodeFromHash = builder.getNodeFromHash( hash );
+
+		return nodeFromHash || this;
+
+	}
+
 	update( /*frame*/ ) {
 
 		console.warn( 'Abstract function.' );
@@ -56,12 +65,11 @@ class Node {
 
 	analyze( builder ) {
 
-		const hash = this.getHash( builder );
-		const sharedNode = builder.getNodeFromHash( hash );
+		const refNode = this.getReference( builder );
 
-		if ( sharedNode !== undefined && this !== sharedNode ) {
+		if ( this !== refNode ) {
 
-			return sharedNode.analyze( builder );
+			return refNode.analyze( builder );
 
 		}
 
@@ -80,12 +88,11 @@ class Node {
 
 	build( builder, output = null ) {
 
-		const hash = this.getHash( builder );
-		const sharedNode = builder.getNodeFromHash( hash );
+		const refNode = this.getReference( builder );
 
-		if ( sharedNode !== undefined && this !== sharedNode ) {
+		if ( this !== refNode ) {
 
-			return sharedNode.build( builder, output );
+			return refNode.build( builder, output );
 
 		}
 

+ 2 - 2
examples/jsm/nodes/core/NodeBuilder.js

@@ -180,7 +180,7 @@ class NodeBuilder {
 
 	}
 
-	getTextureBias( /* textureProperty, uvSnippet, biasSnippet */ ) {
+	getTextureLevel( /* textureProperty, uvSnippet, levelSnippet */ ) {
 
 		console.warn( 'Abstract function.' );
 
@@ -192,7 +192,7 @@ class NodeBuilder {
 
 	}
 
-	getCubeTextureBias( /* textureProperty, uvSnippet, biasSnippet */ ) {
+	getCubeTextureLevel( /* textureProperty, uvSnippet, levelSnippet */ ) {
 
 		console.warn( 'Abstract function.' );
 

+ 2 - 2
examples/jsm/nodes/core/TempNode.js

@@ -13,11 +13,11 @@ class TempNode extends Node {
 		const type = builder.getVectorType( this.getNodeType( builder, output ) );
 		const nodeData = builder.getDataFromNode( this );
 
-		if ( nodeData.propertyName !== undefined ) {
+		if ( builder.context.tempRead !== false && nodeData.propertyName !== undefined ) {
 
 			return builder.format( nodeData.propertyName, type, output );
 
-		} else if ( builder.context.temp !== false && type !== 'void ' && output !== 'void' && nodeData.dependenciesCount > 1 ) {
+		} else if ( builder.context.tempWrite !== false && type !== 'void ' && output !== 'void' && nodeData.dependenciesCount > 1 ) {
 
 			const snippet = super.build( builder, type );
 

+ 2 - 2
examples/jsm/nodes/core/VarNode.js

@@ -65,14 +65,14 @@ class VarNode extends Node {
 	generate( builder ) {
 
 		const node = this.node;
+		const name = this.name;
 
-		if ( node.isTempNode === true ) {
+		if ( name === null && node.isTempNode === true ) {
 
 			return node.build( builder );
 
 		}
 
-		const name = this.name;
 		const type = builder.getVectorType( this.getNodeType( builder ) );
 
 		const snippet = node.build( builder, type );

+ 27 - 0
examples/jsm/nodes/functions/BSDF/DFGApprox.js

@@ -0,0 +1,27 @@
+import {
+	ShaderNode, dotNV, vec2, vec4, add, mul, min, exp2
+} from '../../shadernode/ShaderNodeElements.js';
+
+// Analytical approximation of the DFG LUT, one half of the
+// split-sum approximation used in indirect specular lighting.
+// via 'environmentBRDF' from "Physically Based Shading on Mobile"
+// https://www.unrealengine.com/blog/physically-based-shading-on-mobile
+const DFGApprox = new ShaderNode( ( inputs ) => {
+
+	const { roughness } = inputs;
+
+	const c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );
+
+	const c1 = vec4( 1, 0.0425, 1.04, - 0.04 );
+
+	const r = add( mul( roughness, c0 ), c1 );
+
+	const a004 = add( mul( min( mul( r.x, r.x ), exp2( mul( - 9.28, dotNV ) ) ), r.x ), r.y );
+
+	const fab = add( mul( vec2( - 1.04, 1.04 ), a004 ), r.zw );
+
+	return fab;
+
+} );
+
+export default DFGApprox;

+ 74 - 7
examples/jsm/nodes/functions/PhysicalLightingModel.js

@@ -1,9 +1,61 @@
 import BRDF_Lambert from './BSDF/BRDF_Lambert.js';
 import BRDF_GGX from './BSDF/BRDF_GGX.js';
+import DFGApprox from './BSDF/DFGApprox.js';
 import {
-	ShaderNode, mul, saturate, dot, transformedNormalView,
-	diffuseColor, specularColor, roughness
-} from '../shadernode/ShaderNodeBaseElements.js';
+	ShaderNode,
+	vec3, mul, saturate, add, sub, dot, div, transformedNormalView,
+	pow, exp2, dotNV,
+	diffuseColor, specularColor, roughness, temp
+} from '../shadernode/ShaderNodeElements.js';
+
+// 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 = 1 ) => {
+
+	const fab = DFGApprox.call( { roughness } );
+
+	const FssEss = add( mul( specularColor, fab.x ), mul( specularF90, fab.y ) );
+
+	const Ess = add( fab.x, fab.y );
+	const Ems = sub( 1.0, Ess );
+
+	const Favg = add( specularColor, mul( sub( 1.0, specularColor ), 0.047619 ) ); // 1/21
+	const Fms = div( mul( FssEss, Favg ), sub( 1.0, mul( Ems, Favg ) ) );
+
+	singleScatter.add( FssEss );
+	multiScatter.add( mul( Fms, Ems ) );
+
+};
+
+const RE_IndirectSpecular_Physical = new ShaderNode( ( inputs ) => {
+
+	const { radiance, iblIrradiance, reflectedLight } = inputs;
+
+	// Both indirect specular and indirect diffuse light accumulate here
+
+	const singleScattering = temp( vec3() );
+	const multiScattering = temp( vec3() );
+	const cosineWeightedIrradiance = mul( iblIrradiance, 1 / Math.PI );
+
+	computeMultiscattering( singleScattering, multiScattering );
+
+	const diffuse = mul( diffuseColor, sub( 1.0, add( singleScattering, multiScattering ) ) );
+
+	reflectedLight.indirectSpecular.add( mul( radiance, singleScattering ) );
+	reflectedLight.indirectSpecular.add( mul( multiScattering, cosineWeightedIrradiance ) );
+
+	reflectedLight.indirectDiffuse.add( mul( diffuse, cosineWeightedIrradiance ) );
+
+} );
+
+const RE_IndirectDiffuse_Physical = new ShaderNode( ( inputs ) => {
+
+	const { irradiance, reflectedLight } = inputs;
+
+	reflectedLight.indirectDiffuse.add( mul( irradiance, BRDF_Lambert.call( { diffuseColor } ) ) );
+
+} );
 
 const RE_Direct_Physical = new ShaderNode( ( inputs ) => {
 
@@ -12,16 +64,31 @@ const RE_Direct_Physical = new ShaderNode( ( inputs ) => {
 	const dotNL = saturate( dot( transformedNormalView, lightDirection ) );
 	const irradiance = mul( dotNL, lightColor );
 
-	reflectedLight.directSpecular.add( mul( irradiance, BRDF_GGX.call( { lightDirection, f0: specularColor, f90: 1, roughness } ) ) );
-
 	reflectedLight.directDiffuse.add( mul( irradiance, BRDF_Lambert.call( { diffuseColor: diffuseColor.rgb } ) ) );
 
+	reflectedLight.directSpecular.add( mul( irradiance, BRDF_GGX.call( { lightDirection, f0: specularColor, f90: 1, roughness } ) ) );
+
 } );
 
-const PhysicalLightingModel = new ShaderNode( ( inputs/*, builder*/ ) => {
+const RE_AmbientOcclusion_Physical = new ShaderNode( ( { ambientOcclusion, reflectedLight } ) => {
+
+	const aoNV = add( dotNV, ambientOcclusion );
+	const aoExp = exp2( sub( mul( - 16.0, roughness ), 1.0 ) );
+
+	const aoNode = saturate( add( sub( pow( aoNV, aoExp ), 1.0 ), ambientOcclusion ) );
+
+	reflectedLight.indirectDiffuse.mul( ambientOcclusion );
+
+	reflectedLight.indirectSpecular.mul( aoNode );
 
-	RE_Direct_Physical.call( inputs );
 
 } );
 
+const PhysicalLightingModel = {
+	direct: RE_Direct_Physical,
+	indirectDiffuse: RE_IndirectDiffuse_Physical,
+	indirectSpecular: RE_IndirectSpecular_Physical,
+	ambientOcclusion: RE_AmbientOcclusion_Physical
+};
+
 export default PhysicalLightingModel;

+ 25 - 0
examples/jsm/nodes/lighting/AONode.js

@@ -0,0 +1,25 @@
+import LightingNode from './LightingNode.js';
+import { float, add, mul, sub } from '../shadernode/ShaderNodeElements.js';
+
+class AONode extends LightingNode {
+
+	constructor( aoNode = null ) {
+
+		super();
+
+		this.aoNode = aoNode;
+
+	}
+
+	generate( builder ) {
+
+		const aoIntensity = 1;
+		const aoNode = add( mul( sub( float( this.aoNode ), 1.0 ), aoIntensity ), 1.0 );
+
+		builder.context.ambientOcclusion.mul( aoNode );
+
+	}
+
+}
+
+export default AONode;

+ 37 - 0
examples/jsm/nodes/lighting/AnalyticLightNode.js

@@ -0,0 +1,37 @@
+import LightingNode from './LightingNode.js';
+import { NodeUpdateType } from '../core/constants.js';
+import { uniform } from '../shadernode/ShaderNodeElements.js';
+
+import { Color } from 'three';
+
+class AnalyticLightNode extends LightingNode {
+
+	constructor( light = null ) {
+
+		super();
+
+		this.updateType = NodeUpdateType.Object;
+
+		this.light = light;
+
+		this.colorNode = uniform( new Color() );
+
+	}
+
+	getHash( /*builder*/ ) {
+
+		return this.light.uuid;
+
+	}
+
+	update( /*frame*/ ) {
+
+		const { light } = this;
+
+		this.colorNode.value.copy( light.color ).multiplyScalar( light.intensity );
+
+	}
+
+}
+
+export default AnalyticLightNode;

+ 70 - 0
examples/jsm/nodes/lighting/EnvironmentLightNode.js

@@ -0,0 +1,70 @@
+import LightingNode from './LightingNode.js';
+import ContextNode from '../core/ContextNode.js';
+import MaxMipLevelNode from '../utils/MaxMipLevelNode.js';
+//import ReflectNode from '../accessors/ReflectNode.js';
+import { ShaderNode, float, add, mul, div, log2, clamp, roughness, reflect, mix, vec3, positionViewDirection, negate, normalize, transformedNormalView, transformedNormalWorld, transformDirection, cameraViewMatrix } from '../shadernode/ShaderNodeElements.js';
+
+// taken from here: http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html
+const getSpecularMIPLevel = new ShaderNode( ( { texture, levelNode } ) => {
+
+	const maxMIPLevelScalar = new MaxMipLevelNode( texture );
+
+	const sigma = div( mul( Math.PI, mul( levelNode, levelNode ) ), add( 1.0, levelNode ) );
+	const desiredMIPLevel = add( maxMIPLevelScalar, log2( sigma ) );
+
+	return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
+
+} );
+/*
+const getMaxMIPLevel = new ShaderNode( ( { texture } ) => {
+
+	return new MaxMipLevelNode( texture );
+
+} );
+*/
+class EnvironmentLightNode extends LightingNode {
+
+	constructor( envNode = null ) {
+
+		super();
+
+		this.envNode = envNode;
+
+	}
+
+	generate( builder ) {
+
+		const envNode = this.envNode;
+
+		const flipNormalWorld = vec3( negate( transformedNormalWorld.x ), transformedNormalWorld.yz );
+
+		let reflectVec = reflect( negate( positionViewDirection ), transformedNormalView );
+		reflectVec = normalize( mix( reflectVec, transformedNormalView, mul( roughness, roughness ) ) );
+		reflectVec = transformDirection( reflectVec, cameraViewMatrix );
+		reflectVec = vec3( negate( reflectVec.x ), reflectVec.yz );
+
+		//reflectVec = normalize( mix( new ReflectNode(), flipNormalWorld, mul( roughness, roughness ) ) );
+
+		const radianceContext = new ContextNode( envNode, {
+			tempRead: false,
+			uvNode: reflectVec,
+			levelNode: roughness,
+			levelShaderNode: getSpecularMIPLevel
+		} );
+
+		const irradianceContext = new ContextNode( envNode, {
+			tempRead: false,
+			uvNode: flipNormalWorld,
+			levelNode: float( 1 ),
+			levelShaderNode: getSpecularMIPLevel
+		} );
+
+		builder.context.radiance.add( radianceContext );
+
+		builder.context.iblIrradiance.add( mul( Math.PI, irradianceContext ) );
+
+	}
+
+}
+
+export default EnvironmentLightNode;

+ 50 - 0
examples/jsm/nodes/lighting/HemisphereLightNode.js

@@ -0,0 +1,50 @@
+import AnalyticLightNode from './AnalyticLightNode.js';
+import LightsNode from './LightsNode.js';
+import Object3DNode from '../accessors/Object3DNode.js';
+import { uniform, add, mul, dot, mix, normalize, normalView } from '../shadernode/ShaderNodeElements.js';
+
+import { Color, HemisphereLight } from 'three';
+
+class HemisphereLightNode extends AnalyticLightNode {
+
+	constructor( light = null ) {
+
+		super( light );
+
+		this.lightPositionNode = new Object3DNode( Object3DNode.POSITION );
+		this.lightDirectionNode = normalize( this.lightPositionNode );
+
+		this.groundColorNode = uniform( new Color() );
+
+	}
+
+	update( frame ) {
+
+		const { light } = this;
+
+		super.update( frame );
+
+		this.lightPositionNode.object3d = light;
+
+		this.groundColorNode.value.copy( light.groundColor ).multiplyScalar( light.intensity );
+
+	}
+
+	generate( builder ) {
+
+		const { colorNode, groundColorNode, lightDirectionNode } = this;
+
+		const dotNL = dot( normalView, lightDirectionNode );
+		const hemiDiffuseWeight = add( mul( 0.5, dotNL ), 0.5 );
+
+		const irradiance = mix( groundColorNode, colorNode, hemiDiffuseWeight );
+
+		builder.context.irradiance.add( irradiance );
+
+	}
+
+}
+
+LightsNode.setReference( HemisphereLight, HemisphereLightNode );
+
+export default HemisphereLightNode;

+ 62 - 0
examples/jsm/nodes/lighting/LightingContextNode.js

@@ -0,0 +1,62 @@
+import ContextNode from '../core/ContextNode.js';
+import { float, vec3, add, temp } from '../shadernode/ShaderNodeBaseElements.js';
+
+class LightingContextNode extends ContextNode {
+
+	constructor( node, lightingModelNode = null ) {
+
+		super( node );
+
+		this.lightingModelNode = lightingModelNode;
+
+	}
+
+	getNodeType( /*builder*/ ) {
+
+		return 'vec3';
+
+	}
+
+	generate( builder ) {
+
+		const { context, lightingModelNode } = this;
+
+		if ( context.reflectedLight === undefined ) {
+
+			const directDiffuse = temp( vec3() ),
+				directSpecular = temp( vec3() ),
+				indirectDiffuse = temp( vec3() ),
+				indirectSpecular = temp( vec3() );
+
+			context.reflectedLight = {
+				directDiffuse,
+				directSpecular,
+				indirectDiffuse,
+				indirectSpecular,
+				total: add( directDiffuse, directSpecular, indirectDiffuse, indirectSpecular )
+			};
+
+			context.radiance = temp( vec3() );
+			context.irradiance = temp( vec3() );
+			context.iblIrradiance = temp( vec3() );
+			context.ambientOcclusion = temp( float( 1 ) );
+
+		}
+
+		context.lightingModelNode = lightingModelNode || context.lightingModelNode;
+
+		const type = this.getNodeType( builder );
+
+		super.generate( builder, type );
+
+		if ( lightingModelNode?.indirectDiffuse ) lightingModelNode.indirectDiffuse.call( context );
+		if ( lightingModelNode?.indirectSpecular ) lightingModelNode.indirectSpecular.call( context );
+		if ( lightingModelNode?.ambientOcclusion ) lightingModelNode.ambientOcclusion.call( context );
+
+		return context.reflectedLight.total.build( builder, type );
+
+	}
+
+}
+
+export default LightingContextNode;

+ 19 - 0
examples/jsm/nodes/lighting/LightingNode.js

@@ -0,0 +1,19 @@
+import Node from '../core/Node.js';
+
+class LightingNode extends Node {
+
+	constructor() {
+
+		super( 'vec3' );
+
+	}
+
+	generate( /*builder*/ ) {
+
+		console.warn( 'Abstract function.' );
+
+	}
+
+}
+
+export default LightingNode;

+ 18 - 9
examples/jsm/nodes/lights/LightsNode.js → examples/jsm/nodes/lighting/LightsNode.js

@@ -1,5 +1,7 @@
 import Node from '../core/Node.js';
-import LightNode from './LightNode.js';
+import LightingNode from './LightingNode.js';
+
+const references = new WeakMap();
 
 const sortLights = ( lights ) => {
 
@@ -35,26 +37,24 @@ class LightsNode extends Node {
 
 		}
 
-		return 'vec3( 0.0 )';
-
 	}
 
-	getHash( /*builder*/ ) {
+	getHash( builder ) {
 
 		if ( this._hash === null ) {
 
 			let hash = '';
-			
+
 			const lightNodes = this.lightNodes;
 
 			for ( const lightNode of lightNodes ) {
 
-				hash += lightNode.light.uuid + ' ';
+				hash += lightNode.getHash( builder ) + ' ';
 
 			}
-			
+
 			this._hash = hash;
-			
+
 		}
 
 		return this._hash;
@@ -91,7 +91,10 @@ class LightsNode extends Node {
 
 			if ( lightNode === null ) {
 
-				lightNode = new LightNode( light );
+				const lightClass = light.constructor;
+				const lightNodeClass = references.has( lightClass ) ? references.get( lightClass ) : LightingNode;
+
+				lightNode = new lightNodeClass( light );
 
 			}
 
@@ -106,6 +109,12 @@ class LightsNode extends Node {
 
 	}
 
+	static setReference( lightClass, lightNodeClass ) {
+
+		references.set( lightClass, lightNodeClass );
+
+	}
+
 }
 
 export default LightsNode;

+ 68 - 0
examples/jsm/nodes/lighting/PunctualLightNode.js

@@ -0,0 +1,68 @@
+import AnalyticLightNode from './AnalyticLightNode.js';
+import LightsNode from './LightsNode.js';
+import Object3DNode from '../accessors/Object3DNode.js';
+import getDistanceAttenuation from '../functions/light/getDistanceAttenuation.js';
+import { uniform, mul, normalize, length, sub, positionView } from '../shadernode/ShaderNodeElements.js';
+
+import { PointLight } from 'three';
+
+class PunctualLightNode extends AnalyticLightNode {
+
+	constructor( light = null ) {
+
+		super( light );
+
+		this.cutoffDistanceNode = uniform( 0 );
+		this.decayExponentNode = uniform( 0 );
+
+	}
+
+	update( frame ) {
+
+		const { light } = this;
+
+		super.update( frame );
+
+		this.cutoffDistanceNode.value = light.distance;
+		this.decayExponentNode.value = light.decay;
+
+	}
+
+	generate( builder ) {
+
+		const { colorNode, cutoffDistanceNode, decayExponentNode } = this;
+
+		const lightPositionViewNode = new Object3DNode( Object3DNode.VIEW_POSITION, this.light );
+		const lVector = sub( lightPositionViewNode, positionView );
+
+		const lightDirection = normalize( lVector );
+		const lightDistance = length( lVector );
+
+		const lightAttenuation = getDistanceAttenuation.call( {
+			lightDistance,
+			cutoffDistance: cutoffDistanceNode,
+			decayExponent: decayExponentNode
+		} );
+
+		const lightColor = mul( colorNode, lightAttenuation );
+
+		const lightingModelFunctionNode = builder.context.lightingModelNode;
+		const reflectedLight = builder.context.reflectedLight;
+
+		if ( lightingModelFunctionNode?.direct ) {
+
+			lightingModelFunctionNode.direct.call( {
+				lightDirection,
+				lightColor,
+				reflectedLight
+			}, builder );
+
+		}
+
+	}
+
+}
+
+LightsNode.setReference( PointLight, PunctualLightNode );
+
+export default PunctualLightNode;

+ 0 - 42
examples/jsm/nodes/lights/LightContextNode.js

@@ -1,42 +0,0 @@
-import ContextNode from '../core/ContextNode.js';
-import { reflectedLight } from '../shadernode/ShaderNodeBaseElements.js';
-
-class LightContextNode extends ContextNode {
-
-	constructor( node, lightingModelNode = null ) {
-
-		super( node );
-
-		this.lightingModelNode = lightingModelNode;
-
-	}
-
-	getNodeType( /*builder*/ ) {
-
-		return 'vec3';
-
-	}
-
-	generate( builder ) {
-
-		const { lightingModelNode } = this;
-
-		this.context.reflectedLight = reflectedLight();
-
-		if ( lightingModelNode !== null ) {
-
-			this.context.lightingModelNode = lightingModelNode;
-
-		}
-
-		const type = this.getNodeType( builder );
-
-		super.generate( builder, type );
-
-		return this.context.reflectedLight.build( builder, type );
-
-	}
-
-}
-
-export default LightContextNode;

+ 0 - 82
examples/jsm/nodes/lights/LightNode.js

@@ -1,82 +0,0 @@
-import Node from '../core/Node.js';
-import Object3DNode from '../accessors/Object3DNode.js';
-import PositionNode from '../accessors/PositionNode.js';
-import UniformNode from '../core/UniformNode.js';
-import OperatorNode from '../math/OperatorNode.js';
-import MathNode from '../math/MathNode.js';
-import { NodeUpdateType } from '../core/constants.js';
-import getDistanceAttenuation from '../functions/light/getDistanceAttenuation.js';
-
-import { Color } from 'three';
-
-class LightNode extends Node {
-
-	constructor( light = null ) {
-
-		super( 'vec3' );
-
-		this.updateType = NodeUpdateType.Object;
-
-		this.light = light;
-
-		this._colorNode = new UniformNode( new Color() );
-
-		this._lightCutoffDistanceNode = new UniformNode( 0 );
-		this._lightDecayExponentNode = new UniformNode( 0 );
-
-	}
-
-	getHash( /*builder*/ ) {
-
-		return this.light.uuid;
-
-	}
-
-	update( /* frame */ ) {
-
-		this._colorNode.value.copy( this.light.color ).multiplyScalar( this.light.intensity );
-		this._lightCutoffDistanceNode.value = this.light.distance;
-		this._lightDecayExponentNode.value = this.light.decay;
-
-	}
-
-	generate( builder ) {
-
-		const lightPositionView = new Object3DNode( Object3DNode.VIEW_POSITION );
-		const positionView = new PositionNode( PositionNode.VIEW );
-
-		const lVector = new OperatorNode( '-', lightPositionView, positionView );
-
-		const lightDirection = new MathNode( MathNode.NORMALIZE, lVector );
-
-		const lightDistance = new MathNode( MathNode.LENGTH, lVector );
-
-		const lightAttenuation = getDistanceAttenuation.call( {
-			lightDistance,
-			cutoffDistance: this._lightCutoffDistanceNode,
-			decayExponent: this._lightDecayExponentNode
-		} );
-
-		const lightColor = new OperatorNode( '*', this._colorNode, lightAttenuation );
-
-		lightPositionView.object3d = this.light;
-
-		const lightingModelFunctionNode = builder.context.lightingModelNode;
-
-		if ( lightingModelFunctionNode !== undefined ) {
-
-			const reflectedLight = builder.context.reflectedLight;
-
-			lightingModelFunctionNode.call( {
-				lightDirection,
-				lightColor,
-				reflectedLight
-			}, builder );
-
-		}
-
-	}
-
-}
-
-export default LightNode;

+ 0 - 32
examples/jsm/nodes/lights/ReflectedLightNode.js

@@ -1,32 +0,0 @@
-import TempNode from '../core/Node.js';
-import VarNode from '../core/VarNode.js';
-import ConstNode from '../core/UniformNode.js';
-import OperatorNode from '../math/OperatorNode.js';
-import { Vector3 } from 'three';
-
-class ReflectedLightNode extends TempNode {
-
-	constructor() {
-
-		super( 'vec3' );
-
-		this.directDiffuse = new VarNode( new ConstNode( new Vector3() ), 'DirectDiffuse' );
-		this.directSpecular = new VarNode( new ConstNode( new Vector3() ), 'DirectSpecular' );
-		this.indirectDiffuse = new VarNode( new ConstNode( new Vector3() ), 'IndirectDiffuse' );
-		this.indirectSpecular = new VarNode( new ConstNode( new Vector3() ), 'IndirectSpecular' );
-
-	}
-
-	generate( builder ) {
-
-		const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = this;
-
-		const totalLight = new OperatorNode( '+', directDiffuse, directSpecular, indirectDiffuse, indirectSpecular );
-
-		return totalLight.build( builder );
-
-	}
-
-}
-
-export default ReflectedLightNode;

+ 50 - 18
examples/jsm/nodes/materials/MeshStandardNodeMaterial.js

@@ -1,12 +1,15 @@
 import NodeMaterial from './NodeMaterial.js';
 import {
-	float, vec3, vec4,
-	context, assign, label, mul, invert, mix,
-	normalView,
-	materialRoughness, materialMetalness
+	float, vec3, vec4, normalView, add, context,
+	assign, label, mul, invert, mix, texture, uniform,
+	materialRoughness, materialMetalness, materialEmissive
 } from '../shadernode/ShaderNodeElements.js';
+import LightsNode from '../lighting/LightsNode.js';
+import EnvironmentLightNode from '../lighting/EnvironmentLightNode.js';
+import AONode from '../lighting/AONode.js';
 import getRoughness from '../functions/material/getRoughness.js';
 import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
+import NormalMapNode from '../display/NormalMapNode.js';
 
 import { MeshStandardMaterial } from 'three';
 
@@ -35,7 +38,7 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 		this.envNode = null;
 
-		this.lightNode = null;
+		this.lightsNode = null;
 
 		this.positionNode = null;
 
@@ -47,34 +50,43 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 	build( builder ) {
 
-		const lightNode = this.lightNode || builder.lightNode; // use scene lights
-
 		let { colorNode, diffuseColorNode } = this.generateMain( builder );
+		const envNode = this.envNode || builder.scene.environmentNode;
 
 		diffuseColorNode = this.generateStandardMaterial( builder, { colorNode, diffuseColorNode } );
 
-		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightNode } );
+		if ( this.lightsNode ) builder.lightsNode = this.lightsNode;
 
-		this.generateOutput( builder, { diffuseColorNode, outgoingLightNode } );
+		let materialLightsNode = [];
 
-	}
+		if ( envNode ) {
 
-	generateLight( builder, { diffuseColorNode, lightNode } ) {
+			materialLightsNode.push( new EnvironmentLightNode( envNode ) );
 
-		let outgoingLightNode = super.generateLight( builder, { diffuseColorNode, lightNode, lightingModelNode: PhysicalLightingModel } );
+		}
 
-		// TONE MAPPING
+		if ( builder.material.aoMap ) {
 
-		const renderer = builder.renderer;
+			materialLightsNode.push( new AONode( texture( builder.material.aoMap ) ) );
 
-		if ( renderer.toneMappingNode ) outgoingLightNode = context( renderer.toneMappingNode, { color: outgoingLightNode } );
+		}
 
-		return outgoingLightNode;
+		if ( materialLightsNode.length > 0 ) {
+
+			builder.lightsNode = new LightsNode( [ ...builder.lightsNode.lightNodes, ...materialLightsNode ] );
+
+		}
+
+		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightingModelNode: PhysicalLightingModel } );
+
+		this.generateOutput( builder, { diffuseColorNode, outgoingLightNode } );
 
 	}
 
 	generateStandardMaterial( builder, { colorNode, diffuseColorNode } ) {
 
+		const { material } = builder;
+
 		// METALNESS
 
 		let metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness;
@@ -97,7 +109,7 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 		// NORMAL VIEW
 
-		const normalNode = this.normalNode ? vec3( this.normalNode ) : normalView;
+		const normalNode = this.normalNode ? vec3( this.normalNode ) : ( material.normalMap ? new NormalMapNode( texture( material.normalMap ), uniform( material.normalScale ) ) : normalView );
 
 		builder.addFlow( 'fragment', label( normalNode, 'TransformedNormalView' ) );
 
@@ -105,6 +117,26 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 	}
 
+	generateLight( builder, { diffuseColorNode, lightingModelNode, lightsNode = builder.lightsNode } ) {
+
+		const renderer = builder.renderer;
+
+		// OUTGOING LIGHT
+
+		let outgoingLightNode = super.generateLight( builder, { diffuseColorNode, lightingModelNode, lightsNode } );
+
+		// EMISSIVE
+
+		outgoingLightNode = add( vec3( this.emissiveNode || materialEmissive ), outgoingLightNode );
+
+		// TONE MAPPING
+
+		if ( renderer.toneMappingNode ) outgoingLightNode = context( renderer.toneMappingNode, { color: outgoingLightNode } );
+
+		return outgoingLightNode;
+
+	}
+
 	copy( source ) {
 
 		this.colorNode = source.colorNode;
@@ -124,7 +156,7 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 		this.envNode = source.envNode;
 
-		this.lightNode = source.lightNode;
+		this.lightsNode = source.lightsNode;
 
 		this.positionNode = source.positionNode;
 

+ 7 - 11
examples/jsm/nodes/materials/NodeMaterial.js

@@ -2,9 +2,9 @@ import { Material, ShaderMaterial } from 'three';
 import { getNodesKeys } from '../core/NodeUtils.js';
 import ExpressionNode from '../core/ExpressionNode.js';
 import {
-	float, vec3, vec4,
-	assign, label, mul, add, bypass,
-	positionLocal, skinning, instance, modelViewProjection, lightContext, colorSpace,
+	float, vec4,
+	assign, label, mul, bypass,
+	positionLocal, skinning, instance, modelViewProjection, lightingContext, colorSpace,
 	materialAlphaTest, materialColor, materialOpacity
 } from '../shadernode/ShaderNodeElements.js';
 
@@ -22,10 +22,10 @@ class NodeMaterial extends ShaderMaterial {
 
 	build( builder ) {
 
-		const { lightNode } = this;
+		const { lightsNode } = this;
 		const { diffuseColorNode } = this.generateMain( builder );
 
-		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightNode } );
+		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightsNode } );
 
 		this.generateOutput( builder, { diffuseColorNode, outgoingLightNode } );
 
@@ -93,18 +93,14 @@ class NodeMaterial extends ShaderMaterial {
 
 	}
 
-	generateLight( builder, { diffuseColorNode, lightNode, lightingModelNode } ) {
+	generateLight( builder, { diffuseColorNode, lightingModelNode, lightsNode = builder.lightsNode } ) {
 
 		// < ANALYTIC LIGHTS >
 
 		// OUTGOING LIGHT
 
 		let outgoingLightNode = diffuseColorNode.xyz;
-		if ( lightNode && lightNode.hasLight !== false ) outgoingLightNode = builder.addFlow( 'fragment', label( lightContext( lightNode, lightingModelNode ), 'Light' ) );
-
-		// EMISSIVE
-
-		if ( this.emissiveNode ) outgoingLightNode = add( vec3( this.emissiveNode ), outgoingLightNode );
+		if ( lightsNode && lightsNode.hasLight !== false ) outgoingLightNode = builder.addFlow( 'fragment', label( lightingContext( lightsNode, lightingModelNode ), 'Light' ) );
 
 		return outgoingLightNode;
 

+ 1 - 1
examples/jsm/nodes/math/CondNode.js

@@ -34,7 +34,7 @@ class CondNode extends Node {
 
 		const type = this.getNodeType( builder );
 
-		const context = { temp: false };
+		const context = { tempWrite: false };
 		const nodeProperty = new PropertyNode( null, type ).build( builder );
 
 		const nodeSnippet = new ContextNode( this.condNode/*, context*/ ).build( builder, 'bool' ),

+ 12 - 14
examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js

@@ -20,7 +20,7 @@ import MaterialNode from '../accessors/MaterialNode.js';
 import MaterialReferenceNode from '../accessors/MaterialReferenceNode.js';
 import ModelViewProjectionNode from '../accessors/ModelViewProjectionNode.js';
 import NormalNode from '../accessors/NormalNode.js';
-import Object3DNode from '../accessors/Object3DNode.js';
+import ModelNode from '../accessors/ModelNode.js';
 import PointUVNode from '../accessors/PointUVNode.js';
 import PositionNode from '../accessors/PositionNode.js';
 import ReferenceNode from '../accessors/ReferenceNode.js';
@@ -39,9 +39,6 @@ import MathNode from '../math/MathNode.js';
 import OperatorNode from '../math/OperatorNode.js';
 import CondNode from '../math/CondNode.js';
 
-// lights
-import ReflectedLightNode from '../lights/ReflectedLightNode.js';
-
 // utils
 import ArrayElementNode from '../utils/ArrayElementNode.js';
 import ConvertNode from '../utils/ConvertNode.js';
@@ -49,7 +46,7 @@ import ConvertNode from '../utils/ConvertNode.js';
 // shader node utils
 import { ShaderNode, nodeObject, nodeObjects, nodeArray, nodeProxy, nodeImmutable, ConvertType, getConstNodeType, cacheMaps } from './ShaderNode.js';
 
-// shader node utils
+// shader node base
 
 export { ShaderNode, nodeObject, nodeObjects, nodeArray, nodeProxy, nodeImmutable };
 
@@ -104,7 +101,7 @@ export const uniform = ( nodeOrType ) => {
 
 	const nodeType = getConstNodeType( nodeOrType );
 
-	// TODO: get ConstNode from .traverse() in the future
+	// @TODO: get ConstNode from .traverse() in the future
 	const value = nodeOrType.isNode === true ? nodeOrType.node?.value || nodeOrType.value : nodeOrType;
 
 	return nodeObject( new UniformNode( value, nodeType ) );
@@ -137,8 +134,9 @@ export const cameraPosition = nodeImmutable( CameraNode, CameraNode.POSITION );
 
 export const materialAlphaTest = nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );
 export const materialColor = nodeImmutable( MaterialNode, MaterialNode.COLOR );
+export const materialEmissive = nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );
 export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY );
-export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
+//export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
 export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
 export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS );
 
@@ -159,11 +157,11 @@ export const normalWorld = nodeImmutable( NormalNode, NormalNode.WORLD );
 export const normalView = nodeImmutable( NormalNode, NormalNode.VIEW );
 export const transformedNormalView = nodeImmutable( VarNode, normalView, 'TransformedNormalView' );
 
-export const viewMatrix = nodeProxy( Object3DNode, Object3DNode.VIEW_MATRIX );
-export const normalMatrix = nodeProxy( Object3DNode, Object3DNode.NORMAL_MATRIX );
-export const worldMatrix = nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX );
-export const position = nodeProxy( Object3DNode, Object3DNode.POSITION );
-export const viewPosition = nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION );
+export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX );
+export const modelNormalMatrix = nodeImmutable( ModelNode, ModelNode.NORMAL_MATRIX );
+export const modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX );
+export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION );
+export const modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITION );
 
 export const positionGeometry = nodeImmutable( PositionNode, PositionNode.GEOMETRY );
 export const positionLocal = nodeImmutable( PositionNode, PositionNode.LOCAL );
@@ -260,9 +258,8 @@ export const faceforward = nodeProxy( MathNode, MathNode.FACEFORWARD );
 export const frontFacing = nodeImmutable( FrontFacingNode );
 export const faceDirection = sub( mul( float( frontFacing ), 2 ), 1 );
 
-// lights
+// lighting
 
-export const reflectedLight = nodeProxy( ReflectedLightNode );
 
 // utils
 
@@ -271,3 +268,4 @@ export const element = nodeProxy( ArrayElementNode );
 // miscellaneous
 
 export const dotNV = saturate( dot( transformedNormalView, positionViewDirection ) );
+export const transformedNormalWorld = normalize( transformDirection( transformedNormalView, cameraViewMatrix ) );

+ 11 - 9
examples/jsm/nodes/shadernode/ShaderNodeElements.js

@@ -9,10 +9,10 @@ import ColorSpaceNode from '../display/ColorSpaceNode.js';
 import NormalMapNode from '../display/NormalMapNode.js';
 import ToneMappingNode from '../display/ToneMappingNode.js';
 
-// lights
-import LightNode from '../lights/LightNode.js';
-import LightsNode from '../lights/LightsNode.js';
-import LightContextNode from '../lights/LightContextNode.js';
+// lighting
+import LightsNode from '../lighting/LightsNode.js';
+//import LightingNode from '../lighting/LightingNode.js';
+import LightingContextNode from '../lighting/LightingContextNode.js';
 
 // utils
 import MatcapUVNode from '../utils/MatcapUVNode.js';
@@ -35,7 +35,7 @@ import { nodeObject, nodeProxy, nodeImmutable } from './ShaderNode.js';
 // Node Material Shader Syntax
 //
 
-// shader node utils
+// shader node base
 
 export * from './ShaderNodeBaseElements.js';
 
@@ -44,6 +44,7 @@ export * from './ShaderNodeBaseElements.js';
 export { default as BRDF_GGX } from '../functions/BSDF/BRDF_GGX.js'; // see https://github.com/tc39/proposal-export-default-from
 export { default as BRDF_Lambert } from '../functions/BSDF/BRDF_Lambert.js';
 export { default as D_GGX } from '../functions/BSDF/D_GGX.js';
+export { default as DFGApprox } from '../functions/BSDF/DFGApprox.js';
 export { default as F_Schlick } from '../functions/BSDF/F_Schlick.js';
 export { default as V_GGX_SmithCorrelated } from '../functions/BSDF/V_GGX_SmithCorrelated.js';
 
@@ -71,11 +72,12 @@ export const colorSpace = ( node, encoding ) => nodeObject( new ColorSpaceNode(
 export const normalMap = nodeProxy( NormalMapNode );
 export const toneMapping = ( mapping, exposure, color ) => nodeObject( new ToneMappingNode( mapping, nodeObject( exposure ), nodeObject( color ) ) );
 
-// lights
+// lighting
 
-export const light = nodeProxy( LightNode );
-export const fromLights = ( lights ) => nodeObject( new LightsNode().fromLights( lights ) );
-export const lightContext = nodeProxy( LightContextNode );
+//export const lighting = nodeProxy( LightingNode ); // abstract
+//export const light; // still needs to be added
+export const lights = ( lights ) => nodeObject( new LightsNode().fromLights( lights ) );
+export const lightingContext = nodeProxy( LightingContextNode );
 
 // utils
 

+ 4 - 4
examples/jsm/nodes/utils/MaxMipLevelNode.js

@@ -15,13 +15,13 @@ class MaxMipLevelNode extends UniformNode {
 
 	update() {
 
-		const { width, height } = this.texture.images ? this.texture.images[ 0 ] : this.texture.image;
+		const image = this.texture.images ? this.texture.images[ 0 ].image || this.texture.images[ 0 ] : this.texture.image;
 
-		this.value = Math.log( Math.max( width, height ) ) * Math.LOG2E;
+		if ( image?.width !== undefined ) {
 
-		if ( this.value > 0 ) {
+			const { width, height } = image;
 
-			this.updateType = NodeUpdateType.None;
+			this.value = Math.log2( Math.max( width, height ) );
 
 		}
 

+ 2 - 2
examples/jsm/renderers/webgpu/WebGPURenderStates.js

@@ -1,4 +1,4 @@
-import LightsNode from 'three-nodes/lights/LightsNode.js';
+import LightsNode from 'three-nodes/lighting/LightsNode.js';
 
 class WebGPURenderState {
 
@@ -22,7 +22,7 @@ class WebGPURenderState {
 
 	}
 
-	getLightNode() {
+	getLightsNode() {
 
 		return this.lightsNode.fromLights( this.lightsArray );
 

+ 7 - 7
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -312,17 +312,17 @@ class WebGPURenderer {
 
 		}
 
-		// light node
+		// lights node
 
-		const lightNode = this._currentRenderState.getLightNode();
+		const lightsNode = this._currentRenderState.getLightsNode();
 
 		// process render lists
 
 		const opaqueObjects = this._currentRenderList.opaque;
 		const transparentObjects = this._currentRenderList.transparent;
 
-		if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, scene, lightNode, passEncoder );
-		if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, scene, lightNode, passEncoder );
+		if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, scene, lightsNode, passEncoder );
+		if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, scene, lightsNode, passEncoder );
 
 		// finish render pass
 
@@ -755,7 +755,7 @@ class WebGPURenderer {
 
 	}
 
-	_renderObjects( renderList, camera, scene, lightNode, passEncoder ) {
+	_renderObjects( renderList, camera, scene, lightsNode, passEncoder ) {
 
 		// process renderable objects
 
@@ -777,8 +777,8 @@ class WebGPURenderer {
 
 			const objectProperties = this._properties.get( object );
 
-			objectProperties.lightNode = lightNode;
-			objectProperties.fogNode = scene.fogNode;
+			objectProperties.lightsNode = lightsNode;
+			objectProperties.scene = scene;
 
 			if ( camera.isArrayCamera ) {
 

+ 22 - 9
examples/jsm/renderers/webgpu/WebGPUTextures.js

@@ -363,7 +363,7 @@ class WebGPUTextures {
 
 			if ( image.length === 6 ) {
 
-				this._copyCubeMapToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps );
+				this._copyCubeMapToTexture( image, format, texture, textureGPU, textureGPUDescriptor, needsMipmaps );
 
 			}
 
@@ -391,7 +391,7 @@ class WebGPUTextures {
 
 	}
 
-	_copyBufferToTexture( image, format, textureGPU ) {
+	_copyBufferToTexture( image, format, textureGPU, origin = { x: 0, y: 0, z: 0 } ) {
 
 		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
 		// @TODO: Consider to support valid buffer layouts with other formats like RGB
@@ -404,7 +404,8 @@ class WebGPUTextures {
 		this.device.queue.writeTexture(
 			{
 				texture: textureGPU,
-				mipLevel: 0
+				mipLevel: 0,
+				origin
 			},
 			data,
 			{
@@ -419,19 +420,29 @@ class WebGPUTextures {
 
 	}
 
-	_copyCubeMapToTexture( images, texture, textureGPU, textureGPUDescriptor, needsMipmaps ) {
+	_copyCubeMapToTexture( images, format, texture, textureGPU, textureGPUDescriptor, needsMipmaps ) {
 
 		for ( let i = 0; i < 6; i ++ ) {
 
 			const image = images[ i ];
 
-			this._getImageBitmap( image, texture ).then( imageBitmap => {
+			if ( image.isDataTexture ) {
 
-				this._copyExternalImageToTexture( imageBitmap, textureGPU, { z : i } );
+				this._copyBufferToTexture( image.image, format, textureGPU, { z : i } );
 
 				if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor, i );
 
-			} );
+			} else {
+
+				this._getImageBitmap( image, texture ).then( imageBitmap => {
+
+					this._copyExternalImageToTexture( imageBitmap, textureGPU, { z : i } );
+
+					if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor, i );
+
+				} );
+
+			}
 
 		}
 
@@ -703,8 +714,10 @@ class WebGPUTextures {
 
 		if ( texture.isCubeTexture ) {
 
-			width = ( image.length > 0 ) ? image[ 0 ].width : 1;
-			height = ( image.length > 0 ) ? image[ 0 ].height : 1;
+			const faceImage = image.length > 0 ? image[ 0 ].image || image[ 0 ] : null;
+
+			width = faceImage?.width || 1;
+			height = faceImage?.height || 1;
 			depth = 6; // one image for each side of the cube map
 
 		} else if ( image !== null ) {

+ 6 - 9
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -97,9 +97,6 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		super( object, renderer, new WGSLNodeParser() );
 
-		this.lightNode = null;
-		this.fogNode = null;
-
 		this.bindings = { vertex: [], fragment: [], compute: [] };
 		this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 };
 
@@ -162,11 +159,11 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getSamplerBias( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
+	getSamplerLevel( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
 
 		if ( shaderStage === 'fragment' ) {
 
-			return `textureSampleBias( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${biasSnippet} )`;
+			return `textureSampleLevel( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${biasSnippet} )`;
 
 		} else {
 
@@ -186,9 +183,9 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getTextureBias( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
+	getTextureLevel( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
 
-		return this.getSamplerBias( textureProperty, uvSnippet, biasSnippet, shaderStage );
+		return this.getSamplerLevel( textureProperty, uvSnippet, biasSnippet, shaderStage );
 
 	}
 
@@ -198,9 +195,9 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getCubeTextureBias( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
+	getCubeTextureLevel( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
 
-		return this.getSamplerBias( textureProperty, uvSnippet, biasSnippet, shaderStage );
+		return this.getSamplerLevel( textureProperty, uvSnippet, biasSnippet, shaderStage );
 
 	}
 

+ 5 - 4
examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js

@@ -20,12 +20,13 @@ class WebGPUNodes {
 
 		if ( nodeBuilder === undefined ) {
 
-			const fogNode = objectProperties.fogNode;
-			const lightNode = objectProperties.lightNode;
+			const scene = objectProperties.scene;
+			const lightsNode = objectProperties.lightsNode;
 
 			nodeBuilder = new WebGPUNodeBuilder( object, this.renderer );
-			nodeBuilder.lightNode = lightNode;
-			nodeBuilder.fogNode = fogNode;
+			nodeBuilder.lightsNode = lightsNode;
+			nodeBuilder.fogNode = scene?.fogNode;
+			nodeBuilder.scene = scene;
 			nodeBuilder.build();
 
 			objectProperties.nodeBuilder = nodeBuilder;

BIN
examples/screenshots/webgpu_loader_gltf.jpg


+ 2 - 2
examples/webgpu_compute.html

@@ -29,7 +29,7 @@
 			import * as Nodes from 'three-nodes/Nodes.js';
 
 			import {
-				ShaderNode, compute, context,
+				ShaderNode, compute,
 				uniform, element, storage, attribute,
 				temp, assign, add, sub, cond, abs, negate, max, min, length, vec3, color,
 				greaterThanEqual, lessThanEqual, instanceIndex
@@ -99,7 +99,7 @@
 					const pointer = uniform( pointerVector );
 					const limit = uniform( scaleVector );
 
-					const position = temp( context( add( particle, velocity ), { temp: false } ) );
+					const position = temp( add( particle, velocity ), 'tempPos' ); // @TODO: this should work without 'tempPos' property name
 					position.build( builder );
 
 					assign( velocity.x, cond( greaterThanEqual( abs( position.x ), limit.x ), negate( velocity.x ), velocity.x ) ).build( builder );

+ 4 - 2
examples/webgpu_lights_custom.html

@@ -99,9 +99,9 @@
 
 				} );
 
-				const lightingModelContext = new Nodes.LightContextNode( allLightsNode, customLightingModel );
+				const lightingModelContext = new Nodes.ContextNode( allLightsNode, { lightingModelNode: { direct : customLightingModel } } );
 
-				materialPoints.lightNode = lightingModelContext;
+				materialPoints.lightsNode = lightingModelContext;
 
 				//
 
@@ -113,6 +113,8 @@
 				renderer = new WebGPURenderer();
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMappingNode = new Nodes.ToneMappingNode( THREE.LinearToneMapping, 1 );
+				renderer.outputEncoding = THREE.sRGBEncoding;
 				document.body.appendChild( renderer.domElement );
 
 				// controls

+ 4 - 4
examples/webgpu_lights_selective.html

@@ -99,15 +99,15 @@
 
 				//light nodes ( selective lights )
 
-				const redLightNode = new Nodes.LightsNode().fromLights( [ light1 ] );
-				const blueLightNode = new Nodes.LightsNode().fromLights( [ light2 ] );
+				const redLightsNode = new Nodes.LightsNode().fromLights( [ light1 ] );
+				const blueLightsNode = new Nodes.LightsNode().fromLights( [ light2 ] );
 
 				//models
 
 				const geometryTeapot = new TeapotGeometry( 8, 18 );
 
 				const leftObject = new THREE.Mesh( geometryTeapot, new Nodes.MeshStandardNodeMaterial( { color: 0x555555 } ) );
-				leftObject.material.lightNode = redLightNode;
+				leftObject.material.lightsNode = redLightsNode;
 				leftObject.material.roughnessNode = new Nodes.TextureNode( alphaTexture );
 				leftObject.material.metalness = 0;
 				leftObject.position.x = - 30;
@@ -120,7 +120,7 @@
 				scene.add( centerObject );
 
 				const rightObject = new THREE.Mesh( geometryTeapot, new Nodes.MeshStandardNodeMaterial( { color: 0x555555 } ) );
-				rightObject.material.lightNode = blueLightNode;
+				rightObject.material.lightsNode = blueLightsNode;
 				rightObject.material.metalnessNode = new Nodes.TextureNode( alphaTexture );
 				rightObject.position.x = 30;
 				scene.add( rightObject );

+ 145 - 0
examples/webgpu_loader_gltf.html

@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - GLTFloader</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<br />
+			Battle Damaged Sci-fi Helmet by
+			<a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br />
+			<a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> by <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-nodes/": "./jsm/nodes/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import * as Nodes from 'three-nodes/Nodes.js';
+
+			import WebGPU from './jsm/capabilities/WebGPU.js';
+			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
+
+			import { RGBMLoader } from './jsm/loaders/RGBMLoader.js';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
+
+			let camera, scene, renderer;
+
+			init().then( render ).catch( error );
+
+			async function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 );
+				camera.position.set( - 1.8, 0.6, 2.7 );
+
+				scene = new THREE.Scene();
+
+				const rgbmUrls = [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ];
+
+				const cubeTexture = new RGBMLoader()
+					.setMaxRange( 16 )
+					.setPath( './textures/cube/pisaRGBM16/' )
+					.loadCubemap( rgbmUrls );
+
+				cubeTexture.generateMipmaps = true;
+				cubeTexture.minFilter = THREE.LinearMipmapLinearFilter;
+
+				//scene.backgroundNode = texture;
+				scene.environmentNode = new Nodes.CubeTextureNode( cubeTexture );
+
+				const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
+				loader.load( 'DamagedHelmet.gltf', function ( gltf ) {
+
+					//const light = new THREE.PointLight( 0xffffff );
+					//camera.add( light );
+
+					scene.add( gltf.scene );
+
+					render();
+
+				} );
+
+				renderer = new WebGPURenderer();
+
+				/*// WebGLRenderer comparation test
+				renderer = new THREE.WebGLRenderer( { antialias: false } );
+				renderer.toneMapping = THREE.LinearToneMapping;
+				renderer.toneMappingExposure = 1;
+				scene.environment = cubeTexture;
+				document.getElementById( 'info' ).innerText = 'WebGL';
+				/**/
+
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMappingNode = new Nodes.ToneMappingNode( THREE.LinearToneMapping, 1 );
+				renderer.outputEncoding = THREE.sRGBEncoding;
+				container.appendChild( renderer.domElement );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 2;
+				controls.maxDistance = 10;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				if ( renderer.init ) return renderer.init();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function render() {
+
+				requestAnimationFrame( render );
+
+				renderer.render( scene, camera );
+
+			}
+
+			function error( error ) {
+
+				console.error( error );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 0 - 2
examples/webgpu_skinning.html

@@ -35,8 +35,6 @@
 			import WebGPU from './jsm/capabilities/WebGPU.js';
 			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
 
-			import LightsNode from 'three-nodes/lights/LightsNode.js';
-
 			let camera, scene, renderer;
 
 			let mixer, clock;