Browse Source

Merge pull request #20421 from sunag/nodematerial-webgpu

NodeMaterial: WebGPU
Mr.doob 4 years ago
parent
commit
3f5605c452
33 changed files with 1589 additions and 196 deletions
  1. 38 0
      examples/jsm/renderers/nodes/accessors/UVNode.js
  2. 54 0
      examples/jsm/renderers/nodes/core/AttributeNode.js
  3. 56 0
      examples/jsm/renderers/nodes/core/InputNode.js
  4. 41 0
      examples/jsm/renderers/nodes/core/Node.js
  5. 320 0
      examples/jsm/renderers/nodes/core/NodeBuilder.js
  6. 47 0
      examples/jsm/renderers/nodes/core/NodeFrame.js
  7. 13 0
      examples/jsm/renderers/nodes/core/NodeSlot.js
  8. 26 0
      examples/jsm/renderers/nodes/core/NodeUniform.js
  9. 8 0
      examples/jsm/renderers/nodes/core/constants.js
  10. 15 0
      examples/jsm/renderers/nodes/inputs/ColorNode.js
  11. 15 0
      examples/jsm/renderers/nodes/inputs/FloatNode.js
  12. 30 0
      examples/jsm/renderers/nodes/inputs/TextureNode.js
  13. 15 0
      examples/jsm/renderers/nodes/inputs/Vector2Node.js
  14. 15 0
      examples/jsm/renderers/nodes/inputs/Vector3Node.js
  15. 15 0
      examples/jsm/renderers/nodes/inputs/Vector4Node.js
  16. 46 0
      examples/jsm/renderers/nodes/math/OperatorNode.js
  17. 33 0
      examples/jsm/renderers/nodes/utils/SwitchNode.js
  18. 21 0
      examples/jsm/renderers/nodes/utils/TimerNode.js
  19. 20 127
      examples/jsm/renderers/webgpu/WebGPUBindings.js
  20. 49 35
      examples/jsm/renderers/webgpu/WebGPURenderPipelines.js
  21. 15 2
      examples/jsm/renderers/webgpu/WebGPURenderer.js
  22. 3 1
      examples/jsm/renderers/webgpu/WebGPUSampledTexture.js
  23. 3 1
      examples/jsm/renderers/webgpu/WebGPUSampler.js
  24. 12 0
      examples/jsm/renderers/webgpu/WebGPUUniform.js
  25. 7 7
      examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js
  26. 64 13
      examples/jsm/renderers/webgpu/nodes/ShaderLib.js
  27. 294 0
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  28. 135 0
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniform.js
  29. 29 0
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js
  30. 68 0
      examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js
  31. 9 0
      examples/webgpu_compute.html
  32. 23 4
      examples/webgpu_rtt.html
  33. 50 6
      examples/webgpu_sandbox.html

+ 38 - 0
examples/jsm/renderers/nodes/accessors/UVNode.js

@@ -0,0 +1,38 @@
+import AttributeNode from '../core/AttributeNode.js';
+
+class UVNode extends AttributeNode {
+
+	constructor( index = 0 ) {
+
+		super( 'vec2' );
+
+		this.index = index;
+
+	}
+
+	getIndexProperty( prefix ) {
+
+		return prefix + ( this.index > 0 ? this.index + 1 : '' );
+
+	}
+
+	getAttributeName( /*builder*/ ) {
+
+		return this.getIndexProperty( 'uv' );
+
+	}
+
+	getAttributeProperty( builder ) {
+
+		// customize 'uv' property
+		const property = this.getIndexProperty( 'vUv' );
+
+		this.setAttributeProperty( property );
+
+		return super.getAttributeProperty( builder );
+
+	}
+
+}
+
+export default UVNode;

+ 54 - 0
examples/jsm/renderers/nodes/core/AttributeNode.js

@@ -0,0 +1,54 @@
+import Node from './Node.js';
+
+class AttributeNode extends Node {
+
+	constructor( type, name = null, property = null ) {
+
+		super( type );
+
+		this.name = name;
+		this.property = property;
+
+	}
+
+	setAttributeName( name ) {
+
+		this.name = name;
+
+		return this;
+
+	}
+
+	getAttributeName( /*builder*/ ) {
+
+		return this.name;
+
+	}
+
+	setAttributeProperty( name ) {
+
+		this.property = name;
+
+		return this;
+
+	}
+
+	getAttributeProperty( builder ) {
+
+		const attribute = builder.getAttribute( this.getType( builder ), this.getAttributeName( builder ), this.property );
+
+		return attribute.property;
+
+	}
+
+	generate( builder, output ) {
+
+		const attributeProperty = this.getAttributeProperty( builder );
+
+		return builder.format( attributeProperty, this.getType( builder ), output );
+
+	}
+
+}
+
+export default AttributeNode;

+ 56 - 0
examples/jsm/renderers/nodes/core/InputNode.js

@@ -0,0 +1,56 @@
+import Node from './Node.js';
+
+class InputNode extends Node {
+
+	constructor( type ) {
+
+		super( type );
+
+		this.constant = false;
+
+		Object.defineProperty( this, 'isInputNode', { value: true } );
+
+	}
+
+	setConst( value ) {
+
+		this.constant = value;
+
+		return this;
+
+	}
+
+	getConst() {
+
+		return this.constant;
+
+	}
+
+	generateConst( builder ) {
+
+		return builder.getConst( this.getType( builder ), this.value );
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		if ( this.constant === true ) {
+
+			return builder.format( this.generateConst( builder ), type, output );
+
+		} else {
+
+			const nodeUniform = builder.getUniformFromNode( this, builder.shaderStage, this.getType( builder ) );
+			const propertyName = builder.getPropertyName( nodeUniform );
+
+			return builder.format( propertyName, type, output );
+
+		}
+
+	}
+
+}
+
+export default InputNode;

+ 41 - 0
examples/jsm/renderers/nodes/core/Node.js

@@ -0,0 +1,41 @@
+class Node {
+
+	constructor( type = null ) {
+
+		this.type = type;
+
+		this.needsUpdate = false;
+
+		Object.defineProperty( this, 'isNode', { value: true } );
+
+	}
+
+	getType( /*builder*/ ) {
+
+		return this.type;
+
+	}
+
+	update( /*frame*/ ) {
+
+		console.warn( "Abstract function." );
+
+	}
+
+	generate( /*builder, output*/ ) {
+
+		console.warn( "Abstract function." );
+
+	}
+
+	build( builder, output ) {
+
+		builder.addNode( this );
+
+		return this.generate( builder, output );
+
+	}
+
+}
+
+export default Node;

+ 320 - 0
examples/jsm/renderers/nodes/core/NodeBuilder.js

