浏览代码

WebGPURenderStates: Basic implementation (#23652)

* add Node.id property

* Move static .fromLights() to property and update .getHash()

* WebGPURenderStates: minimal implementation
sunag 3 年之前
父节点
当前提交
64babaa523

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

@@ -2,6 +2,8 @@ import { NodeUpdateType } from './constants.js';
 import { getNodesKeys } from './NodeUtils.js';
 import { MathUtils } from 'three';
 
+let _nodeId = 0;
+
 class Node {
 
 	constructor( nodeType = null ) {
@@ -12,6 +14,8 @@ class Node {
 
 		this.uuid = MathUtils.generateUUID();
 
+		Object.defineProperty( this, 'id', { value: _nodeId ++ } );
+
 	}
 
 	get type() {

+ 15 - 9
examples/jsm/nodes/lights/LightNode.js

@@ -20,18 +20,24 @@ class LightNode extends Node {
 
 		this.light = light;
 
-		this.colorNode = new ColorNode( new Color() );
+		this._colorNode = new ColorNode( new Color() );
 
-		this.lightCutoffDistanceNode = new FloatNode( 0 );
-		this.lightDecayExponentNode = new FloatNode( 0 );
+		this._lightCutoffDistanceNode = new FloatNode( 0 );
+		this._lightDecayExponentNode = new FloatNode( 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;
+		this._colorNode.value.copy( this.light.color ).multiplyScalar( this.light.intensity );
+		this._lightCutoffDistanceNode.value = this.light.distance;
+		this._lightDecayExponentNode.value = this.light.decay;
 
 	}
 
@@ -48,11 +54,11 @@ class LightNode extends Node {
 
 		const lightAttenuation = getDistanceAttenuation( {
 			lightDistance,
-			cutoffDistance: this.lightCutoffDistanceNode,
-			decayExponent: this.lightDecayExponentNode
+			cutoffDistance: this._lightCutoffDistanceNode,
+			decayExponent: this._lightDecayExponentNode
 		} );
 
-		const lightColor = new OperatorNode( '*', this.colorNode, lightAttenuation );
+		const lightColor = new OperatorNode( '*', this._colorNode, lightAttenuation );
 
 		lightPositionView.object3d = this.light;
 

+ 70 - 3
examples/jsm/nodes/lights/LightsNode.js

@@ -1,6 +1,12 @@
 import Node from '../core/Node.js';
 import LightNode from './LightNode.js';
 
+const sortLights = ( lights ) => {
+
+	return lights.sort( ( a, b ) => a.id - b.id );
+
+};
+
 class LightsNode extends Node {
 
 	constructor( lightNodes = [] ) {
@@ -9,6 +15,14 @@ class LightsNode extends Node {
 
 		this.lightNodes = lightNodes;
 
+		this._hash = null;
+
+	}
+
+	get hasLight() {
+
+		return this.lightNodes.length > 0;
+
 	}
 
 	generate( builder ) {
@@ -25,17 +39,70 @@ class LightsNode extends Node {
 
 	}
 
-	static fromLights( lights ) {
+	getHash( /*builder*/ ) {
+
+		if ( this._hash === null ) {
+
+			let hash = '';
+			
+			const lightNodes = this.lightNodes;
+
+			for ( const lightNode of lightNodes ) {
+
+				hash += lightNode.light.uuid + ' ';
+
+			}
+			
+			this._hash = hash;
+			
+		}
+
+		return this._hash;
+
+	}
+
+	getLightNodeByHash( hash ) {
+
+		const lightNodes = this.lightNodes;
+
+		for ( const lightNode of lightNodes ) {
+
+			if ( lightNode.light.uuid === hash ) {
+
+				return lightNode;
+
+			}
+
+		}
+
+		return null;
+
+	}
+
+	fromLights( lights ) {
 
 		const lightNodes = [];
 
+		lights = sortLights( lights );
+
 		for ( const light of lights ) {
 
-			lightNodes.push( new LightNode( light ) );
+			let lightNode = this.getLightNodeByHash( light.uuid );
+
+			if ( lightNode === null ) {
+
+				lightNode = new LightNode( light );
+
+			}
+
+			lightNodes.push( lightNode );
 
 		}
 
-		return new LightsNode( lightNodes );
+		this.lightNodes = lightNodes;
+		this._hash = null;
+
+		return this;
 
 	}
 

+ 66 - 0
examples/jsm/renderers/webgpu/WebGPURenderStates.js

@@ -0,0 +1,66 @@
+import LightsNode from 'three-nodes/lights/LightsNode.js';
+
+class WebGPURenderState {
+
+	constructor() {
+
+		this.lightsNode = new LightsNode();
+
+		this.lightsArray = [];
+
+	}
+
+	init() {
+
+		this.lightsArray.length = 0;
+
+	}
+
+	pushLight( light ) {
+
+		this.lightsArray.push( light );
+
+	}
+
+	getLightNode() {
+
+		return this.lightsNode.fromLights( this.lightsArray );
+
+	}
+
+}
+
+class WebGPURenderStates {
+
+	constructor() {
+
+		this.renderStates = new WeakMap();
+
+	}
+
+	get( scene, camera ) {
+
+		const renderStates = this.renderStates;
+
+		let renderState = renderStates.get( scene );
+
+		if ( renderState === undefined ) {
+
+			renderState = new WebGPURenderState();
+			renderStates.set( scene, renderState );
+
+		}
+
+		return renderState;
+
+	}
+
+	dispose() {
+
+		this.renderStates = new WeakMap();
+
+	}
+
+}
+
+export default WebGPURenderStates;

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

@@ -8,6 +8,7 @@ import WebGPURenderPipelines from './WebGPURenderPipelines.js';
 import WebGPUComputePipelines from './WebGPUComputePipelines.js';
 import WebGPUBindings from './WebGPUBindings.js';
 import WebGPURenderLists from './WebGPURenderLists.js';
+import WebGPURenderStates from './WebGPURenderStates.js';
 import WebGPUTextures from './WebGPUTextures.js';
 import WebGPUBackground from './WebGPUBackground.js';
 import WebGPUNodes from './nodes/WebGPUNodes.js';
@@ -105,11 +106,14 @@ class WebGPURenderer {
 		this._renderPipelines = null;
 		this._computePipelines = null;
 		this._renderLists = null;
+		this._renderStates = null;
 		this._textures = null;
 		this._background = null;
 
 		this._renderPassDescriptor = null;
 
+		this._currentRenderState = null;
+
 		this._currentRenderList = null;
 		this._opaqueSort = null;
 		this._transparentSort = null;
@@ -185,6 +189,7 @@ class WebGPURenderer {
 		this._renderPipelines = new WebGPURenderPipelines( this, device, parameters.sampleCount, this._nodes );
 		this._bindings = this._renderPipelines.bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes, this._nodes );
 		this._renderLists = new WebGPURenderLists();
+		this._renderStates = new WebGPURenderStates();
 		this._background = new WebGPUBackground( this );
 
 		//
@@ -225,6 +230,9 @@ class WebGPURenderer {
 		this._currentRenderList = this._renderLists.get( scene, camera );
 		this._currentRenderList.init();
 
+		this._currentRenderState = this._renderStates.get( scene );
+		this._currentRenderState.init();
+
 		this._projectObject( scene, camera, 0 );
 
 		this._currentRenderList.finish();
@@ -303,13 +311,17 @@ class WebGPURenderer {
 
 		}
 
+		// light node
+
+		const lightNode = this._currentRenderState.getLightNode();
+
 		// process render lists
 
 		const opaqueObjects = this._currentRenderList.opaque;
 		const transparentObjects = this._currentRenderList.transparent;
 
-		if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, scene, passEncoder );
-		if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, scene, passEncoder );
+		if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, scene, lightNode, passEncoder );
+		if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, scene, lightNode, passEncoder );
 
 		// finish render pass
 
@@ -581,6 +593,7 @@ class WebGPURenderer {
 		this._bindings.dispose();
 		this._info.dispose();
 		this._renderLists.dispose();
+		this._renderStates.dispose();
 		this._textures.dispose();
 
 	}
@@ -634,6 +647,7 @@ class WebGPURenderer {
 	_projectObject( object, camera, groupOrder ) {
 
 		const currentRenderList = this._currentRenderList;
+		const currentRenderState = this._currentRenderState;
 
 		if ( object.visible === false ) return;
 
@@ -651,7 +665,7 @@ class WebGPURenderer {
 
 			} else if ( object.isLight ) {
 
-				//currentRenderState.pushLight( object );
+				currentRenderState.pushLight( object );
 
 				if ( object.castShadow ) {
 
@@ -736,7 +750,7 @@ class WebGPURenderer {
 
 	}
 
-	_renderObjects( renderList, camera, scene, passEncoder ) {
+	_renderObjects( renderList, camera, scene, lightNode, passEncoder ) {
 
 		// process renderable objects
 
@@ -758,6 +772,7 @@ class WebGPURenderer {
 
 			const objectProperties = this._properties.get( object );
 
+			objectProperties.lightNode = lightNode;
 			objectProperties.fogNode = scene.fogNode;
 
 			if ( camera.isArrayCamera ) {

+ 2 - 2
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -147,7 +147,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			let vertex = new PositionNode( PositionNode.GEOMETRY );
 
-			if ( lightNode === null && this.lightNode && this.lightNode.hasLights === true ) {
+			if ( lightNode === null && this.lightNode ) {
 
 				lightNode = this.lightNode;
 
@@ -293,7 +293,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			let outputNode = diffuseColorNode;
 
-			if ( lightNode && lightNode.isNode ) {
+			if ( lightNode && lightNode.isNode && lightNode.hasLight !== false ) {
 
 				const lightContextNode = new LightContextNode( lightNode );
 

+ 2 - 0
examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js

@@ -21,8 +21,10 @@ class WebGPUNodes {
 		if ( nodeBuilder === undefined ) {
 
 			const fogNode = objectProperties.fogNode;
+			const lightNode = objectProperties.lightNode;
 
 			nodeBuilder = new WebGPUNodeBuilder( object, this.renderer );
+			nodeBuilder.lightNode = lightNode;
 			nodeBuilder.fogNode = fogNode;
 			nodeBuilder.build();
 

+ 1 - 1
examples/webgpu_lights_custom.html

@@ -75,7 +75,7 @@
 
 				//light nodes ( selective lights )
 
-				const allLightsNode = Nodes.LightsNode.fromLights( [ light1, light2, light3 ] );
+				const allLightsNode = new Nodes.LightsNode().fromLights( [ light1, light2, light3 ] );
 
 				// points
 

+ 23 - 17
examples/webgpu_lights_selective.html

@@ -99,9 +99,8 @@
 
 				//light nodes ( selective lights )
 
-				const allLightsNode = Nodes.LightsNode.fromLights( [ light1, light2, light3, light4 ] );
-				const redLightNode = Nodes.LightsNode.fromLights( [ light1 ] );
-				const blueLightNode = Nodes.LightsNode.fromLights( [ light2 ] );
+				const redLightNode = new Nodes.LightsNode().fromLights( [ light1 ] );
+				const blueLightNode = new Nodes.LightsNode().fromLights( [ light2 ] );
 
 				//models
 
@@ -115,7 +114,6 @@
 				scene.add( leftObject );
 
 				const centerObject = new THREE.Mesh( geometryTeapot, new Nodes.MeshStandardNodeMaterial( { color: 0x555555 } ) );
-				centerObject.material.lightNode = allLightsNode;
 				centerObject.material.normalNode = new Nodes.NormalMapNode( new Nodes.TextureNode( normalMapTexture ) );
 				centerObject.material.roughness = .5;
 				scene.add( centerObject );
@@ -180,24 +178,32 @@
 
 			function render() {
 
-				const time = Date.now() * 0.0005;
+				const time = performance.now() / 1000;
+				const lightTime = time * 0.5;
 
-				light1.position.x = Math.sin( time * 0.7 ) * 30;
-				light1.position.y = Math.cos( time * 0.5 ) * 40;
-				light1.position.z = Math.cos( time * 0.3 ) * 30;
+				light1.position.x = Math.sin( lightTime * 0.7 ) * 30;
+				light1.position.y = Math.cos( lightTime * 0.5 ) * 40;
+				light1.position.z = Math.cos( lightTime * 0.3 ) * 30;
 
-				light2.position.x = Math.cos( time * 0.3 ) * 30;
-				light2.position.y = Math.sin( time * 0.5 ) * 40;
-				light2.position.z = Math.sin( time * 0.7 ) * 30;
+				light2.position.x = Math.cos( lightTime * 0.3 ) * 30;
+				light2.position.y = Math.sin( lightTime * 0.5 ) * 40;
+				light2.position.z = Math.sin( lightTime * 0.7 ) * 30;
 
-				light3.position.x = Math.sin( time * 0.7 ) * 30;
-				light3.position.y = Math.cos( time * 0.3 ) * 40;
-				light3.position.z = Math.sin( time * 0.5 ) * 30;
+				light3.position.x = Math.sin( lightTime * 0.7 ) * 30;
+				light3.position.y = Math.cos( lightTime * 0.3 ) * 40;
+				light3.position.z = Math.sin( lightTime * 0.5 ) * 30;
 
-				light4.position.x = Math.sin( time * 0.3 ) * 30;
-				light4.position.y = Math.cos( time * 0.7 ) * 40;
-				light4.position.z = Math.sin( time * 0.5 ) * 30;
+				light4.position.x = Math.sin( lightTime * 0.3 ) * 30;
+				light4.position.y = Math.cos( lightTime * 0.7 ) * 40;
+				light4.position.z = Math.sin( lightTime * 0.5 ) * 30;
+/*
+				@TODO: Used to test scene light change ( currently unavailable )
 
+				if ( time > 2.0 && light1.parent === null ) scene.add( light1 );
+				if ( time > 2.5 && light2.parent === null ) scene.add( light2 );
+				if ( time > 3.0 && light3.parent === null ) scene.add( light3 );
+				if ( time > 3.5 && light4.parent === null ) scene.add( light4 );
+*/
 				renderer.render( scene, camera );
 
 			}

+ 1 - 1
examples/webgpu_nodes_playground.html

@@ -103,7 +103,7 @@
 				backLight.position.set( - 100, 20, - 260 );
 				scene.add( backLight );
 
-				nodeLights = Nodes.LightsNode.fromLights( [ topLight, backLight ] );
+				nodeLights = new Nodes.LightsNode().fromLights( [ topLight, backLight ] );
 
 				renderer = new WebGPURenderer();
 				renderer.setPixelRatio( window.devicePixelRatio );

+ 1 - 1
examples/webgpu_skinning.html

@@ -67,7 +67,7 @@
 				camera.add( light );
 				scene.add( camera );
 
-				const lightNode = LightsNode.fromLights( [ light ] );
+				const lightNode = new LightsNode().fromLights( [ light ] );
 
 				const loader = new FBXLoader();
 				loader.load( 'models/fbx/Samba Dancing.fbx', function ( object ) {