2
0
Эх сурвалжийг харах

Merge pull request #21208 from sunag/nodematerial-light

WebGPU: NodeMaterial updates
Mr.doob 4 жил өмнө
parent
commit
5e6fa8cc99

+ 2 - 1
examples/files.json

@@ -326,7 +326,8 @@
 	"webgpu": [
 		"webgpu_sandbox",
 		"webgpu_rtt",
-		"webgpu_compute"
+		"webgpu_compute",
+		"webgpu_materials"
 	],
 	"webaudio": [
 		"webaudio_orientation",

+ 6 - 2
examples/jsm/renderers/nodes/accessors/CameraNode.js

@@ -75,9 +75,13 @@ class CameraNode extends Node {
 
 				}
 
-			} else if ( inputNode === null || inputNode.isVector3Node !== true ) {
+			} else if ( scope === CameraNode.POSITION ) {
 
-				inputNode = new Vector3Node();
+				if ( inputNode === null || inputNode.isVector3Node !== true ) {
+
+					inputNode = new Vector3Node();
+
+				}
 
 			}
 

+ 63 - 4
examples/jsm/renderers/nodes/accessors/ModelNode.js

@@ -1,20 +1,38 @@
 import Node from '../core/Node.js';
 import Matrix4Node from '../inputs/Matrix4Node.js';
+import Matrix3Node from '../inputs/Matrix3Node.js';
 import { NodeUpdateType } from '../core/constants.js';
 
 class ModelNode extends Node {
 
 	static VIEW = 'view';
+	static NORMAL = 'normal';
 
 	constructor( scope = ModelNode.VIEW ) {
 
-		super( 'mat4' );
+		super();
 
 		this.scope = scope;
 
 		this.updateType = NodeUpdateType.Object;
 
-		this._inputNode = new Matrix4Node( null );
+		this._inputNode = null;
+
+	}
+
+	getType() {
+
+		const scope = this.scope;
+
+		if ( scope === ModelNode.VIEW ) {
+
+			return 'mat4';
+
+		} else if ( scope === ModelNode.NORMAL ) {
+
+			return 'mat3';
+
+		}
 
 	}
 
@@ -22,14 +40,55 @@ class ModelNode extends Node {
 
 		const object = frame.object;
 		const inputNode = this._inputNode;
+		const scope = this.scope;
+
+		if ( scope === ModelNode.VIEW ) {
+
+			inputNode.value = object.modelViewMatrix;
+
+		} else if ( scope === ModelNode.NORMAL ) {
 
-		inputNode.value = object.modelViewMatrix;
+			inputNode.value = object.normalMatrix;
+
+		}
 
 	}
 
 	generate( builder, output ) {
 
-		return this._inputNode.build( builder, output );
+		const nodeData = builder.getDataFromNode( this );
+
+		let inputNode = this._inputNode;
+
+		if ( nodeData.inputNode === undefined ) {
+
+			const scope = this.scope;
+
+			if ( scope === ModelNode.VIEW ) {
+
+				if ( inputNode === null || inputNode.isMatrix4Node !== true ) {
+
+					inputNode = new Matrix4Node( null );
+
+				}
+
+			} else if ( scope === ModelNode.NORMAL ) {
+
+				if ( inputNode === null || inputNode.isMatrix3Node !== true ) {
+
+					inputNode = new Matrix3Node( null );
+
+				}
+
+			}
+
+			this._inputNode = inputNode;
+
+			nodeData.inputNode = inputNode;
+
+		}
+
+		return inputNode.build( builder, output );
 
 	}
 

+ 84 - 0
examples/jsm/renderers/nodes/accessors/NormalNode.js

@@ -0,0 +1,84 @@
+import Node from '../core/Node.js';
+import AttributeNode from '../core/AttributeNode.js';
+import VaryNode from '../core/VaryNode.js';
+import ModelNode from '../accessors/ModelNode.js';
+import CameraNode from '../accessors/CameraNode.js';
+import OperatorNode from '../math/OperatorNode.js';
+import MathNode from '../math/MathNode.js';
+
+class NormalNode extends Node {
+
+	static LOCAL = 'local';
+	static WORLD = 'world';
+	static VIEW = 'view';
+
+	constructor( scope = NormalNode.LOCAL ) {
+
+		super( 'vec3' );
+
+		this.scope = scope;
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+		const nodeData = builder.getDataFromNode( this, builder.shaderStage );
+		const scope = this.scope;
+
+		let localNormalNode = nodeData.localNormalNode;
+
+		if ( localNormalNode === undefined ) {
+
+			localNormalNode = new AttributeNode( 'normal', 'vec3' );
+
+			nodeData.localNormalNode = localNormalNode;
+
+		}
+
+		let outputNode = localNormalNode;
+
+		if ( scope === NormalNode.VIEW ) {
+
+			let viewNormalNode = nodeData.viewNormalNode;
+
+			if ( viewNormalNode === undefined ) {
+
+				const unnormalizedWNNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL ), localNormalNode );
+				const vertexNormalNode = new MathNode( MathNode.NORMALIZE, unnormalizedWNNode );
+
+				viewNormalNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) );
+
+				nodeData.viewNormalNode = viewNormalNode;
+
+			}
+
+			outputNode = viewNormalNode;
+
+		} else if ( scope === NormalNode.WORLD ) {
+
+			let worldNormalNode = nodeData.worldNormalNode;
+
+			if ( worldNormalNode === undefined ) {
+
+				const vertexNormalNode = new MathNode( MathNode.INVERSE_TRANSFORM_DIRETION, new NormalNode( NormalNode.VIEW ), new CameraNode( CameraNode.VIEW ) );
+
+				worldNormalNode = new VaryNode( vertexNormalNode );
+
+				nodeData.worldNormalNode = worldNormalNode;
+
+			}
+
+			outputNode = worldNormalNode;
+
+		}
+
+		const normalSnipped = outputNode.build( builder, type );
+
+		return builder.format( normalSnipped, type, output );
+
+	}
+
+}
+
+export default NormalNode;