@@ -0,0 +1,320 @@
+import NodeUniform from './NodeUniform.js';
+
+class NodeBuilder {
+
+	constructor( material, renderer ) {
+
+		this.material = material;
+		this.renderer = renderer;
+
+		this.nodes = [];
+		this.updateNodes = [];
+
+		this.vertexShader = null;
+		this.fragmentShader = null;
+
+		this.slots = { vertex: [], fragment: [] };
+		this.defines = { vertex: {}, fragment: {} };
+		this.uniforms = { vertex: [], fragment: [] };
+		this.attributes = {};
+		this.attributeCount = 0;
+
+		this.nodesData = new WeakMap();
+
+		this.shaderStage = null;
+
+	}
+
+	addNode( node ) {
+
+		if ( this.nodes.indexOf( node ) === - 1 ) {
+
+			if ( node.needsUpdate === true ) {
+
+				this.updateNodes.push( node );
+
+			}
+
+			this.nodes.push( node );
+
+		}
+
+	}
+
+	addSlot( shaderStage, slot ) {
+
+		this.slots[ shaderStage ].push( slot );
+
+	}
+
+	define( shaderStage, name, value = '' ) {
+
+		this.defines[ shaderStage ][ name ] = value;
+
+	}
+
+	getTexture( textureProperty, uvSnippet ) {
+
+
+
+	}
+
+	getConst( type, value ) {
+
+		if ( type === 'float' ) return value + ( value % 1 ? '' : '.0' );
+		if ( type === 'vec2' ) return `vec2( ${value.x}, ${value.y} )`;
+		if ( type === 'vec3' ) return `vec3( ${value.x}, ${value.y}, ${value.z} )`;
+		if ( type === 'vec4' ) return `vec4( ${value.x}, ${value.y}, ${value.z}, ${value.w} )`;
+		if ( type === 'color' ) return `vec3( ${value.r}, ${value.g}, ${value.b} )`;
+
+		throw new Error(`Type '${type}' not found in generate constant attempt.`);
+
+	}
+
+	getAttribute( type, name, property = null ) {
+
+		let attribute = this.attributes[ name ];
+
+		if ( attribute === undefined ) {
+
+			const index = this.attributeCount ++;
+
+			if ( property === null ) {
+
+				property = `node_A${index}`;
+
+			}
+
+			attribute = {
+				type,
+				name,
+				index,
+				property
+			};
+
+			this.attributes[ name ] = attribute;
+
+		}
+
+		return attribute;
+
+	}
+
+	getPropertyName( nodeUniform ) {
+
+		return nodeUniform.name;
+
+	}
+
+	getVectorType( type ) {
+
+		if ( type === 'color' ) return 'vec3';
+		else if ( type === 'texture' ) return 'vec4';
+
+		return type;
+
+	}
+
+	getTypeFromLength( type ) {
+
+		if ( type === 1 ) return 'float';
+		if ( type === 2 ) return 'vec2';
+		if ( type === 3 ) return 'vec3';
+		if ( type === 4 ) return 'vec4';
+
+		return 0;
+
+	}
+
+	getTypeLength( type ) {
+
+		type = this.getVectorType( type );
+
+		if ( type === 'float' ) return 1;
+		if ( type === 'vec2' ) return 2;
+		if ( type === 'vec3' ) return 3;
+		if ( type === 'vec4' ) return 4;
+
+		return 0;
+
+	}
+
+	getDataFromNode( node, shaderStage = null ) {
+
+		let nodeData = this.nodesData.get( node );
+
+		if ( nodeData === undefined ) {
+
+			nodeData = { vertex: {}, fragment: {} };
+
+			this.nodesData.set( node, nodeData );
+
+		}
+
+		return shaderStage ? nodeData[ shaderStage ] : nodeData;
+
+	}
+
+	getUniformFromNode( node, shaderStage, type ) {
+
+		const nodeData = this.getDataFromNode( node, shaderStage );
+
+		let nodeUniform = nodeData.uniform;
+
+		if ( nodeUniform === undefined ) {
+
+			const uniforms = this.uniforms[ shaderStage ];
+			const index = uniforms.length;
+
+			nodeUniform = new NodeUniform( 'nodeU' + index, type, node );
+
+			uniforms.push( nodeUniform );
+
+			nodeData.uniform = nodeUniform;
+
+		}
+
+		return nodeUniform;
+
+	}
+
+	/*
+	analyzeNode( node ) {
+
+
+	}
+	*/
+
+	flowNode( node, output ) {
+
+		let flowData = {};
+		flowData.result = node.build( this, output );
+
+		return flowData;
+
+	}
+
+	_buildDefines( shader ) {
+
+		const defines = this.defines[ shader ];
+
+		let code = '';
+
+		for ( let name in defines ) {
+
+			code += `#define ${name} ${defines[ name ]}\n`;
+
+		}
+
+		return code;
+
+	}
+
+	getAttributesBodySnippet( /*shaderStage*/ ) {
+
+
+
+	}
+
+	getAttributesHeaderSnippet( /*shaderStage*/ ) {
+
+
+
+	}
+
+	getUniformsHeaderSnippet( shaderStage ) {
+
+		const uniforms = this.uniforms[ shaderStage ];
+
+		let snippet = '';
+
+		for ( let uniform of uniforms ) {
+
+			snippet += `${uniform.type} ${uniform.name}; `;
+
+		}
+
+		return snippet;
+
+	}
+
+	format( snippet, fromType, toType ) {
+
+		fromType = this.getVectorType( fromType );
+		toType = this.getVectorType( toType );
+
+		const typeToType = `${fromType} to ${toType}`;
+
+		switch ( typeToType ) {
+
+			case 'float to vec2' : return `vec2( ${snippet} )`;
+			case 'float to vec3' : return `vec3( ${snippet} )`;
+			case 'float to vec4' : return `vec4( vec3( ${snippet} ), 1.0 )`;
+
+			case 'vec2 to float' : return `${snippet}.x`;
+			case 'vec2 to vec3' : return `vec3( ${snippet}.x, ${snippet}.y, 0.0 )`;
+			case 'vec2 to vec4' : return `vec4( ${snippet}.x, ${snippet}.y, 0.0, 1.0 )`;
+
+			case 'vec3 to float' : return `${snippet}.x`;
+			case 'vec3 to vec2' : return `${snippet}.xy`;
+			case 'vec3 to vec4' : return `vec4( ${snippet}.x, ${snippet}.y, ${snippet}.z, 1.0 )`;
+
+			case 'vec4 to float' : return `${snippet}.x`;
+			case 'vec4 to vec2' : return `${snippet}.xy`;
+			case 'vec4 to vec3' : return `${snippet}.xyz`;
+
+		}
+
+		return snippet;
+
+	}
+
+	getHash() {
+
+		return this.vertexShader + this.fragmentShader;
+
+	}
+
+	build() {
+
+		const shaderStages = [ 'vertex', 'fragment' ];
+		const shaderData = {};
+
+		for ( let shaderStage of shaderStages ) {
+
+			this.shaderStage = shaderStage;
+
+			let slots = this.slots[ shaderStage ];
+
+			for ( let slot of slots ) {
+
+				let flowData = this.flowNode( slot.node, slot.output );
+
+				this.define( shaderStage, `NODE_${slot.name}`, flowData.result );
+
+			}
+
+		}
+
+		this.shaderStage = null;
+
+		for ( let shaderStage of shaderStages ) {
+
+			this.define( shaderStage, 'NODE_HEADER_UNIFORMS', this.getUniformsHeaderSnippet( shaderStage ) );
+			this.define( shaderStage, 'NODE_HEADER_ATTRIBUTES', this.getAttributesHeaderSnippet( shaderStage ) );
+			this.define( shaderStage, 'NODE_BODY_ATTRIBUTES', this.getAttributesBodySnippet( shaderStage ) );
+
+			shaderData[ shaderStage ] = this._buildDefines( shaderStage );
+
+		}
+
+		this.vertexShader = shaderData.vertex;
+		this.fragmentShader = shaderData.fragment;
+
+		return this;
+
+	}
+
+}
+
+export default NodeBuilder;

+ 47 - 0
examples/jsm/renderers/nodes/core/NodeFrame.js

@@ -0,0 +1,47 @@
+class NodeFrame {
+
+	constructor() {
+
+		this.time = 0;
+		this.deltaTime = 0;
+
+		this.frameId = 0;
+
+		this.startTime = null;
+
+		this.updateMap = new WeakMap();
+
+		this.renderer = null;
+		this.material = null;
+
+	}
+
+	updateNode( node ) {
+
+		if ( this.updateMap.get( node ) !== this.frameId ) {
+
+			this.updateMap.set( node, this.frameId );
+
+			node.update( this );
+
+		}
+
+	}
+
+	update() {
+
+		this.frameId ++;
+
+		if ( this.lastTime === undefined ) this.lastTime = performance.now();
+
+		this.deltaTime = ( performance.now() - this.lastTime ) / 1000;
+
+		this.lastTime = performance.now();
+
+		this.time += this.deltaTime;
+
+	}
+
+}
+
+export default NodeFrame;

+ 13 - 0
examples/jsm/renderers/nodes/core/NodeSlot.js

@@ -0,0 +1,13 @@
+class NodeSlot {
+
+	constructor( node, name, output ) {
+
+		this.node = node;
+		this.name = name;
+		this.output = output;
+
+	}
+
+}
+
+export default NodeSlot;

+ 26 - 0
examples/jsm/renderers/nodes/core/NodeUniform.js

@@ -0,0 +1,26 @@
+class NodeUniform {
+
+	constructor( name, type, node, needsUpdate = undefined ) {
+
+		this.name = name;
+		this.type = type;
+		this.node = node;
+		this.needsUpdate = needsUpdate;
+
+	}
+
+	get value() {
+
+		return this.node.value;
+
+	}
+
+	set value( val ) {
+
+		this.node.value = val;
+
+	}
+
+}
+
+export default NodeUniform;

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

