Browse Source

WebGPURenderer: Shadows Node-Based (#25822)

* PositionNode: Fix positionWorld and positionWorldDirection

* NodeFrame: Added update from RENDER.

* Added WebGPURenderTarget

* Node: Added .updateBefore()

* EquirectUVNode: Fix from new positionWorldDirection

* NodeBuilder: Added .getRenderTarget()

* WebGPUNodes: Fix cache and added updateBefore()

* WebGPURenderObjects: Remove dummy cameras.

* WebGPURenderStates: Replace WeakMap to WebGPUWeakMap

* Added ShadowMap Node-Based

* update examples

* cleanup

* Update examples/jsm/nodes/lighting/AnalyticLightNode.js

Co-authored-by: Levi Pesin <[email protected]>

* update title

* renames updateMap -> frameMap, rendererMap -> renderMap

---------

Co-authored-by: Levi Pesin <[email protected]>
sunag 2 năm trước cách đây
mục cha
commit
b1fa158cd6
28 tập tin đã thay đổi với 558 bổ sung125 xóa
  1. 1 0
      examples/files.json
  2. 4 2
      examples/jsm/nodes/Nodes.js
  3. 2 2
      examples/jsm/nodes/accessors/PositionNode.js
  4. 13 0
      examples/jsm/nodes/core/Node.js
  5. 52 3
      examples/jsm/nodes/core/NodeFrame.js
  6. 1 0
      examples/jsm/nodes/core/constants.js
  7. 0 16
      examples/jsm/nodes/functions/light/getDirectionVector.js
  8. 108 5
      examples/jsm/nodes/lighting/AnalyticLightNode.js
  9. 5 15
      examples/jsm/nodes/lighting/DirectionalLightNode.js
  10. 57 0
      examples/jsm/nodes/lighting/LightNode.js
  11. 2 4
      examples/jsm/nodes/lighting/LightUtils.js
  12. 2 2
      examples/jsm/nodes/lighting/PointLightNode.js
  13. 8 12
      examples/jsm/nodes/lighting/SpotLightNode.js
  14. 2 1
      examples/jsm/nodes/utils/EquirectUVNode.js
  15. 2 2
      examples/jsm/renderers/webgpu/WebGPUAnimation.js
  16. 2 2
      examples/jsm/renderers/webgpu/WebGPUBackground.js
  17. 2 17
      examples/jsm/renderers/webgpu/WebGPURenderObjects.js
  18. 8 6
      examples/jsm/renderers/webgpu/WebGPURenderStates.js
  19. 15 0
      examples/jsm/renderers/webgpu/WebGPURenderTarget.js
  20. 19 2
      examples/jsm/renderers/webgpu/WebGPURenderer.js
  21. 2 4
      examples/jsm/renderers/webgpu/WebGPUTextureRenderer.js
  22. 8 0
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  23. 40 21
      examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js
  24. BIN
      examples/screenshots/webgpu_shadowmap.jpg
  25. 5 5
      examples/webgpu_cubemap_adjustments.html
  26. 6 1
      examples/webgpu_materials.html
  27. 3 3
      examples/webgpu_particles.html
  28. 189 0
      examples/webgpu_shadowmap.html

+ 1 - 0
examples/files.json

@@ -344,6 +344,7 @@
 		"webgpu_particles",
 		"webgpu_rtt",
 		"webgpu_sandbox",
+		"webgpu_shadowmap",
 		"webgpu_skinning",
 		"webgpu_skinning_instancing",
 		"webgpu_skinning_points",

+ 4 - 2
examples/jsm/nodes/Nodes.js

@@ -31,7 +31,8 @@ export { default as UniformNode, uniform } from './core/UniformNode.js';
 export { default as VarNode, label, temp } from './core/VarNode.js';
 export { default as VaryingNode, varying } from './core/VaryingNode.js';
 
-export * as NodeUtils from './core/NodeUtils.js';
+import * as NodeUtils from './core/NodeUtils.js';
+export { NodeUtils };
 
 // math
 export { default as MathNode, EPSILON, INFINITY, radians, degrees, exp, exp2, log, log2, sqrt, inverseSqrt, floor, ceil, normalize, fract, sin, cos, tan, asin, acos, atan, abs, sign, length, negate, oneMinus, dFdx, dFdy, round, reciprocal, atan2, min, max, mod, step, reflect, distance, difference, dot, cross, pow, pow2, pow3, pow4, transformDirection, mix, clamp, saturate, refract, smoothstep, faceForward } from './math/MathNode.js';
@@ -113,6 +114,7 @@ export { default as RangeNode, range } from './geometry/RangeNode.js';
 export { default as ComputeNode, compute } from './gpgpu/ComputeNode.js';
 
 // lighting
+export { default as LightNode, lightTargetDirection } from './lighting/LightNode.js';
 export { default as PointLightNode } from './lighting/PointLightNode.js';
 export { default as DirectionalLightNode } from './lighting/DirectionalLightNode.js';
 export { default as SpotLightNode } from './lighting/SpotLightNode.js';
@@ -153,7 +155,7 @@ 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';
 
-export { default as getDistanceAttenuation } from './functions/light/getDistanceAttenuation.js';
+export { getDistanceAttenuation } from './lighting/LightUtils.js';
 
 export { default as getGeometryRoughness } from './functions/material/getGeometryRoughness.js';
 export { default as getRoughness } from './functions/material/getRoughness.js';

+ 2 - 2
examples/jsm/nodes/accessors/PositionNode.js

@@ -43,7 +43,7 @@ class PositionNode extends Node {
 
 		} else if ( scope === PositionNode.WORLD ) {
 
-			const vertexPositionNode = modelWorldMatrix.transformDirection( positionLocal );
+			const vertexPositionNode = modelWorldMatrix.mul( positionLocal );
 			outputNode = varying( vertexPositionNode );
 
 		} else if ( scope === PositionNode.VIEW ) {
@@ -58,7 +58,7 @@ class PositionNode extends Node {
 
 		} else if ( scope === PositionNode.WORLD_DIRECTION ) {
 
-			const vertexPositionNode = positionWorld.negate();
+			const vertexPositionNode = positionLocal.transformDirection( modelWorldMatrix );
 			outputNode = normalize( varying( vertexPositionNode ) );
 
 		}

+ 13 - 0
examples/jsm/nodes/core/Node.js

@@ -15,6 +15,7 @@ class Node {
 		this.nodeType = nodeType;
 
 		this.updateType = NodeUpdateType.NONE;
+		this.updateBeforeType = NodeUpdateType.NONE;
 
 		this.uuid = MathUtils.generateUUID();
 
@@ -92,6 +93,12 @@ class Node {
 
 	}
 
+	getUpdateBeforeType() {
+
+		return this.updateBeforeType;
+
+	}
+
 	getNodeType( /*builder*/ ) {
 
 		return this.nodeType;
@@ -159,6 +166,12 @@ class Node {
 
 	}
 
+	updateBefore( /*frame*/ ) {
+
+		console.warn( 'Abstract function.' );
+
+	}
+
 	update( /*frame*/ ) {
 
 		console.warn( 'Abstract function.' );

+ 52 - 3
examples/jsm/nodes/core/NodeFrame.js

@@ -8,15 +8,53 @@ class NodeFrame {
 		this.deltaTime = 0;
 
 		this.frameId = 0;
+		this.renderId = 0;
 
 		this.startTime = null;
 
-		this.updateMap = new WeakMap();
+		this.frameMap = new WeakMap();
+		this.frameBeforeMap = new WeakMap();
+		this.renderMap = new WeakMap();
+		this.renderBeforeMap = new WeakMap();
 
 		this.renderer = null;
 		this.material = null;
 		this.camera = null;
 		this.object = null;
+		this.scene = null;
+
+	}
+
+	updateBeforeNode( node ) {
+
+		const updateType = node.getUpdateBeforeType();
+
+		if ( updateType === NodeUpdateType.FRAME ) {
+
+			if ( this.frameBeforeMap.get( node ) !== this.frameId ) {
+
+				this.frameBeforeMap.set( node, this.frameId );
+
+				node.updateBefore( this );
+
+			}
+
+		} else if ( updateType === NodeUpdateType.RENDER ) {
+
+			if ( this.renderBeforeMap.get( node ) !== this.renderId || this.frameBeforeMap.get( node ) !== this.frameId ) {
+
+				this.renderBeforeMap.set( node, this.renderId );
+				this.frameBeforeMap.set( node, this.frameId );
+
+				node.updateBefore( this );
+
+			}
+
+		} else if ( updateType === NodeUpdateType.OBJECT ) {
+
+			node.updateBefore( this );
+
+		}
 
 	}
 
@@ -26,9 +64,20 @@ class NodeFrame {
 
 		if ( updateType === NodeUpdateType.FRAME ) {
 
-			if ( this.updateMap.get( node ) !== this.frameId ) {
+			if ( this.frameMap.get( node ) !== this.frameId ) {
+
+				this.frameMap.set( node, this.frameId );
+
+				node.update( this );
+
+			}
+
+		} else if ( updateType === NodeUpdateType.RENDER ) {
+
+			if ( this.renderMap.get( node ) !== this.renderId || this.frameMap.get( node ) !== this.frameId ) {
 
-				this.updateMap.set( node, this.frameId );
+				this.renderMap.set( node, this.renderId );
+				this.frameMap.set( node, this.frameId );
 
 				node.update( this );
 

+ 1 - 0
examples/jsm/nodes/core/constants.js

@@ -6,6 +6,7 @@ export const NodeShaderStage = {
 export const NodeUpdateType = {
 	NONE: 'none',
 	FRAME: 'frame',
+	RENDER: 'render',
 	OBJECT: 'object'
 };
 

+ 0 - 16
examples/jsm/nodes/functions/light/getDirectionVector.js

@@ -1,16 +0,0 @@
-import { Vector3 } from 'three';
-
-let vector3;
-
-const getDirectionVector = ( light, camera, directionVector ) => {
-
-	vector3 || ( vector3 = new Vector3() );
-
-	directionVector.setFromMatrixPosition( light.matrixWorld );
-	vector3.setFromMatrixPosition( light.target.matrixWorld );
-	directionVector.sub( vector3 );
-	directionVector.transformDirection( camera.matrixWorldInverse );
-
-};
-
-export default getDirectionVector;

+ 108 - 5
examples/jsm/nodes/lighting/AnalyticLightNode.js

@@ -2,8 +2,16 @@ import LightingNode from './LightingNode.js';
 import { NodeUpdateType } from '../core/constants.js';
 import { uniform } from '../core/UniformNode.js';
 import { addNodeClass } from '../core/Node.js';
+import { vec3 } from '../shadernode/ShaderNode.js';
+import { reference } from '../accessors/ReferenceNode.js';
+import { texture } from '../accessors/TextureNode.js';
+import { positionWorld } from '../accessors/PositionNode.js';
+import { cond } from '../math/CondNode.js';
+import MeshBasicNodeMaterial from '../materials/MeshBasicNodeMaterial.js';
 
-import { Color } from 'three';
+import { Color, DepthTexture, NearestFilter } from 'three';
+
+let depthMaterial = null;
 
 class AnalyticLightNode extends LightingNode {
 
@@ -11,11 +19,15 @@ class AnalyticLightNode extends LightingNode {
 
 		super();
 
-		this.updateType = NodeUpdateType.OBJECT;
+		this.updateType = NodeUpdateType.FRAME;
 
 		this.light = light;
 
-		this.colorNode = uniform( new Color() );
+		this.rtt = null;
+		this.shadowNode = null;
+
+		this.color = new Color();
+		this.colorNode = uniform( this.color );
 
 	}
 
@@ -25,11 +37,102 @@ class AnalyticLightNode extends LightingNode {
 
 	}
 
-	update( /*frame*/ ) {
+	constructShadow( builder ) {
+
+		let shadowNode = this.shadowNode;
+
+		if ( shadowNode === null ) {
+
+			if ( depthMaterial === null ) depthMaterial = new MeshBasicNodeMaterial();
+
+			const shadow = this.light.shadow;
+			const rtt = builder.getRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
+
+			const depthTexture = new DepthTexture();
+			depthTexture.minFilter = NearestFilter;
+			depthTexture.magFilter = NearestFilter;
+
+			rtt.depthTexture = depthTexture;
+
+			shadow.camera.updateProjectionMatrix();
+
+			//
+
+			const bias = reference( 'bias', 'float', shadow );
+
+			//const diffuseFactor = normalView.dot( objectViewPosition( this.light ).sub( positionView ).normalize().negate() );
+			//bias = mix( bias, 0, diffuseFactor );
+
+			let shadowCoord = uniform( shadow.matrix ).mul( positionWorld );
+			shadowCoord = shadowCoord.xyz.div( shadowCoord.w );
+
+			shadowCoord = vec3(
+				shadowCoord.x,
+				shadowCoord.y.oneMinus(),
+				shadowCoord.z
+			);
+
+			// @TODO: Optimize using WebGPU compare-sampler
+
+			let depth = texture( depthTexture, shadowCoord.xy );
+			depth = depth.mul( .5 ).add( .5 ).add( bias );
+
+			shadowNode = cond( shadowCoord.z.lessThan( depth ).or( shadowCoord.y.lessThan( .000001 ) /*@TODO: find the cause and remove it soon */ ), 1, 0 );
+			//shadowNode = step( shadowCoord.z, depth );
+
+			//
+
+			this.rtt = rtt;
+			this.colorNode = this.colorNode.mul( shadowNode );
+
+			this.shadowNode = shadowNode;
+
+			//
+
+			this.updateBeforeType = NodeUpdateType.RENDER;
+
+		}
+
+	}
+
+	construct( builder ) {
+
+		if ( this.light.castShadow ) this.constructShadow( builder );
+
+	}
+
+	updateShadow( frame ) {
+
+		const { rtt, light } = this;
+		const { renderer, scene } = frame;
+
+		scene.overrideMaterial = depthMaterial;
+
+		rtt.setSize( light.shadow.mapSize.width, light.shadow.mapSize.height );
+
+		light.shadow.updateMatrices( light );
+
+		renderer.setRenderTarget( rtt );
+		renderer.render( scene, light.shadow.camera );
+		renderer.setRenderTarget( null );
+
+		scene.overrideMaterial = null;
+
+	}
+
+	updateBefore( frame ) {
+
+		const { light } = this;
+
+		if ( light.castShadow ) this.updateShadow( frame );
+
+	}
+
+	update( frame ) {
 
 		const { light } = this;
 
-		this.colorNode.value.copy( light.color ).multiplyScalar( light.intensity );
+		this.color.copy( light.color ).multiplyScalar( light.intensity );
 
 	}
 

+ 5 - 15
examples/jsm/nodes/lighting/DirectionalLightNode.js

@@ -1,10 +1,9 @@
 import AnalyticLightNode from './AnalyticLightNode.js';
+import { lightTargetDirection } from './LightNode.js';
 import { addLightNode } from './LightsNode.js';
-import getDirectionVector from '../functions/light/getDirectionVector.js';
-import { uniform } from '../core/UniformNode.js';
 import { addNodeClass } from '../core/Node.js';
 
-import { Vector3, DirectionalLight } from 'three';
+import { DirectionalLight } from 'three';
 
 class DirectionalLightNode extends AnalyticLightNode {
 
@@ -12,23 +11,14 @@ class DirectionalLightNode extends AnalyticLightNode {
 
 		super( light );
 
-		this.directionNode = uniform( new Vector3() );
-
-	}
-
-	update( frame ) {
-
-		getDirectionVector( this.light, frame.camera, this.directionNode.value );
-
-		super.update( frame );
-
 	}
 
 	construct( builder ) {
 
-		const lightDirection = this.directionNode.normalize();
-		const lightColor = this.colorNode;
+		super.construct( builder );
 
+		const lightColor = this.colorNode;
+		const lightDirection = lightTargetDirection( this.light );
 		const lightingModelFunctionNode = builder.context.lightingModelNode;
 		const reflectedLight = builder.context.reflectedLight;
 

+ 57 - 0
examples/jsm/nodes/lighting/LightNode.js

@@ -0,0 +1,57 @@
+import Node, { addNodeClass } from '../core/Node.js';
+import { nodeProxy } from '../shadernode/ShaderNode.js';
+import { objectPosition } from '../accessors/Object3DNode.js';
+import { cameraViewMatrix } from '../accessors/CameraNode.js';
+
+class LightNode extends Node {
+
+	constructor( scope = LightNode.TARGET_DIRECTION, light = null ) {
+
+		super();
+
+		this.scope = scope;
+		this.light = light;
+
+	}
+
+	construct() {
+
+		const { scope, light } = this;
+
+		let output = null;
+
+		if ( scope === LightNode.TARGET_DIRECTION ) {
+
+			output = cameraViewMatrix.transformDirection( objectPosition( light ).sub( objectPosition( light.target ) ) );
+
+		}
+
+		return output;
+
+	}
+
+	serialize( data ) {
+
+		super.serialize( data );
+
+		data.scope = this.scope;
+
+	}
+
+	deserialize( data ) {
+
+		super.deserialize( data );
+
+		this.scope = data.scope;
+
+	}
+
+}
+
+LightNode.TARGET_DIRECTION = 'targetDirection';
+
+export default LightNode;
+
+export const lightTargetDirection = nodeProxy( LightNode, LightNode.TARGET_DIRECTION );
+
+addNodeClass( LightNode );

+ 2 - 4
examples/jsm/nodes/functions/light/getDistanceAttenuation.js → examples/jsm/nodes/lighting/LightUtils.js

@@ -1,6 +1,6 @@
-import { ShaderNode } from '../../shadernode/ShaderNode.js';
+import { ShaderNode } from '../shadernode/ShaderNode.js';
 
-const getDistanceAttenuation = new ShaderNode( ( inputs ) => {
+export const getDistanceAttenuation = new ShaderNode( ( inputs ) => {
 
 	const { lightDistance, cutoffDistance, decayExponent } = inputs;
 
@@ -15,5 +15,3 @@ const getDistanceAttenuation = new ShaderNode( ( inputs ) => {
 	);
 
 } ); // validated
-
-export default getDistanceAttenuation;

+ 2 - 2
examples/jsm/nodes/lighting/PointLightNode.js

@@ -1,6 +1,6 @@
 import AnalyticLightNode from './AnalyticLightNode.js';
 import { addLightNode } from './LightsNode.js';
-import getDistanceAttenuation from '../functions/light/getDistanceAttenuation.js';
+import { getDistanceAttenuation } from './LightUtils.js';
 import { uniform } from '../core/UniformNode.js';
 import { objectViewPosition } from '../accessors/Object3DNode.js';
 import { positionView } from '../accessors/PositionNode.js';
@@ -34,7 +34,7 @@ class PointLightNode extends AnalyticLightNode {
 
 		const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this;
 
-		const lVector = objectViewPosition( light ).sub( positionView );
+		const lVector = objectViewPosition( light ).sub( positionView ); // @TODO: Add it into LightNode
 
 		const lightDirection = lVector.normalize();
 		const lightDistance = lVector.length();

+ 8 - 12
examples/jsm/nodes/lighting/SpotLightNode.js

@@ -1,14 +1,14 @@
 import AnalyticLightNode from './AnalyticLightNode.js';
+import { lightTargetDirection } from './LightNode.js';
 import { addLightNode } from './LightsNode.js';
-import getDistanceAttenuation from '../functions/light/getDistanceAttenuation.js';
-import getDirectionVector from '../functions/light/getDirectionVector.js';
+import { getDistanceAttenuation } from './LightUtils.js';
 import { uniform } from '../core/UniformNode.js';
 import { smoothstep } from '../math/MathNode.js';
 import { objectViewPosition } from '../accessors/Object3DNode.js';
 import { positionView } from '../accessors/PositionNode.js';
 import { addNodeClass } from '../core/Node.js';
 
-import { Vector3, SpotLight } from 'three';
+import { SpotLight } from 'three';
 
 class SpotLightNode extends AnalyticLightNode {
 
@@ -16,8 +16,6 @@ class SpotLightNode extends AnalyticLightNode {
 
 		super( light );
 
-		this.directionNode = uniform( new Vector3() );
-
 		this.coneCosNode = uniform( 0 );
 		this.penumbraCosNode = uniform( 0 );
 
@@ -32,8 +30,6 @@ class SpotLightNode extends AnalyticLightNode {
 
 		const { light } = this;
 
-		getDirectionVector( light, frame.camera, this.directionNode.value );
-
 		this.coneCosNode.value = Math.cos( light.angle );
 		this.penumbraCosNode.value = Math.cos( light.angle * ( 1 - light.penumbra ) );
 
@@ -52,12 +48,14 @@ class SpotLightNode extends AnalyticLightNode {
 
 	construct( builder ) {
 
+		super.construct( builder );
+
 		const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this;
 
-		const lVector = objectViewPosition( light ).sub( positionView );
+		const lVector = objectViewPosition( light ).sub( positionView ); // @TODO: Add it into LightNode
 
 		const lightDirection = lVector.normalize();
-		const angleCos = lightDirection.dot( this.directionNode );
+		const angleCos = lightDirection.dot( lightTargetDirection( light ) );
 		const spotAttenuation = this.getSpotAttenuation( angleCos );
 
 		const lightDistance = lVector.length();
@@ -68,9 +66,7 @@ class SpotLightNode extends AnalyticLightNode {
 			decayExponent: decayExponentNode
 		} );
 
-		const finalColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
-
-		const lightColor = spotAttenuation.greaterThan( 0 ).cond( finalColor, 0 );
+		const lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
 
 		const lightingModelFunctionNode = builder.context.lightingModelNode;
 		const reflectedLight = builder.context.reflectedLight;

+ 2 - 1
examples/jsm/nodes/utils/EquirectUVNode.js

@@ -1,4 +1,5 @@
 import TempNode from '../core/TempNode.js';
+import { negate } from '../math/MathNode.js';
 import { positionWorldDirection } from '../accessors/PositionNode.js';
 import { nodeProxy, vec2 } from '../shadernode/ShaderNode.js';
 import { addNodeClass } from '../core/Node.js';
@@ -15,7 +16,7 @@ class EquirectUVNode extends TempNode {
 
 	construct() {
 
-		const dir = this.dirNode;
+		const dir = negate( this.dirNode );
 
 		const u = dir.z.atan2( dir.x ).mul( 1 / ( Math.PI * 2 ) ).add( 0.5 );
 		const v = dir.y.clamp( - 1.0, 1.0 ).asin().mul( 1 / Math.PI ).add( 0.5 );

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

@@ -23,9 +23,9 @@ class WebGPUAnimation {
 
 			this.requestId = self.requestAnimationFrame( update );
 
-			this.animationLoop( time, frame );
+			this.nodes.nodeFrame.update();
 
-			this.nodes.updateFrame();
+			this.animationLoop( time, frame );
 
 		};
 

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

@@ -1,6 +1,6 @@
 import { GPULoadOp, GPUStoreOp } from './constants.js';
 import { Color, Mesh, BoxGeometry, BackSide } from 'three';
-import { context, transformDirection, positionWorld, modelWorldMatrix, MeshBasicNodeMaterial } from 'three/nodes';
+import { context, positionWorldDirection, MeshBasicNodeMaterial } from 'three/nodes';
 
 let _clearAlpha;
 const _clearColor = new Color();
@@ -61,7 +61,7 @@ class WebGPUBackground {
 
 				this.boxMeshNode = context( backgroundNode, {
 					// @TODO: Add Texture2D support using node context
-					getUVNode: () => transformDirection( positionWorld, modelWorldMatrix )
+					getUVNode: () => positionWorldDirection
 				} );
 
 				const nodeMaterial = new MeshBasicNodeMaterial();

+ 2 - 17
examples/jsm/renderers/webgpu/WebGPURenderObjects.js

@@ -1,18 +1,5 @@
 import WebGPUWeakMap from './WebGPUWeakMap.js';
 import WebGPURenderObject from './WebGPURenderObject.js';
-import { PerspectiveCamera, OrthographicCamera } from 'three';
-
-const dummyCameras = {
-	PerspectiveCamera: new PerspectiveCamera(),
-	OrthographicCamera: new OrthographicCamera()
-};
-
-function getChainKeys( object, material, scene, camera, lightsNode ) {
-
-	// use dummy camera for optimize cache in case of use others cameras with the same type
-	return [ object, material, scene, dummyCameras[ camera.type ], lightsNode ];
-
-}
 
 class WebGPURenderObjects {
 
@@ -29,7 +16,7 @@ class WebGPURenderObjects {
 
 	get( object, material, scene, camera, lightsNode ) {
 
-		const chainKey = getChainKeys( object, material, scene, camera, lightsNode );
+		const chainKey = [ object, material, scene, camera, lightsNode ];
 
 		let renderObject = this.cache.get( chainKey );
 
@@ -47,9 +34,7 @@ class WebGPURenderObjects {
 
 	remove( object, material, scene, camera, lightsNode ) {
 
-		const chainKey = getChainKeys( object, material, scene, camera, lightsNode );
-
-		this.cache.delete( chainKey );
+		this.cache.delete( [ object, material, scene, camera, lightsNode ] );
 
 	}
 

+ 8 - 6
examples/jsm/renderers/webgpu/WebGPURenderStates.js

@@ -1,3 +1,4 @@
+import WebGPUWeakMap from './WebGPUWeakMap.js';
 import { lights } from 'three/nodes';
 
 class WebGPURenderState {
@@ -34,20 +35,21 @@ class WebGPURenderStates {
 
 	constructor() {
 
-		this.renderStates = new WeakMap();
+		this.renderStates = new WebGPUWeakMap();
 
 	}
 
-	get( scene, /* camera */ ) {
+	get( scene, camera ) {
 
-		const renderStates = this.renderStates;
+		const chainKey = [ scene, camera ];
 
-		let renderState = renderStates.get( scene );
+		let renderState = this.renderStates.get( chainKey );
 
 		if ( renderState === undefined ) {
 
 			renderState = new WebGPURenderState();
-			renderStates.set( scene, renderState );
+
+			this.renderStates.set( chainKey, renderState );
 
 		}
 
@@ -57,7 +59,7 @@ class WebGPURenderStates {
 
 	dispose() {
 
-		this.renderStates = new WeakMap();
+		this.renderStates = new WebGPUWeakMap();
 
 	}
 

+ 15 - 0
examples/jsm/renderers/webgpu/WebGPURenderTarget.js

@@ -0,0 +1,15 @@
+import { WebGLRenderTarget } from 'three';
+
+// @TODO: Consider rename WebGLRenderTarget to just RenderTarget
+
+class WebGPURenderTarget extends WebGLRenderTarget {
+
+	constructor( width, height, options = {} ) {
+
+		super( width, height, options );
+
+	}
+
+}
+
+export default WebGPURenderTarget;

+ 19 - 2
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -170,6 +170,12 @@ class WebGPURenderer {
 		this._parameters.requiredFeatures = ( parameters.requiredFeatures === undefined ) ? [] : parameters.requiredFeatures;
 		this._parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;
 
+		// backwards compatibility
+
+		this.shadow = {
+			shadowMap: {}
+		};
+
 	}
 
 	async init() {
@@ -246,7 +252,12 @@ class WebGPURenderer {
 
 		//
 
-		if ( this._animation.isAnimating === false ) this._nodes.updateFrame();
+		const nodeFrame = this._nodes.nodeFrame;
+
+		let previousRenderId = nodeFrame.renderId;
+		nodeFrame.renderId ++;
+
+		if ( this._animation.isAnimating === false ) nodeFrame.update();
 
 		if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
 
@@ -260,7 +271,7 @@ class WebGPURenderer {
 		this._currentRenderList = this._renderLists.get( scene, camera );
 		this._currentRenderList.init();
 
-		this._currentRenderState = this._renderStates.get( scene );
+		this._currentRenderState = this._renderStates.get( scene, camera );
 		this._currentRenderState.init();
 
 		this._projectObject( scene, camera, 0 );
@@ -383,6 +394,8 @@ class WebGPURenderer {
 		passEncoder.end();
 		device.queue.submit( [ cmdEncoder.finish() ] );
 
+		nodeFrame.renderId = previousRenderId;
+
 	}
 
 	setAnimationLoop( callback ) {
@@ -848,6 +861,10 @@ class WebGPURenderer {
 
 		//
 
+		this._nodes.updateBefore( renderObject );
+
+		//
+
 		object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
 		object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
 

+ 2 - 4
examples/jsm/renderers/webgpu/WebGPUTextureRenderer.js

@@ -1,4 +1,4 @@
-import { WebGLRenderTarget } from 'three';
+import WebGPURenderTarget from './WebGPURenderTarget.js';
 
 class WebGPUTextureRenderer {
 
@@ -6,9 +6,7 @@ class WebGPUTextureRenderer {
 
 		this.renderer = renderer;
 
-		// @TODO: Consider to introduce WebGPURenderTarget or rename WebGLRenderTarget to just RenderTarget
-
-		this.renderTarget = new WebGLRenderTarget( options );
+		this.renderTarget = new WebGPURenderTarget( 1, 1, options );
 
 	}
 

+ 8 - 0
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -10,6 +10,8 @@ import WebGPUUniformBuffer from '../WebGPUUniformBuffer.js';
 import WebGPUStorageBuffer from '../WebGPUStorageBuffer.js';
 import { getVectorLength, getStrideLength } from '../WebGPUBufferUtils.js';
 
+import WebGPURenderTarget from '../WebGPURenderTarget.js';
+
 import { NodeBuilder, WGSLNodeParser, CodeNode, NodeMaterial } from 'three/nodes';
 
 const gpuShaderStageLib = {
@@ -686,6 +688,12 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
+	getRenderTarget( width, height, options ) {
+
+		return new WebGPURenderTarget( width, height, options );
+
+	}
+
 	getMethod( method ) {
 
 		if ( wgslPolyfill[ method ] !== undefined ) {

+ 40 - 21
examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js

@@ -64,12 +64,6 @@ class WebGPUNodes {
 
 	}
 
-	updateFrame() {
-
-		this.nodeFrame.update();
-
-	}
-
 	getEnvironmentNode( scene ) {
 
 		return scene.environmentNode || this.properties.get( scene ).environmentNode || null;
@@ -114,17 +108,17 @@ class WebGPUNodes {
 
 		if ( rendererToneMapping !== NoToneMapping ) {
 
-			if ( rendererProperties.toneMappingCacheKey !== rendererToneMapping ) {
+			if ( rendererProperties.toneMapping !== rendererToneMapping ) {
 
-				rendererProperties.toneMappingNode = toneMapping( renderer.toneMapping, reference( 'toneMappingExposure', 'float', renderer ) );
-				rendererProperties.toneMappingCacheKey = rendererToneMapping;
+				rendererProperties.toneMappingNode = toneMapping( rendererToneMapping, reference( 'toneMappingExposure', 'float', renderer ) );
+				rendererProperties.toneMapping = rendererToneMapping;
 
 			}
 
 		} else {
 
 			delete rendererProperties.toneMappingNode;
-			delete rendererProperties.toneMappingCacheKey;
+			delete rendererProperties.toneMapping;
 
 		}
 
@@ -137,7 +131,7 @@ class WebGPUNodes {
 
 		if ( background ) {
 
-			if ( sceneProperties.backgroundCacheKey !== background.uuid ) {
+			if ( sceneProperties.background !== background ) {
 
 				let backgroundNode = null;
 
@@ -164,17 +158,21 @@ class WebGPUNodes {
 
 					backgroundNode = texture( background, nodeUV );
 
+				} else if ( background.isColor !== true ) {
+
+					console.error( 'WebGPUNodes: Unsupported background configuration.', background );
+
 				}
 
 				sceneProperties.backgroundNode = backgroundNode;
-				sceneProperties.backgroundCacheKey = background.uuid;
+				sceneProperties.background = background;
 
 			}
 
 		} else if ( sceneProperties.backgroundNode ) {
 
 			delete sceneProperties.backgroundNode;
-			delete sceneProperties.backgroundCacheKey;
+			delete sceneProperties.background;
 
 		}
 
@@ -187,7 +185,7 @@ class WebGPUNodes {
 
 		if ( fog ) {
 
-			if ( sceneProperties.fogCacheKey !== fog.uuid ) {
+			if ( sceneProperties.fog !== fog ) {
 
 				let fogNode = null;
 
@@ -206,14 +204,14 @@ class WebGPUNodes {
 				}
 
 				sceneProperties.fogNode = fogNode;
-				sceneProperties.fogCacheKey = fog.uuid;
+				sceneProperties.fog = fog;
 
 			}
 
 		} else {
 
 			delete sceneProperties.fogNode;
-			delete sceneProperties.fogCacheKey;
+			delete sceneProperties.fog;
 
 		}
 
@@ -226,7 +224,7 @@ class WebGPUNodes {
 
 		if ( environment ) {
 
-			if ( sceneProperties.environmentCacheKey !== environment.uuid ) {
+			if ( sceneProperties.environment !== environment ) {
 
 				let environmentNode = null;
 
@@ -245,30 +243,51 @@ class WebGPUNodes {
 				}
 
 				sceneProperties.environmentNode = environmentNode;
-				sceneProperties.environmentCacheKey = environment.uuid;
+				sceneProperties.environment = environment;
 
 			}
 
 		} else if ( sceneProperties.environmentNode ) {
 
 			delete sceneProperties.environmentNode;
-			delete sceneProperties.environmentCacheKey;
+			delete sceneProperties.environment;
 
 		}
 
 	}
 
-	update( renderObject ) {
+	getUpdateNodes( renderObject ) {
 
 		const nodeBuilder = this.get( renderObject );
 		const nodeFrame = this.nodeFrame;
 
+		nodeFrame.scene = renderObject.scene;
 		nodeFrame.object = renderObject.object;
 		nodeFrame.camera = renderObject.camera;
 		nodeFrame.renderer = renderObject.renderer;
 		nodeFrame.material = renderObject.material;
 
-		for ( const node of nodeBuilder.updateNodes ) {
+		return nodeBuilder.updateNodes;
+
+	}
+
+	updateBefore( renderObject ) {
+
+		const nodeFrame = this.nodeFrame;
+
+		for ( const node of this.getUpdateNodes( renderObject ) ) {
+
+			nodeFrame.updateBeforeNode( node );
+
+		}
+
+	}
+
+	update( renderObject ) {
+
+		const nodeFrame = this.nodeFrame;
+
+		for ( const node of this.getUpdateNodes( renderObject ) ) {
 
 			nodeFrame.updateNode( node );
 

BIN
examples/screenshots/webgpu_shadowmap.jpg


+ 5 - 5
examples/webgpu_cubemap_adjustments.html

@@ -31,7 +31,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { uniform, mix, cubeTexture, reference, positionWorld, normalWorld, modelWorldMatrix, reflectVector, toneMapping } from 'three/nodes';
+			import { uniform, mix, cubeTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector, toneMapping } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
@@ -105,11 +105,11 @@
 				const rotateY1Matrix = new THREE.Matrix4();
 				const rotateY2Matrix = new THREE.Matrix4();
 
-				const getEnvironmentNode = ( reflectNode ) => {
+				const getEnvironmentNode = ( reflectNode, positionNode ) => {
 
 					const custom1UV = reflectNode.xyz.mul( uniform( rotateY1Matrix ) );
 					const custom2UV = reflectNode.xyz.mul( uniform( rotateY2Matrix ) );
-					const mixCubeMaps = mix( cubeTexture( cube1Texture, custom1UV ), cubeTexture( cube2Texture, custom2UV ), positionWorld.y.add( mixNode ).clamp() );
+					const mixCubeMaps = mix( cubeTexture( cube1Texture, custom1UV ), cubeTexture( cube2Texture, custom2UV ), positionNode.y.add( mixNode ).clamp() );
 
 					const proceduralEnv = mix( mixCubeMaps, normalWorld, proceduralNode );
 
@@ -121,9 +121,9 @@
 
 				const blurNode = uniform( 0 );
 
-				scene.environmentNode = getEnvironmentNode( reflectVector );
+				scene.environmentNode = getEnvironmentNode( reflectVector, positionWorld );
 
-				scene.backgroundNode = getEnvironmentNode( positionWorld.transformDirection( modelWorldMatrix ) ).context( {
+				scene.backgroundNode = getEnvironmentNode( positionWorldDirection, positionLocal ).context( {
 					getSamplerLevelNode: () => blurNode
 				} );
 

+ 6 - 1
examples/webgpu_materials.html

@@ -29,7 +29,7 @@
 			import * as THREE from 'three';
 			import * as Nodes from 'three/nodes';
 
-			import { attribute, positionLocal, normalLocal, normalWorld, normalView, color, texture, ShaderNode, func, uv, vec3, triplanarTexture, viewportBottomLeft, js, string, global, MeshBasicNodeMaterial, NodeObjectLoader } from 'three/nodes';
+			import { attribute, positionLocal, positionWorld, normalLocal, normalWorld, normalView, color, texture, ShaderNode, func, uv, vec3, triplanarTexture, viewportBottomLeft, js, string, global, MeshBasicNodeMaterial, NodeObjectLoader } from 'three/nodes';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
@@ -94,6 +94,11 @@
 				material.colorNode = positionLocal;
 				materials.push( material );
 
+				// PositionWorld
+				material = new MeshBasicNodeMaterial();
+				material.colorNode = positionWorld;
+				materials.push( material );
+
 				// NormalLocal
 				material = new MeshBasicNodeMaterial();
 				material.colorNode = normalLocal;

+ 3 - 3
examples/webgpu_particles.html

@@ -26,7 +26,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { range, texture, mix, uv, color, positionWorld, timerLocal, attribute, SpriteNodeMaterial } from 'three/nodes';
+			import { range, texture, mix, uv, color, positionLocal, timerLocal, attribute, SpriteNodeMaterial } from 'three/nodes';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
@@ -76,13 +76,13 @@
 
 				const life = lifeTime.div( lifeRange );
 
-				const fakeLightEffect = positionWorld.y.oneMinus().max( 0.2 );
+				const fakeLightEffect = positionLocal.y.oneMinus().max( 0.2 );
 
 				const textureNode = texture( map, uv().rotateUV( timer.mul( rotateRange ) ) );
 
 				const opacityNode = textureNode.a.mul( life.oneMinus() );
 
-				const smokeColor = mix( color( 0x2c1501 ), color( 0x222222 ), positionWorld.y.mul( 3 ).clamp() );
+				const smokeColor = mix( color( 0x2c1501 ), color( 0x222222 ), positionLocal.y.mul( 3 ).clamp() );
 
 				// create particles
 

+ 189 - 0
examples/webgpu_shadowmap.html

@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - shadow map</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> - webgpu shadow map
+		</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/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</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';
+
+			let camera, scene, renderer, clock;
+			let dirLight, spotLight;
+			let torusKnot, dirGroup;
+
+			init();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( 0, 10, 20 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x222244 );
+				scene.fog = new THREE.Fog( 0x222244, 50, 100 );
+
+				// lights
+
+				scene.add( new THREE.AmbientLight( 0x444444 ) );
+
+				spotLight = new THREE.SpotLight( 0xff8888, 300 );
+				spotLight.angle = Math.PI / 5;
+				spotLight.penumbra = 0.3;
+				spotLight.position.set( 8, 10, 5 );
+				spotLight.castShadow = true;
+				spotLight.shadow.camera.near = 8;
+				spotLight.shadow.camera.far = 1000;
+				spotLight.shadow.mapSize.width = 2048;
+				spotLight.shadow.mapSize.height = 2048;
+				spotLight.shadow.bias = .01;
+				scene.add( spotLight );
+
+				dirLight = new THREE.DirectionalLight( 0x8888ff, 2 );
+				dirLight.position.set( 3, 20, 12 );
+				dirLight.castShadow = true;
+				dirLight.shadow.camera.near = 8;
+				dirLight.shadow.camera.far = 500;
+				dirLight.shadow.camera.right = 17;
+				dirLight.shadow.camera.left = - 17;
+				dirLight.shadow.camera.top	= 17;
+				dirLight.shadow.camera.bottom = - 17;
+				dirLight.shadow.mapSize.width = 2048;
+				dirLight.shadow.mapSize.height = 2048;
+				dirLight.shadow.bias = .0001;
+
+				dirGroup = new THREE.Group();
+				dirGroup.add( dirLight );
+				scene.add( dirGroup );
+
+				// geometry
+
+				const geometry = new THREE.TorusKnotGeometry( 25, 8, 75, 20 );
+				const material = new THREE.MeshPhongMaterial( {
+					color: 0x999999,
+					shininess: 0,
+					specular: 0x222222
+				} );
+
+				torusKnot = new THREE.Mesh( geometry, material );
+				torusKnot.scale.multiplyScalar( 1 / 18 );
+				torusKnot.position.y = 3;
+				torusKnot.castShadow = true;
+				torusKnot.receiveShadow = true;
+				scene.add( torusKnot );
+
+				const cylinderGeometry = new THREE.CylinderGeometry( 0.75, 0.75, 7, 32 );
+
+				const pillar1 = new THREE.Mesh( cylinderGeometry, material );
+				pillar1.position.set( 8, 3.5, 8 );
+				pillar1.castShadow = true;
+				pillar1.receiveShadow = true;
+
+				const pillar2 = pillar1.clone();
+				pillar2.position.set( 8, 3.5, - 8 );
+				const pillar3 = pillar1.clone();
+				pillar3.position.set( - 8, 3.5, 8 );
+				const pillar4 = pillar1.clone();
+				pillar4.position.set( - 8, 3.5, - 8 );
+
+				scene.add( pillar1 );
+				scene.add( pillar2 );
+				scene.add( pillar3 );
+				scene.add( pillar4 );
+
+				const planeGeometry = new THREE.PlaneGeometry( 200, 200 );
+				const planeMaterial = new THREE.MeshPhongMaterial( {
+					color: 0x999999,
+					shininess: 0,
+					specular: 0x111111
+				} );
+
+				const ground = new THREE.Mesh( planeGeometry, planeMaterial );
+				ground.rotation.x = - Math.PI / 2;
+				ground.scale.multiplyScalar( 3 );
+				ground.castShadow = true;
+				ground.receiveShadow = true;
+				scene.add( ground );
+
+				// renderer
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
+				document.body.appendChild( renderer.domElement );
+
+				// Mouse control
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 2, 0 );
+				controls.update();
+
+				clock = new THREE.Clock();
+
+				window.addEventListener( 'resize', resize );
+
+			}
+
+			function resize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate( time ) {
+
+				const delta = clock.getDelta();
+
+				torusKnot.rotation.x += 0.25 * delta;
+				torusKnot.rotation.y += 0.5 * delta;
+				torusKnot.rotation.z += 1 * delta;
+
+				dirGroup.rotation.y += 0.7 * delta;
+				dirLight.position.z = 17 + Math.sin( time * 0.001 ) * 5;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>