+ 1 - 1
examples/jsm/renderers/nodes/accessors/PositionNode.js

@@ -3,7 +3,7 @@ import AttributeNode from '../core/AttributeNode.js';
 
 class PositionNode extends Node {
 
-	static LOCAL = 'position';
+	static LOCAL = 'local';
 
 	constructor( scope = PositionNode.POSITION ) {
 

+ 2 - 1
examples/jsm/renderers/nodes/core/AttributeNode.js

@@ -43,7 +43,8 @@ class AttributeNode extends Node {
 
 			if ( nodeVary === undefined ) {
 
-				nodeVary = builder.getVaryFromNode( this, attribute.type, attributeName );
+				nodeVary = builder.getVaryFromNode( this, attribute.type );
+				nodeVary.snippet = attributeName;
 
 				nodeData.nodeVary = nodeVary;
 

+ 15 - 1
examples/jsm/renderers/nodes/core/Node.js

@@ -36,7 +36,21 @@ class Node {
 
 	}
 
-	build( builder, output ) {
+	buildStage( builder, shaderStage, output = null ) {
+		
+		const oldShaderStage = builder.shaderStage;
+		
+		builder.shaderStage = shaderStage;
+		
+		const snippet = this.build( builder, output );
+		
+		builder.shaderStage = oldShaderStage;
+		
+		return snippet;
+		
+	}
+
+	build( builder, output = null ) {
 
 		builder.addNode( this );
 

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

@@ -198,7 +198,7 @@ class NodeBuilder {
 
 	}
 
-	getVaryFromNode( node, type, value ) {
+	getVaryFromNode( node, type ) {
 
 		const nodeData = this.getDataFromNode( node );
 
@@ -209,7 +209,7 @@ class NodeBuilder {
 			const varys = this.varys;
 			const index = varys.length;
 
-			nodeVary = new NodeVary( 'nodeV' + index, type, value );
+			nodeVary = new NodeVary( 'nodeV' + index, type );
 
 			varys.push( nodeVary );
 

+ 2 - 2
examples/jsm/renderers/nodes/core/NodeVary.js

@@ -1,10 +1,10 @@
 class NodeVary {
 
-	constructor( name, type, value ) {
+	constructor( name, type, snippet = '' ) {
 
 		this.name = name;
 		this.type = type;
-		this.value = value;
+		this.snippet = snippet;
 
 		Object.defineProperty( this, 'isNodeVary', { value: true } );
 

+ 6 - 2
examples/jsm/renderers/nodes/core/VaryNode.js

@@ -1,4 +1,5 @@
 import Node from './Node.js';
+import { NodeShaderStage } from './constants.js';
 
 class VaryNode extends Node {
 
@@ -22,9 +23,12 @@ class VaryNode extends Node {
 
 		const type = this.getType( builder );
 
-		const value = this.value.build( builder, type );
+		// force nodeVary.snippet work in vertex stage
+		const snippet = this.value.buildStage( builder, NodeShaderStage.Vertex, type );
+
+		const nodeVary = builder.getVaryFromNode( this, type );
+		nodeVary.snippet = snippet;
 
-		const nodeVary = builder.getVaryFromNode( this, type, value );
 		const propertyName = builder.getPropertyName( nodeVary );
 
 		return builder.format( propertyName, type, output );

+ 5 - 0
examples/jsm/renderers/nodes/core/constants.js

@@ -1,3 +1,8 @@
+export const NodeShaderStage = {
+	Vertex: 'vertex',
+	Fragment: 'fragment'
+};
+
 export const NodeUpdateType = {
 	None: 'none',
 	Frame: 'frame',

+ 91 - 0
examples/jsm/renderers/nodes/math/MathNode.js

@@ -0,0 +1,91 @@
+import Node from '../core/Node.js';
+import OperatorNode from './OperatorNode.js';
+
+class MathNode extends Node {
+
+	static NORMALIZE = 'normalize';
+	static INVERSE_TRANSFORM_DIRETION = 'inverseTransformDirection';
+
+	constructor( method, a, b = null ) {
+
+		super();
+
+		this.method = method;
+
+		this.a = a;
+		this.b = b;
+
+	}
+
+	getType( builder ) {
+
+		const method = this.method;
+
+		if ( method === MathNode.INVERSE_TRANSFORM_DIRETION ) {
+
+			return 'vec3';
+
+		} else {
+
+			const typeA = this.a.getType( builder );
+
+			if ( this.b !== null ) {
+
+				if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
+
+					// anytype x anytype: use the greater length vector
+
+					return typeB;
+
+				}
+
+			}
+
+			return typeA;
+
+		}
+
+	}
+
+	generate( builder, output ) {
+
+		const method = this.method;
+		const type = this.getType( builder );
+
+		let a = null, b = null;
+
+		if ( method === MathNode.INVERSE_TRANSFORM_DIRETION ) {
+
+			a = this.a.build( builder, 'vec3' );
+			b = this.b.build( builder, 'mat4' );
+
+			// add in FunctionNode later
+			return `normalize( ( vec4( ${a}, 0.0 ) * ${b} ).xyz )`;
+
+		} else {
+
+			a = this.a.build( builder, type );
+
+			if ( this.b !== null ) {
+
+				b = this.b.build( builder, type );
+
+			}
+
+		}
+
+		if ( b !== null ) {
+
+			return builder.format( `${method}( ${a}, ${b} )`, type, output );
+
+		} else {
+
+			return builder.format( `${method}( ${a} )`, type, output );
+
+		}
+
+	}
+
+}
+
+export default MathNode;

+ 4 - 3
examples/jsm/renderers/webgpu/WebGPURenderPipelines.js

@@ -100,7 +100,7 @@ class WebGPURenderPipelines {
 			const disposeCallback = onMaterialDispose.bind( this );
 			materialProperties.disposeCallback = disposeCallback;
 
-			material.addEventListener( 'dispose', onMaterialDispose.bind( this ) );
+			material.addEventListener( 'dispose', disposeCallback );
 
 			// determine shader attributes
 
@@ -792,9 +792,10 @@ function onMaterialDispose( event ) {
 	const shaderModules = this.shaderModules;
 
 	const material = event.target;
+	const materialProperties = properties.get( material );
 	const nodeBuilder = nodes.get( material );
 
-	material.removeEventListener( 'dispose', onMaterialDispose );
+	material.removeEventListener( 'dispose', materialProperties.disposeCallback );
 
 	properties.remove( material );
 	nodes.remove( material );
@@ -802,7 +803,7 @@ function onMaterialDispose( event ) {
 	shaderModules.vertex.delete( nodeBuilder.vertexShader );
 	shaderModules.fragment.delete( nodeBuilder.fragmentShader );
 
-	// @TODO: need implement pipeline
+	// @TODO: still needed remove bindings and pipeline
 
 }
 

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

@@ -244,7 +244,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			for ( const vary of this.varys ) {
 
-				snippet += `${vary.name} = ${vary.value};`;
+				snippet += `${vary.name} = ${vary.snippet};`;
 
 			}
 

+ 202 - 0
examples/webgpu_materials.html

@@ -0,0 +1,202 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - materials</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 materials
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
+			import WebGPU from './jsm/renderers/webgpu/WebGPU.js';
+
+			import { TeapotGeometry } from './jsm/geometries/TeapotGeometry.js';
+
+			import AttributeNode from './jsm/renderers/nodes/core/AttributeNode.js';
+			import PositionNode from './jsm/renderers/nodes/accessors/PositionNode.js';
+			import NormalNode from './jsm/renderers/nodes/accessors/NormalNode.js';
+			import TextureNode from './jsm/renderers/nodes/inputs/TextureNode.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+
+			let stats;
+
+			let camera, scene, renderer;
+
+			const objects = [], materials = [];
+
+			init().then( animate ).catch( error );
+
+			async function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw 'No WebGPU support';
+
+				}
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.set( 0, 200, 800 );
+
+				scene = new THREE.Scene();
+
+				// Grid
+
+				const helper = new THREE.GridHelper( 1000, 40, 0x303030, 0x303030 );
+				helper.material.colorNode = new AttributeNode( 'color', 'vec3' );
+				helper.position.y = - 75;
+				scene.add( helper );
+
+				// Materials
+
+				const textureLoader = new THREE.TextureLoader();
+
+				const texture = textureLoader.load( './textures/uv_grid_opengl.jpg' );
+				texture.wrapS = THREE.RepeatWrapping;
+				texture.wrapT = THREE.RepeatWrapping;
+
+				let material;
+
+				// PositionNode.LOCAL
+				material = new THREE.MeshBasicMaterial();
+				material.colorNode = new PositionNode( PositionNode.LOCAL );
+				materials.push( material );
+
+				// NormalNode.LOCAL
+				material = new THREE.MeshBasicMaterial();
+				material.colorNode = new NormalNode( NormalNode.LOCAL );
+				materials.push( material );
+
+				// NormalNode.WORLD
+				material = new THREE.MeshBasicMaterial();
+				material.colorNode = new NormalNode( NormalNode.WORLD );
+				materials.push( material );
+
+				// NormalNode.VIEW
+				material = new THREE.MeshBasicMaterial();
+				material.colorNode = new NormalNode( NormalNode.VIEW );
+				materials.push( material );
+
+				// TextureNode
+				material = new THREE.MeshBasicMaterial();
+				material.colorNode = new TextureNode( texture );
+				materials.push( material );
+
+				// Opacity
+				material = new THREE.MeshBasicMaterial();
+				material.opacityNode = new TextureNode( texture );
+				material.transparent = true;
+				materials.push( material );
+
+				// Geometry
+
+				const geometry = new TeapotGeometry( 50, 18 );
+
+				for ( let i = 0, l = materials.length; i < l; i ++ ) {
+
+					addMesh( geometry, materials[ i ] );
+
+				}
+
+				//
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				//
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				return renderer.init();
+
+			}
+
+			function addMesh( geometry, material ) {
+
+				const mesh = new THREE.Mesh( geometry, material );
+
+				mesh.position.x = ( objects.length % 4 ) * 200 - 400;
+				mesh.position.z = Math.floor( objects.length / 4 ) * 200 - 200;
+
+				mesh.rotation.x = Math.random() * 200 - 100;
+				mesh.rotation.y = Math.random() * 200 - 100;
+				mesh.rotation.z = Math.random() * 200 - 100;
+
+				objects.push( mesh );
+
+				scene.add( mesh );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				const timer = 0.0001 * Date.now();
+
+				camera.position.x = Math.cos( timer ) * 1000;
+				camera.position.z = Math.sin( timer ) * 1000;
+
+				camera.lookAt( scene.position );
+
+				for ( let i = 0, l = objects.length; i < l; i ++ ) {
+
+					const object = objects[ i ];
+
+					object.rotation.x += 0.01;
+					object.rotation.y += 0.005;
+
+				}
+
+				renderer.render( scene, camera );
+
+			}
+
+			function error( error ) {
+
+				console.error( error );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 8 - 3
examples/webgpu_sandbox.html

@@ -22,10 +22,12 @@
 			import AttributeNode from './jsm/renderers/nodes/core/AttributeNode.js';
 			import FloatNode from './jsm/renderers/nodes/inputs/FloatNode.js';
 			import Vector2Node from './jsm/renderers/nodes/inputs/Vector2Node.js';
+			import Vector3Node from './jsm/renderers/nodes/inputs/Vector3Node.js';
 			import ColorNode from './jsm/renderers/nodes/inputs/ColorNode.js';
 			import TextureNode from './jsm/renderers/nodes/inputs/TextureNode.js';
 			import UVNode from './jsm/renderers/nodes/accessors/UVNode.js';
 			import PositionNode from './jsm/renderers/nodes/accessors/PositionNode.js';
+			import NormalNode from './jsm/renderers/nodes/accessors/NormalNode.js';
 			import OperatorNode from './jsm/renderers/nodes/math/OperatorNode.js';
 			import SwitchNode from './jsm/renderers/nodes/utils/SwitchNode.js';
 			import TimerNode from './jsm/renderers/nodes/utils/TimerNode.js';
@@ -95,10 +97,13 @@
 				const geometrySphere = new THREE.SphereGeometry( .5, 64, 64 );
 				const materialSphere = new THREE.MeshBasicMaterial();
 
-				const displaceScaled = new OperatorNode( '*', new TextureNode( textureDisplace ), new FloatNode( .2 ) );
+				const displaceAnimated = new SwitchNode( new TextureNode( textureDisplace ), 'x' );
+				const displaceY = new OperatorNode( '*', displaceAnimated, new FloatNode( .25 ).setConst( true ) );
 
-				materialSphere.colorNode = new TextureNode( textureDisplace );
-				materialSphere.positionNode = new OperatorNode( '+', new PositionNode(), displaceScaled );
+				const displace = new OperatorNode( '*', new NormalNode( NormalNode.LOCAL ), displaceY );
+
+				materialSphere.colorNode = displaceY;
+				materialSphere.positionNode = new OperatorNode( '+', new PositionNode( PositionNode.LOCAL ), displace );
 
 				const sphere = new THREE.Mesh( geometrySphere, materialSphere );
 				sphere.position.set( - 2, - 1, 0 );