@@ -0,0 +1,8 @@
+export const NodeType = {
+	Float: 'float',
+	Vector2: 'vec2',
+	Vector3: 'vec3',
+	Vector4: 'vec4',
+	Matrix3: 'matrix3',
+	Matrix4: 'matrix4'
+};

+ 15 - 0
examples/jsm/renderers/nodes/inputs/ColorNode.js

@@ -0,0 +1,15 @@
+import InputNode from '../core/InputNode.js';
+
+class ColorNode extends InputNode {
+
+	constructor( value ) {
+
+		super( 'color' );
+
+		this.value = value;
+
+	}
+
+}
+
+export default ColorNode;

+ 15 - 0
examples/jsm/renderers/nodes/inputs/FloatNode.js

@@ -0,0 +1,15 @@
+import InputNode from '../core/InputNode.js';
+
+class FloatNode extends InputNode {
+
+	constructor( value = 0 ) {
+
+		super( 'float' );
+
+		this.value = value;
+
+	}
+
+}
+
+export default FloatNode;

+ 30 - 0
examples/jsm/renderers/nodes/inputs/TextureNode.js

@@ -0,0 +1,30 @@
+import InputNode from '../core/InputNode.js';
+import UVNode from '../accessors/UVNode.js';
+
+class TextureNode extends InputNode {
+
+	constructor( value, uv = new UVNode() ) {
+
+		super( 'texture' );
+
+		this.value = value;
+		this.uv = uv;
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		const textureProperty = super.generate( builder, type );
+		const uvSnippet = this.uv.build( builder, 'vec2' );
+
+		const textureCallSnippet = builder.getTexture( textureProperty, uvSnippet );
+
+		return builder.format( textureCallSnippet, type, output );
+
+	}
+
+}
+
+export default TextureNode;

+ 15 - 0
examples/jsm/renderers/nodes/inputs/Vector2Node.js

@@ -0,0 +1,15 @@
+import InputNode from '../core/InputNode.js';
+
+class Vector2Node extends InputNode {
+
+	constructor( value ) {
+
+		super( 'vec2' );
+
+		this.value = value;
+
+	}
+
+}
+
+export default Vector2Node;

+ 15 - 0
examples/jsm/renderers/nodes/inputs/Vector3Node.js

@@ -0,0 +1,15 @@
+import InputNode from '../core/InputNode.js';
+
+class Vector3Node extends InputNode {
+
+	constructor( value ) {
+
+		super( 'vec3' );
+
+		this.value = value;
+
+	}
+
+}
+
+export default Vector3Node;

+ 15 - 0
examples/jsm/renderers/nodes/inputs/Vector4Node.js

@@ -0,0 +1,15 @@
+import InputNode from '../core/InputNode.js';
+
+class Vector4Node extends InputNode {
+
+	constructor( value ) {
+
+		super( 'vec4' );
+
+		this.value = value;
+
+	}
+
+}
+
+export default Vector4Node;

+ 46 - 0
examples/jsm/renderers/nodes/math/OperatorNode.js

@@ -0,0 +1,46 @@
+import Node from '../core/Node.js';
+
+class OperatorNode extends Node {
+
+	constructor( op, a, b ) {
+
+		super();
+
+		this.op = op;
+
+		this.a = a;
+		this.b = b;
+
+	}
+
+	getType( builder ) {
+
+		const typeA = this.a.getType( builder );
+		const typeB = this.b.getType( builder );
+
+		// use the greater length vector
+
+		if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
+
+			return typeB;
+
+		}
+
+		return typeA;
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		const a = this.a.build( builder, type );
+		const b = this.b.build( builder, type );
+
+		return builder.format( `( ${a} ${this.op} ${b} )`, type, output );
+
+	}
+
+}
+
+export default OperatorNode;

+ 33 - 0
examples/jsm/renderers/nodes/utils/SwitchNode.js

@@ -0,0 +1,33 @@
+import Node from '../core/Node.js';
+
+class SwitchNode extends Node {
+
+	constructor( node, components = 'x' ) {
+
+		super();
+
+		this.node = node;
+		this.components = components;
+
+	}
+
+	getType( builder ) {
+
+		return builder.getTypeFromLength( this.components.length );
+
+	}
+
+	generate( builder, output ) {
+
+		const nodeType = this.node.getType( builder );
+		const nodeSnippet = this.node.build( builder, nodeType );
+
+		const snippet = `${nodeSnippet}.${this.components}`;
+
+		return builder.format( snippet, this.getType( builder ), output );
+
+	}
+
+}
+
+export default SwitchNode;

+ 21 - 0
examples/jsm/renderers/nodes/utils/TimerNode.js

@@ -0,0 +1,21 @@
+import FloatNode from '../inputs/FloatNode.js';
+
+class TimerNode extends FloatNode {
+
+	constructor() {
+
+		super();
+
+		this.needsUpdate = true;
+
+	}
+
+	update( frame ) {
+
+		this.value = frame.time;
+
+	}
+
+}
+
+export default TimerNode;

+ 20 - 127
examples/jsm/renderers/webgpu/WebGPUBindings.js

@@ -5,7 +5,7 @@ import { WebGPUSampledTexture } from './WebGPUSampledTexture.js';
 
 class WebGPUBindings {
 
-	constructor( device, info, properties, textures, pipelines, computePipelines, attributes ) {
+	constructor( device, info, properties, textures, pipelines, computePipelines, attributes, nodes ) {
 
 		this.device = device;
 		this.info = info;
@@ -14,6 +14,7 @@ class WebGPUBindings {
 		this.pipelines = pipelines;
 		this.computePipelines = computePipelines;
 		this.attributes = attributes;
+		this.nodes = nodes;
 
 		this.uniformsData = new WeakMap();
 
@@ -33,27 +34,12 @@ class WebGPUBindings {
 
 			const pipeline = this.pipelines.get( object );
 			const material = object.material;
-			let bindings;
 
-			// each material defines an array of bindings (ubos, textures, samplers etc.)
-
-			if ( material.isMeshBasicMaterial ) {
-
-				bindings = this._getMeshBasicBindings();
-
-			} else if ( material.isPointsMaterial ) {
-
-				bindings = this._getPointsBasicBindings();
-
-			} else if ( material.isLineBasicMaterial ) {
+			const nodeBuilder = this.nodes.get( material );
 
-				bindings = this._getLinesBasicBindings();
-
-			} else {
+			// each material defines an array of bindings (ubos, textures, samplers etc.)
 
-				console.error( 'THREE.WebGPURenderer: Unknwon shader type.' );
-
-			}
+			let bindings = this.composeBindings( object, nodeBuilder.getBindings( 'fragment' ) );
 
 			// setup (static) binding layout and (dynamic) binding group
 
@@ -151,39 +137,31 @@ class WebGPUBindings {
 			} else if ( binding.isSampler ) {
 
 				const material = object.material;
-				const texture = material[ binding.name ];
-
-				if ( texture !== null ) {
+				const texture = binding.texture;
 
-					textures.updateSampler( texture );
+				textures.updateSampler( texture );
 
-					const samplerGPU = textures.getSampler( texture );
+				const samplerGPU = textures.getSampler( texture );
 
-					if ( binding.samplerGPU !== samplerGPU ) {
+				if ( binding.samplerGPU !== samplerGPU ) {
 
-						binding.samplerGPU = samplerGPU;
-						needsBindGroupRefresh = true;
-
-					}
+					binding.samplerGPU = samplerGPU;
+					needsBindGroupRefresh = true;
 
 				}
 
 			} else if ( binding.isSampledTexture ) {
 
 				const material = object.material;
-				const texture = material[ binding.name ];
-
-				if ( texture !== null ) {
+				const texture = binding.texture;
 
-					const forceUpdate = textures.updateTexture( texture );
-					const textureGPU = textures.getTextureGPU( texture );
+				const forceUpdate = textures.updateTexture( texture );
+				const textureGPU = textures.getTextureGPU( texture );
 
-					if ( binding.textureGPU !== textureGPU || forceUpdate === true ) {
+				if ( binding.textureGPU !== textureGPU || forceUpdate === true ) {
 
-						binding.textureGPU = textureGPU;
-						needsBindGroupRefresh = true;
-
-					}
+					binding.textureGPU = textureGPU;
+					needsBindGroupRefresh = true;
 
 				}
 
@@ -284,9 +262,9 @@ class WebGPUBindings {
 
 	}
 
-	_getMeshBasicBindings() {
+	composeBindings( object, uniforms ) {
 
-		const bindings = [];
+		let bindings = [];
 
 		// UBOs
 
@@ -312,97 +290,12 @@ class WebGPUBindings {
 
 		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
 
-		// material (opacity for testing)
-
-		const opacityUniform = new FloatUniform( 'opacity', 1 );
-
-		const opacityGroup = new WebGPUUniformsGroup( 'opacityUniforms' );
-		opacityGroup.addUniform( opacityUniform );
-		opacityGroup.setVisibility( GPUShaderStage.FRAGMENT );
-		opacityGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
-
-			const material = object.material;
-			const opacity = ( material.transparent === true ) ? material.opacity : 1.0;
-
-			opacityUniform.setValue( opacity );
-
-		} );
-
-		// sampler
-
-		const diffuseSampler = new WebGPUSampler( 'map' );
-
-		// texture
-
-		const diffuseTexture = new WebGPUSampledTexture( 'map' );
-
 		// the order of WebGPUBinding objects must match the binding order in the shader
 
 		bindings.push( modelGroup );
 		bindings.push( cameraGroup );
-		bindings.push( opacityGroup );
-		bindings.push( diffuseSampler );
-		bindings.push( diffuseTexture );
-
-		return bindings;
-
-	}
-
-	_getPointsBasicBindings() {
-
-		const bindings = [];
-
-		// UBOs
 
-		const modelViewUniform = new Matrix4Uniform( 'modelMatrix' );
-		const modelViewMatrixUniform = new Matrix4Uniform( 'modelViewMatrix' );
-
-		const modelGroup = new WebGPUUniformsGroup( 'modelUniforms' );
-		modelGroup.addUniform( modelViewUniform );
-		modelGroup.addUniform( modelViewMatrixUniform );
-		modelGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
-
-			modelViewUniform.setValue( object.matrixWorld );
-			modelViewMatrixUniform.setValue( object.modelViewMatrix );
-
-		} );
-
-		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
-
-		//
-
-		bindings.push( modelGroup );
-		bindings.push( cameraGroup );
-
-		return bindings;
-
-	}
-
-	_getLinesBasicBindings() {
-
-		const bindings = [];
-
-		// UBOs
-
-		const modelViewUniform = new Matrix4Uniform( 'modelMatrix' );
-		const modelViewMatrixUniform = new Matrix4Uniform( 'modelViewMatrix' );
-
-		const modelGroup = new WebGPUUniformsGroup( 'modelUniforms' );
-		modelGroup.addUniform( modelViewUniform );
-		modelGroup.addUniform( modelViewMatrixUniform );
-		modelGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
-
-			modelViewUniform.setValue( object.matrixWorld );
-			modelViewMatrixUniform.setValue( object.modelViewMatrix );
-
-		} );
-
-		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
-
-		//
-
-		bindings.push( modelGroup );
-		bindings.push( cameraGroup );
+		bindings.push( ... uniforms );
 
 		return bindings;
 

+ 49 - 35
examples/jsm/renderers/webgpu/WebGPURenderPipelines.js

@@ -9,23 +9,23 @@ import {
 	ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstAlphaFactor, OneMinusDstAlphaFactor, DstColorFactor, OneMinusDstColorFactor, SrcAlphaSaturateFactor
 } from '../../../../build/three.module.js';
 
-import ShaderLib from './ShaderLib.js';
-
 class WebGPURenderPipelines {
 
-	constructor( renderer, properties, device, glslang, sampleCount ) {
+	constructor( renderer, properties, device, glslang, sampleCount, nodes ) {
 
 		this.renderer = renderer;
 		this.properties = properties;
 		this.device = device;
 		this.glslang = glslang;
 		this.sampleCount = sampleCount;
+		this.nodes = nodes;
 
 		this.pipelines = new WeakMap();
 		this.shaderAttributes = new WeakMap();
+		
 		this.shaderModules = {
-			vertex: new WeakMap(),
-			fragment: new WeakMap()
+			vertex: new Map(),
+			fragment: new Map()
 		};
 
 	}
@@ -51,67 +51,60 @@ class WebGPURenderPipelines {
 		if ( pipeline === undefined ) {
 
 			const device = this.device;
-			const material = object.material;
-
-			// shader source
-
-			let shader;
-
-			if ( material.isMeshBasicMaterial ) {
-
-				shader = ShaderLib.meshBasic;
-
-			} else if ( material.isPointsMaterial ) {
+			const properties = this.properties;
 
-				shader = ShaderLib.pointsBasic;
-
-			} else if ( material.isLineBasicMaterial ) {
-
-				shader = ShaderLib.lineBasic;
-
-			} else {
+			const material = object.material;
 
-				console.error( 'THREE.WebGPURenderer: Unknwon shader type.' );
+			// get shader
 
-			}
+			const nodeBuilder = this.nodes.get( material );
 
 			// shader modules
 
 			const glslang = this.glslang;
 
-			let moduleVertex = this.shaderModules.vertex.get( shader );
+			let moduleVertex = this.shaderModules.vertex.get( nodeBuilder.vertexShader );
 
 			if ( moduleVertex === undefined ) {
 
-				const byteCodeVertex = glslang.compileGLSL( shader.vertexShader, 'vertex' );
+				const byteCodeVertex = glslang.compileGLSL( nodeBuilder.vertexShader, 'vertex' );
 
 				moduleVertex = {
 					module: device.createShaderModule( { code: byteCodeVertex } ),
 					entryPoint: 'main'
 				};
 
-				this.shaderModules.vertex.set( shader, moduleVertex );
+				this.shaderModules.vertex.set( nodeBuilder.vertexShader, moduleVertex );
 
 			}
 
-			let moduleFragment = this.shaderModules.fragment.get( shader );
+			let moduleFragment = this.shaderModules.fragment.get( nodeBuilder.fragmentShader );
 
 			if ( moduleFragment === undefined ) {
 
-				const byteCodeFragment = glslang.compileGLSL( shader.fragmentShader, 'fragment' );
+				const byteCodeFragment = glslang.compileGLSL( nodeBuilder.fragmentShader, 'fragment' );
 
 				moduleFragment = {
 					module: device.createShaderModule( { code: byteCodeFragment } ),
 					entryPoint: 'main'
 				};
 
-				this.shaderModules.fragment.set( shader, moduleFragment );
+				this.shaderModules.fragment.set( nodeBuilder.fragmentShader, moduleFragment );
 
 			}
 
+			// dispose material
+
+			const materialProperties = properties.get( material );
+
+			const disposeCallback = onMaterialDispose.bind( this );
+			materialProperties.disposeCallback = disposeCallback;
+
+			material.addEventListener( 'dispose', onMaterialDispose.bind( this ) );
+
 			// determine shader attributes
 
-			const shaderAttributes = this._parseShaderAttributes( shader.vertexShader );
+			const shaderAttributes = this._parseShaderAttributes( nodeBuilder.vertexShader );
 
 			// vertex buffers
 
@@ -227,8 +220,8 @@ class WebGPURenderPipelines {
 		this.pipelines = new WeakMap();
 		this.shaderAttributes = new WeakMap();
 		this.shaderModules = {
-			vertex: new WeakMap(),
-			fragment: new WeakMap()
+			vertex: new Map(),
+			fragment: new Map()
 		};
 
 	}
@@ -760,7 +753,7 @@ class WebGPURenderPipelines {
 
 		// find "layout (location = num) in type name" in vertex shader
 
-		const regex = /^\s*layout\s*\(\s*location\s*=\s*(?<location>[0-9]+)\s*\)\s*in\s+(?<type>\w+)\s+(?<name>\w+)\s*;/gmi;
+		const regex = /\s*layout\s*\(\s*location\s*=\s*(?<location>[0-9]+)\s*\)\s*in\s+(?<type>\w+)\s+(?<name>\w+)\s*;/gmi;
 		let shaderAttribute = null;
 
 		const attributes = [];
@@ -792,4 +785,25 @@ class WebGPURenderPipelines {
 
 }
 
+function onMaterialDispose( event ) {
+
+	const properties = this.properties;
+	const nodes = this.nodes;
+	const shaderModules = this.shaderModules;
+
+	const material = event.target;
+	const nodeBuilder = nodes.get( material );
+
+	material.removeEventListener( 'dispose', onMaterialDispose );
+
+	properties.remove( material );
+	nodes.remove( material );
+
+	shaderModules.vertex.delete( nodeBuilder.vertexShader );
+	shaderModules.fragment.delete( nodeBuilder.fragmentShader );
+
+	// @TODO: need implement pipeline
+
+}
+
 export default WebGPURenderPipelines;

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

@@ -11,6 +11,8 @@ import WebGPURenderLists from './WebGPURenderLists.js';
 import WebGPUTextures from './WebGPUTextures.js';
 import WebGPUBackground from './WebGPUBackground.js';
 
+import WebGPUNodes from './nodes/WebGPUNodes.js';
+
 import { Frustum, Matrix4, Vector3, Color } from '../../../../build/three.module.js';
 
 console.info( 'THREE.WebGPURenderer: Modified Matrix4.makePerspective() and Matrix4.makeOrtographic() to work with WebGPU, see https://github.com/mrdoob/three.js/issues/20276.' );
@@ -97,6 +99,7 @@ class WebGPURenderer {
 		this._properties = null;
 		this._attributes = null;
 		this._geometries = null;
+		this._nodes = null;
 		this._bindings = null;
 		this._objects = null;
 		this._renderPipelines = null;
@@ -175,9 +178,10 @@ class WebGPURenderer {
 		this._geometries = new WebGPUGeometries( this._attributes, this._info );
 		this._textures = new WebGPUTextures( device, this._properties, this._info, compiler );
 		this._objects = new WebGPUObjects( this._geometries, this._info );
-		this._renderPipelines = new WebGPURenderPipelines( this, this._properties, device, compiler, parameters.sampleCount );
+		this._nodes = new WebGPUNodes( this );
+		this._renderPipelines = new WebGPURenderPipelines( this, this._properties, device, compiler, parameters.sampleCount, this._nodes );
 		this._computePipelines = new WebGPUComputePipelines( device, compiler );
-		this._bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes );
+		this._bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes, this._nodes );
 		this._renderLists = new WebGPURenderLists();
 		this._background = new WebGPUBackground( this );
 
@@ -201,6 +205,12 @@ class WebGPURenderer {
 
 	render( scene, camera ) {
 
+		// @TODO: move this to animation loop
+
+		this._nodes.updateFrame();
+
+		//
+
 		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
 
 		if ( camera.parent === null ) camera.updateMatrixWorld();
@@ -514,6 +524,7 @@ class WebGPURenderer {
 		this._properties.dispose();
 		this._renderPipelines.dispose();
 		this._computePipelines.dispose();
+		this._nodes.dispose();
 		this._bindings.dispose();
 		this._info.dispose();
 		this._renderLists.dispose();
@@ -720,6 +731,7 @@ class WebGPURenderer {
 
 						passEncoder.setViewport( vp.x, vp.y, vp.width, vp.height, minDepth, maxDepth );
 
+						this._nodes.update( object, camera2 );
 						this._bindings.update( object, camera2 );
 						this._renderObject( object, passEncoder );
 
@@ -729,6 +741,7 @@ class WebGPURenderer {
 
 			} else {
 
+				this._nodes.update( object, camera );
 				this._bindings.update( object, camera );
 				this._renderObject( object, passEncoder );
 

+ 3 - 1
examples/jsm/renderers/webgpu/WebGPUSampledTexture.js

@@ -3,10 +3,12 @@ import { GPUBindingType, GPUTextureViewDimension } from './constants.js';
 
 class WebGPUSampledTexture extends WebGPUBinding {
 
-	constructor( name ) {
+	constructor( name, texture ) {
 
 		super( name );
 
+		this.texture = texture;
+
 		this.dimension = GPUTextureViewDimension.TwoD;
 
 		this.type = GPUBindingType.SampledTexture;

+ 3 - 1
examples/jsm/renderers/webgpu/WebGPUSampler.js

@@ -3,10 +3,12 @@ import { GPUBindingType } from './constants.js';
 
 class WebGPUSampler extends WebGPUBinding {
 
-	constructor( name ) {
+	constructor( name, texture ) {
 
 		super( name );
 
+		this.texture = texture;
+
 		this.type = GPUBindingType.Sampler;
 		this.visibility = GPUShaderStage.FRAGMENT;
 

+ 12 - 0
examples/jsm/renderers/webgpu/WebGPUUniform.js

@@ -19,6 +19,12 @@ class WebGPUUniform {
 		this.value = value;
 
 	}
+	
+	getValue() {
+		
+		return this.value;
+		
+	}
 
 }
 
@@ -34,6 +40,12 @@ class FloatUniform extends WebGPUUniform {
 		Object.defineProperty( this, 'isFloatUniform', { value: true } );
 
 	}
+	
+	getValue() {
+		
+		return this.nodeUniform.value;
+		
+	}
 
 }
 

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

@@ -127,7 +127,7 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		let updated = false;
 
 		const a = this.array;
-		const v = uniform.value;
+		const v = uniform.getValue();
 		const offset = uniform.offset;
 
 		if ( a[ offset ] !== v ) {
@@ -146,7 +146,7 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		let updated = false;
 
 		const a = this.array;
-		const v = uniform.value;
+		const v = uniform.getValue();
 		const offset = uniform.offset;
 
 		if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y ) {
@@ -167,7 +167,7 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		let updated = false;
 
 		const a = this.array;
-		const v = uniform.value;
+		const v = uniform.getValue();
 		const offset = uniform.offset;
 
 		if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z ) {
@@ -189,7 +189,7 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		let updated = false;
 
 		const a = this.array;
-		const v = uniform.value;
+		const v = uniform.getValue();
 		const offset = uniform.offset;
 
 		if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z || a[ offset + 4 ] !== v.w ) {
@@ -212,7 +212,7 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		let updated = false;
 
 		const a = this.array;
-		const c = uniform.value;
+		const c = uniform.getValue();
 		const offset = uniform.offset;
 
 		if ( a[ offset + 0 ] !== c.r || a[ offset + 1 ] !== c.g || a[ offset + 2 ] !== c.b ) {
@@ -234,7 +234,7 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		let updated = false;
 
 		const a = this.array;
-		const e = uniform.value.elements;
+		const e = uniform.getValue().elements;
 		const offset = uniform.offset;
 
 		if ( a[ offset + 0 ] !== e[ 0 ] || a[ offset + 1 ] !== e[ 1 ] || a[ offset + 2 ] !== e[ 2 ] ||
@@ -264,7 +264,7 @@ class WebGPUUniformsGroup extends WebGPUBinding {
 		let updated = false;
 
 		const a = this.array;
-		const e = uniform.value.elements;
+		const e = uniform.getValue().elements;
 		const offset = uniform.offset;
 
 		if ( arraysEqual( a, e, offset ) === false ) {

+ 64 - 13
examples/jsm/renderers/webgpu/ShaderLib.js → examples/jsm/renderers/webgpu/nodes/ShaderLib.js

@@ -3,9 +3,9 @@ const ShaderLib = {
 		vertexShader: `#version 450
 
 		layout(location = 0) in vec3 position;
-		layout(location = 1) in vec2 uv;
 
-		layout(location = 0) out vec2 vUv;
+		NODE_HEADER_ATTRIBUTES
+		NODE_HEADER_UNIFORMS
 
 		layout(set = 0, binding = 0) uniform ModelUniforms {
 			mat4 modelMatrix;
@@ -19,23 +19,32 @@ const ShaderLib = {
 		} cameraUniforms;
 
 		void main(){
-			vUv = uv;
+			NODE_BODY_ATTRIBUTES
 			gl_Position = cameraUniforms.projectionMatrix * modelUniforms.modelViewMatrix * vec4( position, 1.0 );
 		}`,
 		fragmentShader: `#version 450
-		layout(set = 0, binding = 2) uniform OpacityUniforms {
-			float opacity;
-		} opacityUniforms;
 
-		layout(set = 0, binding = 3) uniform sampler mySampler;
-		layout(set = 0, binding = 4) uniform texture2D myTexture;
+		NODE_HEADER_ATTRIBUTES
+		NODE_HEADER_UNIFORMS
 
-		layout(location = 0) in vec2 vUv;
 		layout(location = 0) out vec4 outColor;
 
 		void main() {
-			outColor = texture( sampler2D( myTexture, mySampler ), vUv );
-			outColor.a *= opacityUniforms.opacity;
+
+			outColor = vec4( 1.0, 1.0, 1.0, 1.0 );
+
+			#ifdef NODE_COLOR
+
+				outColor = NODE_COLOR;
+
+			#endif
+
+			#ifdef NODE_OPACITY
+
+				outColor.a *= NODE_OPACITY;
+
+			#endif
+
 		}`
 	},
 	pointsBasic: {
@@ -43,6 +52,9 @@ const ShaderLib = {
 
 		layout(location = 0) in vec3 position;
 
+		NODE_HEADER_ATTRIBUTES
+		NODE_HEADER_UNIFORMS
+
 		layout(set = 0, binding = 0) uniform ModelUniforms {
 			mat4 modelMatrix;
 			mat4 modelViewMatrix;
@@ -54,14 +66,32 @@ const ShaderLib = {
 		} cameraUniforms;
 
 		void main(){
+			NODE_BODY_ATTRIBUTES
 			gl_Position = cameraUniforms.projectionMatrix * modelUniforms.modelViewMatrix * vec4( position, 1.0 );
 		}`,
 		fragmentShader: `#version 450
 
+		NODE_HEADER_ATTRIBUTES
+		NODE_HEADER_UNIFORMS
+
 		layout(location = 0) out vec4 outColor;
 
 		void main() {
-			outColor = vec4( 1.0, 0.0, 0.0, 1.0 );
+
+			outColor = vec4( 1.0, 1.0, 1.0, 1.0 );
+
+			#ifdef NODE_COLOR
+
+				outColor = NODE_COLOR;
+
+			#endif
+
+			#ifdef NODE_OPACITY
+
+				outColor.a = NODE_OPACITY;
+
+			#endif
+
 		}`
 	},
 	lineBasic: {
@@ -69,6 +99,9 @@ const ShaderLib = {
 
 		layout(location = 0) in vec3 position;
 
+		NODE_HEADER_ATTRIBUTES
+		NODE_HEADER_UNIFORMS
+
 		layout(set = 0, binding = 0) uniform ModelUniforms {
 			mat4 modelMatrix;
 			mat4 modelViewMatrix;
@@ -80,14 +113,32 @@ const ShaderLib = {
 		} cameraUniforms;
 
 		void main(){
+			NODE_BODY_ATTRIBUTES
 			gl_Position = cameraUniforms.projectionMatrix * modelUniforms.modelViewMatrix * vec4( position, 1.0 );
 		}`,
 		fragmentShader: `#version 450
 
+		NODE_HEADER_ATTRIBUTES
+		NODE_HEADER_UNIFORMS
+
 		layout(location = 0) out vec4 outColor;
 
 		void main() {
-			outColor = vec4( 1.0, 0.0, 0.0, 1.0 );
+
+			outColor = vec4( 1.0, 1.0, 1.0, 1.0 );
+
+			#ifdef NODE_COLOR
+
+				outColor = NODE_COLOR;
+
+			#endif
+
+			#ifdef NODE_OPACITY
+
+				outColor.a = NODE_OPACITY;
+
+			#endif
+
 		}`
 	}
 };

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

@@ -0,0 +1,294 @@
+import WebGPUNodeUniformsGroup from './WebGPUNodeUniformsGroup.js';
+import { FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform, ColorNodeUniform } from './WebGPUNodeUniform.js';
+import WebGPUSampler from '../WebGPUSampler.js';
+import { WebGPUSampledTexture } from '../WebGPUSampledTexture.js';
+
+import NodeSlot from '../../nodes/core/NodeSlot.js';
+import NodeBuilder from '../../nodes/core/NodeBuilder.js';
+
+import ShaderLib from './ShaderLib.js';
+
+class WebGPUNodeBuilder extends NodeBuilder {
+
+	constructor( material, renderer ) {
+
+		super( material, renderer );
+
+		this.bindingIndex = 2;
+		this.bindings = { vertex: [], fragment: [] };
+
+		this.attributeIndex = 1;
+		this.varyIndex = 0;
+
+		this.uniformsGroup = {};
+
+		this.nativeShader = null;
+
+		this._parseMaterial();
+
+	}
+
+	_parseMaterial() {
+
+		const material = this.material;
+
+		// get shader
+
+		if ( material.isMeshBasicMaterial ) {
+
+			this.nativeShader = ShaderLib.meshBasic;
+
+		} else if ( material.isPointsMaterial ) {
+
+			this.nativeShader = ShaderLib.pointsBasic;
+
+		} else if ( material.isLineBasicMaterial ) {
+
+			this.nativeShader = ShaderLib.lineBasic;
+
+		} else {
+
+			console.error( 'THREE.WebGPURenderer: Unknwon shader type.' );
+
+		}
+
+		// parse inputs
+
+		if ( material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
+
+			if ( material.colorNode !== undefined ) {
+
+				this.addSlot( 'fragment', new NodeSlot( material.colorNode, 'COLOR', 'vec4' ) );
+
+			}
+
+			if ( material.opacityNode !== undefined ) {
+
+				this.addSlot( 'fragment', new NodeSlot( material.opacityNode, 'OPACITY', 'float' ) );
+
+			}
+
+		}
+
+	}
+
+	getTexture( textureProperty, uvSnippet ) {
+
+		return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet} )`;
+
+	}
+
+	getPropertyName( nodeUniform ) {
+
+		if ( nodeUniform.type === 'texture' ) {
+
+			return nodeUniform.name;
+
+		} else {
+
+			return `nodeUniforms.${nodeUniform.name}`;
+
+		}
+
+	}
+
+	getBindings( shaderStage ) {
+
+		return this.bindings[ shaderStage ];
+
+	}
+
+	getUniformFromNode( node, shaderStage, type ) {
+
+		const uniformNode = super.getUniformFromNode( node, shaderStage, type );
+		const nodeData = this.getDataFromNode( node, shaderStage );
+
+		if ( nodeData.uniformGPU === undefined ) {
+
+			let uniformGPU;
+
+			const bindings = this.bindings[ shaderStage ];
+
+			if ( type === 'texture' ) {
+
+				const sampler = new WebGPUSampler( `${uniformNode.name}_sampler`, uniformNode.value );
+				const texture = new WebGPUSampledTexture( uniformNode.name, uniformNode.value );
+
+				// add first textures in sequence and group for last
+				const lastBinding = bindings[ bindings.length - 1 ];
+				const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length;
+
+				bindings.splice( index, 0, sampler, texture );
+
+				uniformGPU = { sampler, texture };
+
+			} else {
+
+				let uniformsGroup = this.uniformsGroup[ shaderStage ];
+
+				if ( uniformsGroup === undefined ) {
+
+					uniformsGroup = new WebGPUNodeUniformsGroup( shaderStage );
+
+					this.uniformsGroup[ shaderStage ] = uniformsGroup;
+
+					bindings.push( uniformsGroup );
+
+				}
+
+				if ( type === 'float' ) {
+
+					uniformGPU = new FloatNodeUniform( uniformNode );
+
+				} else if ( type === 'vec2' ) {
+
+					uniformGPU = new Vector2NodeUniform( uniformNode );
+
+				} else if ( type === 'vec3' ) {
+
+					uniformGPU = new Vector3NodeUniform( uniformNode );
+
+				} else if ( type === 'vec4' ) {
+
+					uniformGPU = new Vector4NodeUniform( uniformNode );
+
+				} else if ( type === 'color' ) {
+
+					uniformGPU = new ColorNodeUniform( uniformNode );
+
+				} else {
+
+					throw new Error( `Uniform "${type}" not declared.` );
+
+				}
+
+				uniformsGroup.addUniform( uniformGPU );
+
+			}
+
+			nodeData.uniformGPU = uniformGPU;
+
+		}
+
+		return uniformNode;
+
+	}
+
+	getAttributesHeaderSnippet( shaderStage ) {
+
+		let snippet = '';
+
+		const attributes = this.attributes;
+
+		let attributeIndex = this.attributeIndex;
+		let varyIndex = this.varyIndex;
+
+		for ( let name in attributes ) {
+
+			let attribute = attributes[ name ];
+
+			let type = attribute.type;
+			let property = attribute.property;
+
+			if ( shaderStage === 'vertex' ) {
+
+				snippet += `layout(location = ${attributeIndex ++}) in ${type} ${name};`;
+				snippet += `layout(location = ${varyIndex ++}) out ${type} ${property};`;
+
+			} else if ( shaderStage === 'fragment' ) {
+
+				snippet += `layout(location = ${varyIndex ++}) in ${type} ${property};`;
+
+			}
+
+		}
+
+		return snippet;
+
+	}
+
+	getAttributesBodySnippet( shaderStage ) {
+
+		let snippet = '';
+
+		const attributes = this.attributes;
+
+		for ( let name in attributes ) {
+
+			let attribute = attributes[ name ];
+
+			let property = attribute.property;
+
+			snippet += `${property} = ${name};`;
+
+		}
+
+		return snippet;
+
+	}
+
+	getUniformsHeaderSnippet( shaderStage ) {
+
+		const uniforms = this.uniforms[ shaderStage ];
+
+		let snippet = '';
+		let groupSnippet = '';
+
+		let bindingIndex = this.bindingIndex;
+
+		for ( let uniform of uniforms ) {
+
+			if ( uniform.type === 'texture' ) {
+
+				snippet += `layout(set = 0, binding = ${bindingIndex ++}) uniform sampler ${uniform.name}_sampler;`;
+				snippet += `layout(set = 0, binding = ${bindingIndex ++}) uniform texture2D ${uniform.name};`;
+
+			} else {
+
+				let vectorType = this.getVectorType( uniform.type );
+
+				groupSnippet += `uniform ${vectorType} ${uniform.name};`;
+
+			}
+
+		}
+
+		if ( groupSnippet ) {
+
+			snippet += `layout(set = 0, binding = ${bindingIndex ++}) uniform NodeUniforms { ${groupSnippet} } nodeUniforms;`;
+
+		}
+
+		return snippet;
+
+	}
+
+	composeShaderCode( code, snippet ) {
+
+		// use regex maybe for security?
+		const versionStrIndex = code.indexOf( "\n" );
+
+		let finalCode = code.substr( 0, versionStrIndex ) + "\n\n";
+
+		finalCode += snippet;
+
+		finalCode += code.substr( versionStrIndex );
+
+		return finalCode;
+
+	}
+
+	build() {
+
+		super.build();
+
+		this.vertexShader = this.composeShaderCode( this.nativeShader.vertexShader, this.vertexShader );
+		this.fragmentShader = this.composeShaderCode( this.nativeShader.fragmentShader, this.fragmentShader );
+
+		return this;
+
+	}
+
+}
+
+export default WebGPUNodeBuilder;

+ 135 - 0
examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniform.js

@@ -0,0 +1,135 @@
+import {
+	FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform,
+	ColorUniform, Matrix3Uniform, Matrix4Uniform
+} from '../WebGPUUniform.js';
+
+class FloatNodeUniform extends FloatUniform {
+
+	constructor( nodeUniform ) {
+
+		super( nodeUniform.name, nodeUniform.value );
+
+		this.nodeUniform = nodeUniform;
+
+	}
+
+	getValue() {
+
+		return this.nodeUniform.value;
+
+	}
+
+}
+
+class Vector2NodeUniform extends Vector2Uniform {
+
+	constructor( nodeUniform ) {
+
+		super( nodeUniform.name, nodeUniform.value );
+
+		this.nodeUniform = nodeUniform;
+
+	}
+
+	getValue() {
+
+		return this.nodeUniform.value;
+
+	}
+
+}
+
+class Vector3NodeUniform extends Vector3Uniform {
+
+	constructor( nodeUniform ) {
+
+		super( nodeUniform.name, nodeUniform.value );
+
+		this.nodeUniform = nodeUniform;
+
+	}
+
+	getValue() {
+
+		return this.nodeUniform.value;
+
+	}
+
+}
+
+class Vector4NodeUniform extends Vector4Uniform {
+
+	constructor( nodeUniform ) {
+
+		super( nodeUniform.name, nodeUniform.value );
+
+		this.nodeUniform = nodeUniform;
+
+	}
+
+	getValue() {
+
+		return this.nodeUniform.value;
+
+	}
+
+}
+
+class ColorNodeUniform extends ColorUniform {
+
+	constructor( nodeUniform ) {
+
+		super( nodeUniform.name, nodeUniform.value );
+
+		this.nodeUniform = nodeUniform;
+
+	}
+
+	getValue() {
+
+		return this.nodeUniform.value;
+
+	}
+
+}
+
+class Matrix3NodeUniform extends Matrix3Uniform {
+
+	constructor( nodeUniform ) {
+
+		super( nodeUniform.name, nodeUniform.value );
+
+		this.nodeUniform = nodeUniform;
+
+	}
+
+	getValue() {
+
+		return this.nodeUniform.value;
+
+	}
+
+}
+
+class Matrix4NodeUniform extends Matrix4Uniform {
+
+	constructor( nodeUniform ) {
+
+		super( nodeUniform.name, nodeUniform.value );
+
+		this.nodeUniform = nodeUniform;
+
+	}
+
+	getValue() {
+
+		return this.nodeUniform.value;
+
+	}
+
+}
+
+export {
+	FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
+	ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
+};

+ 29 - 0
examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js

@@ -0,0 +1,29 @@
+import WebGPUUniformsGroup from '../WebGPUUniformsGroup.js';
+
+class WebGPUNodeUniformsGroup extends WebGPUUniformsGroup {
+
+	constructor( shaderStage ) {
+
+		super( 'nodeUniforms' );
+
+		let shaderStageVisibility;
+
+		if ( shaderStage === 'vertex' ) shaderStageVisibility = GPUShaderStage.VERTEX;
+		else if ( shaderStage === 'fragment' ) shaderStageVisibility = GPUShaderStage.FRAGMENT;
+
+		this.setVisibility( shaderStageVisibility );
+
+		//this.setOnBeforeUpdate( this._onBeforeUpdate );
+
+	}
+	/*
+	_onBeforeUpdate( object, camera ) {
+
+		const material = object.material;
+
+	}
+	*/
+
+}
+
+export default WebGPUUniformsGroup;

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

@@ -0,0 +1,68 @@
+import WebGPUNodeBuilder from './WebGPUNodeBuilder.js';
+import NodeFrame from '../../nodes/core/NodeFrame.js';
+
+class WebGPUNodes {
+
+	constructor( renderer ) {
+
+		this.renderer = renderer;
+
+		this.nodeFrame = new NodeFrame();
+
+		this.builders = new WeakMap();
+
+	}
+
+	get( material ) {
+
+		let nodeBuilder = this.builders.get( material );
+
+		if ( nodeBuilder === undefined ) {
+
+			nodeBuilder = new WebGPUNodeBuilder( material, this.renderer ).build();
+
+			this.builders.set( material, nodeBuilder );
+
+		}
+
+		return nodeBuilder;
+
+	}
+
+	remove( object ) {
+
+		this.builders.delete( object );
+
+	}
+
+	updateFrame() {
+
+		this.nodeFrame.update();
+
+	}
+
+	update( object, camera ) {
+
+		const material = object.material;
+
+		const nodeBuilder = this.get( material );
+
+		this.nodeFrame.material = object.material;
+
+		for ( let node of nodeBuilder.updateNodes ) {
+
+			this.nodeFrame.updateNode( node );
+
+		}
+
+	}
+
+	dispose() {
+
+		this.builders = new WeakMap();
+
+	}
+
+}
+
+export default WebGPUNodes;

+ 9 - 0
examples/webgpu_compute.html

@@ -21,6 +21,10 @@
 			import WebGPUUniformsGroup from './jsm/renderers/webgpu/WebGPUUniformsGroup.js';
 			import { Vector2Uniform } from './jsm/renderers/webgpu/WebGPUUniform.js';
 
+			import AttributeNode from './jsm/renderers/nodes/core/AttributeNode.js';
+			import ColorNode from './jsm/renderers/nodes/inputs/ColorNode.js';
+			import OperatorNode from './jsm/renderers/nodes/math/OperatorNode.js';
+
 			let camera, scene, renderer;
 			let pointer;
 
@@ -151,7 +155,12 @@
 				const pointsGeometry = new THREE.BufferGeometry().setAttribute(
 					'position', particleBuffer.attribute
 				);
+
+				pointsGeometry.setAttribute( 'color', pointsGeometry.getAttribute( 'position' ) );
+
 				const pointsMaterial = new THREE.PointsMaterial();
+				pointsMaterial.colorNode = new OperatorNode( '+', new AttributeNode( 'vec3', 'color' ), new ColorNode( new THREE.Color( 0x0000FF ) ) );
+
 				const mesh = new THREE.Points( pointsGeometry, pointsMaterial );
 				scene.add( mesh );
 

+ 23 - 4
examples/webgpu_rtt.html

@@ -18,7 +18,12 @@
 			import WebGPUTextureRenderer from './jsm/renderers/webgpu/WebGPUTextureRenderer.js';
 			import WebGPU from './jsm/renderers/webgpu/WebGPU.js';
 
+			import TextureNode from './jsm/renderers/nodes/inputs/TextureNode.js';
+			import OperatorNode from './jsm/renderers/nodes/math/OperatorNode.js';
+			import Vector2Node from './jsm/renderers/nodes/inputs/Vector2Node.js';
+
 			let camera, scene, renderer;
+			let mouse = new THREE.Vector2();
 
 			let cameraFX, sceneFX, textureRenderer;
 
@@ -49,8 +54,11 @@
 				const loader = new THREE.TextureLoader();
 				const texture = loader.load( './textures/uv_grid_opengl.jpg' );
 
-				const geometryBox = new THREE.BoxGeometry();
-				const materialBox = new THREE.MeshBasicMaterial( { map: texture } );
+				const geometryBox = new THREE.BoxBufferGeometry();
+				const materialBox = new THREE.MeshBasicMaterial();
+				materialBox.colorNode = new TextureNode( texture );
+
+				//
 
 				box = new THREE.Mesh( geometryBox, materialBox );
 				scene.add( box );
@@ -65,7 +73,8 @@
 				textureRenderer = new WebGPUTextureRenderer( renderer );
 				textureRenderer.setSize( window.innerWidth * dpr, window.innerHeight * dpr );
 
-				window.addEventListener( 'resize', onWindowResize );
+				window.addEventListener( 'mousemove', onWindowMouseMove, false );
+				window.addEventListener( 'resize', onWindowResize, false );
 
 				// FX
 
@@ -76,7 +85,10 @@
 
 				// @TODO Until NodeMaterial is available just copy the beauty pass to screen
 
-				const materialFX = new THREE.MeshBasicMaterial( { map: textureRenderer.getTexture() } );
+				const screenFXNode = new OperatorNode( '+', new Vector2Node( mouse ), new Vector2Node( new THREE.Vector2( .5, .5 ) ).setConst( true ) );
+
+				const materialFX = new THREE.MeshBasicMaterial();
+				materialFX.colorNode = new OperatorNode( '*', new TextureNode( textureRenderer.getTexture() ), screenFXNode );
 
 				const quad = new THREE.Mesh( geometryFX, materialFX );
 				sceneFX.add( quad );
@@ -87,6 +99,13 @@
 
 			}
 
+			function onWindowMouseMove( e ) {
+
+				mouse.x = e.offsetX / screen.width;
+				mouse.y = e.offsetY / screen.height;
+
+			}
+
 			function onWindowResize() {
 
 				camera.aspect = window.innerWidth / window.innerHeight;

+ 50 - 6
examples/webgpu_sandbox.html

@@ -19,6 +19,17 @@
 			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
 			import WebGPU from './jsm/renderers/webgpu/WebGPU.js';
 
+			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 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';
+
 			let camera, scene, renderer;
 
 			let box;
@@ -45,18 +56,43 @@
 
 				const textureLoader = new THREE.TextureLoader();
 				const texture = textureLoader.load( './textures/uv_grid_opengl.jpg' );
+				texture.wrapS = THREE.RepeatWrapping;
+				texture.wrapT = THREE.RepeatWrapping;
+				texture.name = 'uv_grid';
+
+				// compressed texture
+
+				const ddsLoader = new DDSLoader();
+				const dxt5Texture = ddsLoader.load( './textures/compressed/explosion_dxt5_mip.dds' );
+
+				//
 
 				const geometryBox = new THREE.BoxGeometry();
 				const materialBox = new THREE.MeshBasicMaterial( { map: texture } );
 
+				const timerNode = new TimerNode();
+
+				// birection speed
+				const timerScaleNode = new OperatorNode( '*', timerNode, new Vector2Node( new THREE.Vector2( - .5, .1 ) ).setConst( true ) );
+				const animateUV = new OperatorNode( '+', new UVNode(), timerScaleNode );
+
+				materialBox.colorNode = new TextureNode( texture, animateUV );
+
+				// test uv 2
+				//geometryBox.setAttribute( 'uv2', geometryBox.getAttribute( 'uv' ) );
+				//materialBox.colorNode = new TextureNode( texture, new UVNode( 1 ) );
+
 				box = new THREE.Mesh( geometryBox, materialBox );
 				box.position.set( 0, 1, 0 );
 				scene.add( box );
 
 				// data texture
 
-				const geometryPlane = new THREE.PlaneGeometry();
-				const materialPlane = new THREE.MeshBasicMaterial( { map: createDataTexture(), transparent: true, opacity: 0.5 } );
+				const geometryPlane = new THREE.PlaneBufferGeometry();
+				const materialPlane = new THREE.MeshBasicMaterial();
+				materialPlane.colorNode = new OperatorNode( '+', new TextureNode( createDataTexture() ), new ColorNode( new THREE.Color( 0x0000FF ) ) );
+				materialPlane.opacityNode = new SwitchNode( new TextureNode( dxt5Texture ), 'a' );
+				materialPlane.transparent = true;
 
 				const plane = new THREE.Mesh( geometryPlane, materialPlane );
 				plane.position.set( 0, - 1, 0 );
@@ -64,10 +100,9 @@
 
 				// compressed texture
 
-				const ddsLoader = new DDSLoader();
-				const dxt5Texture = ddsLoader.load( './textures/compressed/explosion_dxt5_mip.dds' );
-
-				const materialCompressed = new THREE.MeshBasicMaterial( { map: dxt5Texture, transparent: true } );
+				const materialCompressed = new THREE.MeshBasicMaterial();
+				materialCompressed.colorNode = new TextureNode( dxt5Texture );
+				materialCompressed.transparent = true;
 
 				const boxCompressed = new THREE.Mesh( geometryBox, materialCompressed );
 				boxCompressed.position.set( - 2, 1, 0 );
@@ -87,6 +122,10 @@
 				const geometryPoints = new THREE.BufferGeometry().setFromPoints( points );
 				const materialPoints = new THREE.PointsMaterial();
 
+				geometryPoints.setAttribute( 'color', geometryPoints.getAttribute( 'position' ) );
+
+				materialPoints.colorNode = new OperatorNode( '*', new AttributeNode( 'vec3', 'color' ), new FloatNode( 3 ).setConst( true ) );
+
 				const pointCloud = new THREE.Points( geometryPoints, materialPoints );
 				pointCloud.position.set( 2, - 1, 0 );
 				scene.add( pointCloud );
@@ -99,7 +138,12 @@
 					new THREE.Vector3( 0.5, 0.5, 0 ),
 					new THREE.Vector3( - 0.5, 0.5, 0 )
 				] );
+
+				geometryLine.setAttribute( 'color', geometryLine.getAttribute( 'position' ) );
+
 				const materialLine = new THREE.LineBasicMaterial();
+				materialLine.colorNode = new AttributeNode( 'vec3', 'color' );
+
 				const line = new THREE.Line( geometryLine, materialLine );
 				line.position.set( 2, 1, 0 );
 				scene.add( line );