浏览代码

Merge pull request #21170 from sunag/nodematerial-webgpu

WebGPU: All UBOs and varys is generate by nodes
Michael Herzog 4 年之前
父节点
当前提交
d4c3ea2ebd
共有 33 个文件被更改,包括 685 次插入384 次删除
  1. 96 0
      examples/jsm/renderers/nodes/accessors/CameraNode.js
  2. 32 0
      examples/jsm/renderers/nodes/accessors/GLPositionNode.js
  3. 39 0
      examples/jsm/renderers/nodes/accessors/ModelNode.js
  4. 39 0
      examples/jsm/renderers/nodes/accessors/PositionNode.js
  5. 2 19
      examples/jsm/renderers/nodes/accessors/UVNode.js
  6. 21 13
      examples/jsm/renderers/nodes/core/AttributeNode.js
  7. 9 1
      examples/jsm/renderers/nodes/core/Node.js
  8. 14 0
      examples/jsm/renderers/nodes/core/NodeAttribute.js
  9. 117 59
      examples/jsm/renderers/nodes/core/NodeBuilder.js
  10. 14 2
      examples/jsm/renderers/nodes/core/NodeFrame.js
  11. 2 0
      examples/jsm/renderers/nodes/core/NodeUniform.js
  12. 15 0
      examples/jsm/renderers/nodes/core/NodeVary.js
  13. 36 0
      examples/jsm/renderers/nodes/core/VaryNode.js
  14. 8 2
      examples/jsm/renderers/nodes/core/constants.js
  15. 2 0
      examples/jsm/renderers/nodes/inputs/ColorNode.js
  16. 2 0
      examples/jsm/renderers/nodes/inputs/FloatNode.js
  17. 18 0
      examples/jsm/renderers/nodes/inputs/Matrix3Node.js
  18. 18 0
      examples/jsm/renderers/nodes/inputs/Matrix4Node.js
  19. 2 0
      examples/jsm/renderers/nodes/inputs/TextureNode.js
  20. 2 0
      examples/jsm/renderers/nodes/inputs/Vector2Node.js
  21. 4 1
      examples/jsm/renderers/nodes/inputs/Vector3Node.js
  22. 4 1
      examples/jsm/renderers/nodes/inputs/Vector4Node.js
  23. 46 5
      examples/jsm/renderers/nodes/math/OperatorNode.js
  24. 2 1
      examples/jsm/renderers/nodes/utils/TimerNode.js
  25. 2 0
      examples/jsm/renderers/webgpu/WebGPUBinding.js
  26. 7 73
      examples/jsm/renderers/webgpu/WebGPUBindings.js
  27. 0 6
      examples/jsm/renderers/webgpu/WebGPUUniform.js
  28. 21 126
      examples/jsm/renderers/webgpu/nodes/ShaderLib.js
  29. 77 50
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  30. 0 9
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js
  31. 8 5
      examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js
  32. 2 4
      examples/webgpu_compute.html
  33. 24 7
      examples/webgpu_sandbox.html

+ 96 - 0
examples/jsm/renderers/nodes/accessors/CameraNode.js

@@ -0,0 +1,96 @@
+import Node from '../core/Node.js';
+import Vector3Node from '../inputs/Vector3Node.js';
+import Matrix4Node from '../inputs/Matrix4Node.js';
+import { NodeUpdateType } from '../core/constants.js';
+
+class CameraNode extends Node {
+
+	static POSITION = 'position';
+	static PROJECTION = 'projection';
+	static VIEW = 'view';
+
+	constructor( scope = CameraNode.POSITION ) {
+
+		super();
+
+		this.updateType = NodeUpdateType.Frame;
+
+		this.scope = scope;
+
+		this._inputNode = null;
+
+	}
+
+	getType() {
+
+		const scope = this.scope;
+
+		if ( scope === CameraNode.PROJECTION || scope === CameraNode.VIEW ) {
+
+			return 'mat4';
+
+		}
+
+		return 'vec3';
+
+	}
+
+	update( frame ) {
+
+		const camera = frame.camera;
+		const inputNode = this._inputNode;
+		const scope = this.scope;
+
+		if ( scope === CameraNode.PROJECTION ) {
+
+			inputNode.value = camera.projectionMatrix;
+
+		} else if ( scope === CameraNode.VIEW ) {
+
+			inputNode.value = camera.matrixWorldInverse;
+
+		} else if ( scope === CameraNode.POSITION ) {
+
+			camera.getWorldPosition( inputNode.value );
+
+		}
+
+	}
+
+	generate( builder, output ) {
+
+		const nodeData = builder.getDataFromNode( this );
+
+		let inputNode = this._inputNode;
+
+		if ( nodeData.inputNode === undefined ) {
+
+			const scope = this.scope;
+
+			if ( scope === CameraNode.PROJECTION || scope === CameraNode.VIEW ) {
+
+				if ( inputNode === null || inputNode.isMatrix4Node !== true ) {
+
+					inputNode = new Matrix4Node( null );
+
+				}
+
+			} else if ( inputNode === null || inputNode.isVector3Node !== true ) {
+
+				inputNode = new Vector3Node();
+
+			}
+
+			this._inputNode = inputNode;
+
+			nodeData.inputNode = inputNode;
+
+		}
+
+		return inputNode.build( builder, output );
+
+	}
+
+}
+
+export default CameraNode;

+ 32 - 0
examples/jsm/renderers/nodes/accessors/GLPositionNode.js

@@ -0,0 +1,32 @@
+import Node from '../core/Node.js';
+import CameraNode from '../accessors/CameraNode.js';
+import ModelNode from '../accessors/ModelNode.js';
+import OperatorNode from '../math/OperatorNode.js';
+import PositionNode from '../accessors/PositionNode.js';
+
+class GLPositionNode extends Node {
+
+	constructor( position = new PositionNode() ) {
+
+		super( 'vec4' );
+
+		this.position = position;
+
+		this._mvpMatrix = new OperatorNode( '*', new CameraNode( CameraNode.PROJECTION ), new ModelNode( ModelNode.VIEW ) );
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		const mvpSnipped = this._mvpMatrix.build( builder );
+		const positionSnipped = this.position.build( builder, 'vec3' );
+
+		return builder.format( `( ${mvpSnipped} * vec4( ${positionSnipped}, 1.0 ) )`, type, output );
+
+	}
+
+}
+
+export default GLPositionNode;

+ 39 - 0
examples/jsm/renderers/nodes/accessors/ModelNode.js

@@ -0,0 +1,39 @@
+import Node from '../core/Node.js';
+import Vector3Node from '../inputs/Vector3Node.js';
+import Matrix4Node from '../inputs/Matrix4Node.js';
+import { NodeUpdateType } from '../core/constants.js';
+
+class ModelNode extends Node {
+
+	static VIEW = 'view';
+
+	constructor( scope = ModelNode.VIEW ) {
+
+		super( 'mat4' );
+
+		this.scope = scope;
+
+		this.updateType = NodeUpdateType.Object;
+
+		this._inputNode = new Matrix4Node( null );
+
+	}
+
+	update( frame ) {
+
+		const object = frame.object;
+		const inputNode = this._inputNode;
+
+		inputNode.value = object.modelViewMatrix;
+
+	}
+
+	generate( builder, output ) {
+
+		return this._inputNode.build( builder, output );
+
+	}
+
+}
+
+export default ModelNode;

+ 39 - 0
examples/jsm/renderers/nodes/accessors/PositionNode.js

@@ -0,0 +1,39 @@
+import Node from '../core/Node.js';
+import AttributeNode from '../core/AttributeNode.js';
+
+class PositionNode extends Node {
+
+	static LOCAL = 'position';
+
+	constructor( scope = PositionNode.POSITION ) {
+
+		super( 'vec3' );
+
+		this.scope = scope;
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+		const nodeData = builder.getDataFromNode( this, builder.shaderStage );
+
+		let positionNode = nodeData.positionNode;
+
+		if ( positionNode === undefined ) {
+
+			positionNode = new AttributeNode( 'position', 'vec3' );
+
+			nodeData.positionNode = positionNode;
+
+		}
+
+		const positionSnipped = positionNode.build( builder, type );
+
+		return builder.format( positionSnipped, type, output );
+
+	}
+
+}
+
+export default PositionNode;

+ 2 - 19
examples/jsm/renderers/nodes/accessors/UVNode.js

@@ -4,32 +4,15 @@ class UVNode extends AttributeNode {
 
 	constructor( index = 0 ) {
 
-		super( 'vec2' );
+		super( null, '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 );
+		return 'uv' + ( this.index > 0 ? this.index + 1 : '' );
 
 	}
 

+ 21 - 13
examples/jsm/renderers/nodes/core/AttributeNode.js

@@ -2,12 +2,11 @@ import Node from './Node.js';
 
 class AttributeNode extends Node {
 
-	constructor( type, name = null, property = null ) {
+	constructor( name, type ) {
 
 		super( type );
 
 		this.name = name;
-		this.property = property;
 
 	}
 
@@ -25,27 +24,36 @@ class AttributeNode extends Node {
 
 	}
 
-	setAttributeProperty( name ) {
+	generate( builder, output ) {
 
-		this.property = name;
+		const attributeName = this.getAttributeName( builder );
+		const attributeType = this.getType( builder );
 
-		return this;
+		const attribute = builder.getAttribute( attributeName, attributeType );
 
-	}
+		if ( builder.isShaderStage( 'vertex' ) ) {
 
-	getAttributeProperty( builder ) {
+			return builder.format( attribute.name, attribute.type, output );
 
-		const attribute = builder.getAttribute( this.getType( builder ), this.getAttributeName( builder ), this.property );
+		} else {
 
-		return attribute.property;
+			const nodeData = builder.getDataFromNode( this, builder.shaderStage );
 
-	}
+			let nodeVary = nodeData.varyNode;
 
-	generate( builder, output ) {
+			if ( nodeVary === undefined ) {
+
+				nodeVary = builder.getVaryFromNode( this, attribute.type, attributeName );
+
+				nodeData.nodeVary = nodeVary;
+
+			}
+
+			const varyName = builder.getPropertyName( nodeVary );
 
-		const attributeProperty = this.getAttributeProperty( builder );
+			return builder.format( varyName, attribute.type, output );
 
-		return builder.format( attributeProperty, this.getType( builder ), output );
+		}
 
 	}
 

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

@@ -1,15 +1,23 @@
+import { NodeUpdateType } from './constants.js';
+
 class Node {
 
 	constructor( type = null ) {
 
 		this.type = type;
 
-		this.needsUpdate = false;
+		this.updateType = NodeUpdateType.None;
 
 		Object.defineProperty( this, 'isNode', { value: true } );
 
 	}
 
+	getUpdateType( /*builder*/ ) {
+
+		return this.updateType;
+
+	}
+
 	getType( /*builder*/ ) {
 
 		return this.type;

+ 14 - 0
examples/jsm/renderers/nodes/core/NodeAttribute.js

@@ -0,0 +1,14 @@
+class NodeAttribute {
+
+	constructor( name, type ) {
+
+		this.name = name;
+		this.type = type;
+
+		Object.defineProperty( this, 'isNodeAttribute', { value: true } );
+
+	}
+
+}
+
+export default NodeAttribute;

+ 117 - 59
examples/jsm/renderers/nodes/core/NodeBuilder.js

@@ -1,4 +1,7 @@
 import NodeUniform from './NodeUniform.js';
+import NodeAttribute from './NodeAttribute.js';
+import NodeVary from './NodeVary.js';
+import { NodeUpdateType } from './constants.js';
 
 class NodeBuilder {
 
@@ -16,8 +19,8 @@ class NodeBuilder {
 		this.slots = { vertex: [], fragment: [] };
 		this.defines = { vertex: {}, fragment: {} };
 		this.uniforms = { vertex: [], fragment: [] };
-		this.attributes = {};
-		this.attributeCount = 0;
+		this.attributes = [];
+		this.varys = [];
 
 		this.nodesData = new WeakMap();
 
@@ -29,7 +32,9 @@ class NodeBuilder {
 
 		if ( this.nodes.indexOf( node ) === - 1 ) {
 
-			if ( node.needsUpdate === true ) {
+			const updateType = node.getUpdateType( this );
+
+			if ( updateType !== NodeUpdateType.None ) {
 
 				this.updateNodes.push( node );
 
@@ -55,7 +60,7 @@ class NodeBuilder {
 
 	getTexture( /* textureProperty, uvSnippet */ ) {
 
-
+		console.warn( 'Abstract function.' );
 
 	}
 
@@ -71,38 +76,53 @@ class NodeBuilder {
 
 	}
 
-	getAttribute( type, name, property = null ) {
+	getAttribute( name, type ) {
 
-		let attribute = this.attributes[ name ];
+		const attributes = this.attributes;
 
-		if ( attribute === undefined ) {
+		// find attribute
 
-			const index = this.attributeCount ++;
+		for ( const attribute of attributes ) {
 
-			if ( property === null ) {
+			if ( attribute.name === name ) {
 
-				property = `node_A${index}`;
+				return attribute;
 
 			}
 
-			attribute = {
-				type,
-				name,
-				index,
-				property
-			};
+		}
+
+		// create a new if no exist
 
-			this.attributes[ name ] = attribute;
+		const attribute = new NodeAttribute( name, type );
 
-		}
+		attributes.push( attribute );
 
 		return attribute;
 
 	}
 
-	getPropertyName( nodeUniform ) {
+	getPropertyName( node ) {
+
+		return node.name;
+
+	}
+
+	isVector( type ) {
+
+		return /vec\d/.test( type );
+
+	}
+
+	isMatrix( type ) {
+
+		return /mat\d/.test( type );
+
+	}
+
+	isShaderStage( shaderStage ) {
 
-		return nodeUniform.name;
+		return this.shaderStage === shaderStage;
 
 	}
 
@@ -178,6 +198,29 @@ class NodeBuilder {
 
 	}
 
+	getVaryFromNode( node, type, value ) {
+
+		const nodeData = this.getDataFromNode( node );
+
+		let nodeVary = nodeData.vary;
+
+		if ( nodeVary === undefined ) {
+
+			const varys = this.varys;
+			const index = varys.length;
+
+			nodeVary = new NodeVary( 'nodeV' + index, type, value );
+
+			varys.push( nodeVary );
+
+			nodeData.vary = nodeVary;
+
+		}
+
+		return nodeVary;
+
+	}
+
 	/*
 	analyzeNode( node ) {
 
@@ -212,60 +255,31 @@ class NodeBuilder {
 
 	getAttributesBodySnippet( /*shaderStage*/ ) {
 
-
+		console.warn( 'Abstract function.' );
 
 	}
 
 	getAttributesHeaderSnippet( /*shaderStage*/ ) {
 
-
+		console.warn( 'Abstract function.' );
 
 	}
 
-	getUniformsHeaderSnippet( shaderStage ) {
-
-		const uniforms = this.uniforms[ shaderStage ];
+	getVarysHeaderSnippet( /*shaderStage*/ ) {
 
-		let snippet = '';
-
-		for ( const uniform of uniforms ) {
-
-			snippet += `${uniform.type} ${uniform.name}; `;
-
-		}
-
-		return snippet;
+		console.warn( 'Abstract function.' );
 
 	}
 
-	format( snippet, fromType, toType ) {
+	getVarysBodySnippet( /*shaderStage*/ ) {
 
-		fromType = this.getVectorType( fromType );
-		toType = this.getVectorType( toType );
+		console.warn( 'Abstract function.' );
 
-		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`;
+	getUniformsHeaderSnippet( /*shaderStage*/ ) {
 
-		}
-
-		return snippet;
+		console.warn( 'Abstract function.' );
 
 	}
 
@@ -302,7 +316,9 @@ class NodeBuilder {
 
 			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 ) );
+			this.define( shaderStage, 'NODE_HEADER_VARYS', this.getVarysHeaderSnippet( shaderStage ) );
+
+			this.define( shaderStage, 'NODE_BODY_VARYS', this.getVarysBodySnippet( shaderStage ) );
 
 			shaderData[ shaderStage ] = this._buildDefines( shaderStage );
 
@@ -315,6 +331,48 @@ class NodeBuilder {
 
 	}
 
+	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}, 0.0 )`;
+			case 'vec2 to vec4' : return `vec4( ${snippet}.xy, 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}, 1.0 )`;
+
+			case 'vec4 to float' : return `${snippet}.x`;
+			case 'vec4 to vec2' : return `${snippet}.xy`;
+			case 'vec4 to vec3' : return `${snippet}.xyz`;
+
+			case 'mat3 to float' : return `( ${snippet} * vec3( 1.0 ) ).x`;
+			case 'mat3 to vec2' : return `( ${snippet} * vec3( 1.0 ) ).xy`;
+			case 'mat3 to vec3' : return `( ${snippet} * vec3( 1.0 ) ).xyz`;
+			case 'mat3 to vec4' : return `vec4( ${snippet} * vec3( 1.0 ), 1.0 )`;
+
+			case 'mat4 to float' : return `( ${snippet} * vec4( 1.0 ) ).x`;
+			case 'mat4 to vec2' : return `( ${snippet} * vec4( 1.0 ) ).xy`;
+			case 'mat4 to vec3' : return `( ${snippet} * vec4( 1.0 ) ).xyz`;
+			case 'mat4 to vec4' : return `( ${snippet} * vec4( 1.0 ) )`;
+
+		}
+
+		return snippet;
+
+	}
+
+
 }
 
 export default NodeBuilder;

+ 14 - 2
examples/jsm/renderers/nodes/core/NodeFrame.js

@@ -1,3 +1,5 @@
+import { NodeUpdateType } from './constants.js';
+
 class NodeFrame {
 
 	constructor() {
@@ -13,14 +15,24 @@ class NodeFrame {
 
 		this.renderer = null;
 		this.material = null;
+		this.camera = null;
+		this.object = null;
 
 	}
 
 	updateNode( node ) {
 
-		if ( this.updateMap.get( node ) !== this.frameId ) {
+		if ( node.updateType === NodeUpdateType.Frame ) {
+
+			if ( this.updateMap.get( node ) !== this.frameId ) {
+
+				this.updateMap.set( node, this.frameId );
+
+				node.update( this );
+
+			}
 
-			this.updateMap.set( node, this.frameId );
+		} else if ( node.updateType === NodeUpdateType.Object ) {
 
 			node.update( this );
 

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

@@ -7,6 +7,8 @@ class NodeUniform {
 		this.node = node;
 		this.needsUpdate = needsUpdate;
 
+		Object.defineProperty( this, 'isNodeUniform', { value: true } );
+
 	}
 
 	get value() {

+ 15 - 0
examples/jsm/renderers/nodes/core/NodeVary.js

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

+ 36 - 0
examples/jsm/renderers/nodes/core/VaryNode.js

@@ -0,0 +1,36 @@
+import Node from './Node.js';
+
+class VaryNode extends Node {
+
+	constructor( value ) {
+
+		super();
+
+		this.value = value;
+
+	}
+
+	getType( builder ) {
+
+		// VaryNode is auto type
+
+		return this.value.getType( builder );
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		const value = this.value.build( builder, type );
+
+		const nodeVary = builder.getVaryFromNode( this, type, value );
+		const propertyName = builder.getPropertyName( nodeVary );
+
+		return builder.format( propertyName, type, output );
+
+	}
+
+}
+
+export default VaryNode;

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

@@ -1,8 +1,14 @@
+export const NodeUpdateType = {
+	None: 'none',
+	Frame: 'frame',
+	Object: 'object'
+};
+
 export const NodeType = {
 	Float: 'float',
 	Vector2: 'vec2',
 	Vector3: 'vec3',
 	Vector4: 'vec4',
-	Matrix3: 'matrix3',
-	Matrix4: 'matrix4'
+	Matrix3: 'mat3',
+	Matrix4: 'mat4'
 };

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

@@ -8,6 +8,8 @@ class ColorNode extends InputNode {
 
 		this.value = value;
 
+		Object.defineProperty( this, 'isColorNode', { value: true } );
+
 	}
 
 }

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

@@ -8,6 +8,8 @@ class FloatNode extends InputNode {
 
 		this.value = value;
 
+		Object.defineProperty( this, 'isFloatNode', { value: true } );
+
 	}
 
 }

+ 18 - 0
examples/jsm/renderers/nodes/inputs/Matrix3Node.js

@@ -0,0 +1,18 @@
+import InputNode from '../core/InputNode.js';
+import { Matrix3 } from '../../../../../build/three.module.js';
+
+class Matrix3Node extends InputNode {
+
+	constructor( value = new Matrix3() ) {
+
+		super( 'mat3' );
+
+		this.value = value;
+
+		Object.defineProperty( this, 'isMatrix3Node', { value: true } );
+
+	}
+
+}
+
+export default Matrix3Node;

+ 18 - 0
examples/jsm/renderers/nodes/inputs/Matrix4Node.js

@@ -0,0 +1,18 @@
+import InputNode from '../core/InputNode.js';
+import { Matrix4 } from '../../../../../build/three.module.js';
+
+class Matrix4Node extends InputNode {
+
+	constructor( value = new Matrix4() ) {
+
+		super( 'mat4' );
+
+		this.value = value;
+
+		Object.defineProperty( this, 'isMatrix4Node', { value: true } );
+
+	}
+
+}
+
+export default Matrix4Node;

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

@@ -10,6 +10,8 @@ class TextureNode extends InputNode {
 		this.value = value;
 		this.uv = uv;
 
+		Object.defineProperty( this, 'isTextureNode', { value: true } );
+
 	}
 
 	generate( builder, output ) {

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

@@ -8,6 +8,8 @@ class Vector2Node extends InputNode {
 
 		this.value = value;
 
+		Object.defineProperty( this, 'isVector2Node', { value: true } );
+
 	}
 
 }

+ 4 - 1
examples/jsm/renderers/nodes/inputs/Vector3Node.js

@@ -1,13 +1,16 @@
 import InputNode from '../core/InputNode.js';
+import { Vector3 } from '../../../../../build/three.module.js';
 
 class Vector3Node extends InputNode {
 
-	constructor( value ) {
+	constructor( value = new Vector3() ) {
 
 		super( 'vec3' );
 
 		this.value = value;
 
+		Object.defineProperty( this, 'isVector3Node', { value: true } );
+
 	}
 
 }

+ 4 - 1
examples/jsm/renderers/nodes/inputs/Vector4Node.js

@@ -1,13 +1,16 @@
 import InputNode from '../core/InputNode.js';
+import { Vector4 } from '../../../../../build/three.module.js';
 
 class Vector4Node extends InputNode {
 
-	constructor( value ) {
+	constructor( value = new Vector4() ) {
 
 		super( 'vec4' );
 
 		this.value = value;
 
+		Object.defineProperty( this, 'isVector4Node', { value: true } );
+
 	}
 
 }

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

@@ -18,9 +18,21 @@ class OperatorNode extends Node {
 		const typeA = this.a.getType( builder );
 		const typeB = this.b.getType( builder );
 
-		// use the greater length vector
+		if ( builder.isMatrix( typeA ) && builder.isVector( typeB ) ) {
 
-		if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
+			// matrix x vector
+
+			return typeB;
+
+		} else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) {
+
+			// vector x matrix
+
+			return typeA;
+
+		} else if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
+
+			// anytype x anytype: use the greater length vector
 
 			return typeB;
 
@@ -30,12 +42,41 @@ class OperatorNode extends Node {
 
 	}
 
+	getVectorFromMatrix( type ) {
+
+		return 'vec' + type.substr( 3 );
+
+	}
+
 	generate( builder, output ) {
 
-		const type = this.getType( builder );
+		let typeA = this.a.getType( builder );
+		let typeB = this.b.getType( builder );
+
+		let type = this.getType( builder );
+
+		if ( builder.isMatrix( typeA ) && builder.isVector( typeB ) ) {
+
+			// matrix x vector
+
+			type = typeB = this.getVectorFromMatrix( typeA );
+
+		} else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) {
+
+			// vector x matrix
+
+			type = typeB = this.getVectorFromMatrix( typeB );
+
+		} else {
+
+			// anytype x anytype
+
+			typeA = typeB = type;
+
+		}
 
-		const a = this.a.build( builder, type );
-		const b = this.b.build( builder, type );
+		const a = this.a.build( builder, typeA );
+		const b = this.b.build( builder, typeB );
 
 		return builder.format( `( ${a} ${this.op} ${b} )`, type, output );
 

+ 2 - 1
examples/jsm/renderers/nodes/utils/TimerNode.js

@@ -1,4 +1,5 @@
 import FloatNode from '../inputs/FloatNode.js';
+import { NodeUpdateType } from '../core/constants.js';
 
 class TimerNode extends FloatNode {
 
@@ -6,7 +7,7 @@ class TimerNode extends FloatNode {
 
 		super();
 
-		this.needsUpdate = true;
+		this.updateType = NodeUpdateType.Frame;
 
 	}
 

+ 2 - 0
examples/jsm/renderers/webgpu/WebGPUBinding.js

@@ -7,6 +7,8 @@ class WebGPUBinding {
 
 		this.type = null; // read-only
 
+		this.isShared = false;
+
 	}
 
 	setVisibility( visibility ) {

+ 7 - 73
examples/jsm/renderers/webgpu/WebGPUBindings.js

@@ -1,6 +1,3 @@
-import WebGPUUniformsGroup from './WebGPUUniformsGroup.js';
-import { Matrix3Uniform, Matrix4Uniform } from './WebGPUUniform.js';
-
 class WebGPUBindings {
 
 	constructor( device, info, properties, textures, pipelines, computePipelines, attributes, nodes ) {
@@ -16,12 +13,8 @@ class WebGPUBindings {
 
 		this.uniformsData = new WeakMap();
 
-		this.sharedUniformsGroups = new Map();
-
 		this.updateMap = new WeakMap();
 
-		this._setupSharedUniformsGroups();
-
 	}
 
 	get( object ) {
@@ -37,7 +30,7 @@ class WebGPUBindings {
 
 			// each material defines an array of bindings (ubos, textures, samplers etc.)
 
-			const bindings = this.composeBindings( object, nodeBuilder.getBindings( 'fragment' ) );
+			const bindings = nodeBuilder.getBindings();
 
 			// setup (static) binding layout and (dynamic) binding group
 
@@ -92,7 +85,6 @@ class WebGPUBindings {
 
 		const updateMap = this.updateMap;
 		const frame = this.info.render.frame;
-		const sharedUniformsGroups = this.sharedUniformsGroups;
 
 		let needsBindGroupRefresh = false;
 
@@ -100,12 +92,12 @@ class WebGPUBindings {
 
 		for ( const binding of bindings ) {
 
-			if ( binding.isUniformsGroup ) {
+			const isShared = binding.isShared;
+			const isUpdated = updateMap.get( binding ) === frame;
 
-				const isShared = sharedUniformsGroups.has( binding.name );
-				const isUpdated = updateMap.get( binding ) === frame;
+			if ( isShared && isUpdated ) continue;
 
-				if ( isShared && isUpdated ) continue;
+			if ( binding.isUniformsGroup ) {
 
 				const array = binding.array;
 				const bufferGPU = binding.bufferGPU;
@@ -125,8 +117,6 @@ class WebGPUBindings {
 
 				}
 
-				updateMap.set( binding, frame );
-
 			} else if ( binding.isStorageBuffer ) {
 
 				const attribute = binding.attribute;
@@ -163,6 +153,8 @@ class WebGPUBindings {
 
 			}
 
+			updateMap.set( binding, frame );
+
 		}
 
 		if ( needsBindGroupRefresh === true ) {
@@ -258,64 +250,6 @@ class WebGPUBindings {
 
 	}
 
-	composeBindings( object, uniforms ) {
-
-		const bindings = [];
-
-		// UBOs
-
-		// model
-
-		const modelViewUniform = new Matrix4Uniform( 'modelMatrix' );
-		const modelViewMatrixUniform = new Matrix4Uniform( 'modelViewMatrix' );
-		const normalMatrixUniform = new Matrix3Uniform( 'normalMatrix' );
-
-		const modelGroup = new WebGPUUniformsGroup( 'modelUniforms' );
-		modelGroup.addUniform( modelViewUniform );
-		modelGroup.addUniform( modelViewMatrixUniform );
-		modelGroup.addUniform( normalMatrixUniform );
-		modelGroup.setOnBeforeUpdate( function ( object/*, camera */ ) {
-
-			modelViewUniform.setValue( object.matrixWorld );
-			modelViewMatrixUniform.setValue( object.modelViewMatrix );
-			normalMatrixUniform.setValue( object.normalMatrix );
-
-		} );
-
-		// camera
-
-		const cameraGroup = this.sharedUniformsGroups.get( 'cameraUniforms' );
-
-		// the order of WebGPUBinding objects must match the binding order in the shader
-
-		bindings.push( modelGroup );
-		bindings.push( cameraGroup );
-
-		bindings.push( ... uniforms );
-
-		return bindings;
-
-	}
-
-	_setupSharedUniformsGroups() {
-
-		const projectionMatrixUniform = new Matrix4Uniform( 'projectionMatrix' );
-		const viewMatrixUniform = new Matrix4Uniform( 'viewMatrix' );
-
-		const cameraGroup = new WebGPUUniformsGroup( 'cameraUniforms' );
-		cameraGroup.addUniform( projectionMatrixUniform );
-		cameraGroup.addUniform( viewMatrixUniform );
-		cameraGroup.setOnBeforeUpdate( function ( object, camera ) {
-
-			projectionMatrixUniform.setValue( camera.projectionMatrix );
-			viewMatrixUniform.setValue( camera.matrixWorldInverse );
-
-		} );
-
-		this.sharedUniformsGroups.set( cameraGroup.name, cameraGroup );
-
-	}
-
 }
 
 export default WebGPUBindings;

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

@@ -41,12 +41,6 @@ class FloatUniform extends WebGPUUniform {
 
 	}
 
-	getValue() {
-
-		return this.nodeUniform.value;
-
-	}
-
 }
 
 class Vector2Uniform extends WebGPUUniform {

+ 21 - 126
examples/jsm/renderers/webgpu/nodes/ShaderLib.js

@@ -1,145 +1,40 @@
 const ShaderLib = {
-	meshBasic: {
+	common: {
 		vertexShader: `#version 450
 
-		layout(location = 0) in vec3 position;
+NODE_HEADER_ATTRIBUTES
+NODE_HEADER_UNIFORMS
+NODE_HEADER_VARYS
 
-		NODE_HEADER_ATTRIBUTES
-		NODE_HEADER_UNIFORMS
-
-		layout(set = 0, binding = 0) uniform ModelUniforms {
-			mat4 modelMatrix;
-			mat4 modelViewMatrix;
-			mat3 normalMatrix;
-		} modelUniforms;
-
-		layout(set = 0, binding = 1) uniform CameraUniforms {
-			mat4 projectionMatrix;
-			mat4 viewMatrix;
-		} 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, 1.0, 1.0, 1.0 );
-
-			#ifdef NODE_COLOR
-
-				outColor = NODE_COLOR;
-
-			#endif
-
-			#ifdef NODE_OPACITY
-
-				outColor.a *= NODE_OPACITY;
-
-			#endif
-
-		}`
-	},
-	pointsBasic: {
-		vertexShader: `#version 450
-
-		layout(location = 0) in vec3 position;
-
-		NODE_HEADER_ATTRIBUTES
-		NODE_HEADER_UNIFORMS
-
-		layout(set = 0, binding = 0) uniform ModelUniforms {
-			mat4 modelMatrix;
-			mat4 modelViewMatrix;
-		} modelUniforms;
-
-		layout(set = 0, binding = 1) uniform CameraUniforms {
-			mat4 projectionMatrix;
-			mat4 viewMatrix;
-		} 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, 1.0, 1.0, 1.0 );
-
-			#ifdef NODE_COLOR
-
-				outColor = NODE_COLOR;
-
-			#endif
-
-			#ifdef NODE_OPACITY
-
-				outColor.a = NODE_OPACITY;
-
-			#endif
-
-		}`
-	},
-	lineBasic: {
-		vertexShader: `#version 450
-
-		layout(location = 0) in vec3 position;
-
-		NODE_HEADER_ATTRIBUTES
-		NODE_HEADER_UNIFORMS
-
-		layout(set = 0, binding = 0) uniform ModelUniforms {
-			mat4 modelMatrix;
-			mat4 modelViewMatrix;
-		} modelUniforms;
-
-		layout(set = 0, binding = 1) uniform CameraUniforms {
-			mat4 projectionMatrix;
-			mat4 viewMatrix;
-		} cameraUniforms;
-
-		void main(){
-			NODE_BODY_ATTRIBUTES
-			gl_Position = cameraUniforms.projectionMatrix * modelUniforms.modelViewMatrix * vec4( position, 1.0 );
-		}`,
+void main(){
+	NODE_BODY_VARYS
+	gl_Position = NODE_GL_POSITION;
+}`,
 		fragmentShader: `#version 450
 
-		NODE_HEADER_ATTRIBUTES
-		NODE_HEADER_UNIFORMS
+NODE_HEADER_ATTRIBUTES
+NODE_HEADER_UNIFORMS
+NODE_HEADER_VARYS
 
-		layout(location = 0) out vec4 outColor;
+layout(location = 0) out vec4 outColor;
 
-		void main() {
+void main() {
 
-			outColor = vec4( 1.0, 1.0, 1.0, 1.0 );
+	outColor = vec4( 1.0, 1.0, 1.0, 1.0 );
 
-			#ifdef NODE_COLOR
+	#ifdef NODE_COLOR
 
-				outColor = NODE_COLOR;
+		outColor = NODE_COLOR;
 
-			#endif
+	#endif
 
-			#ifdef NODE_OPACITY
+	#ifdef NODE_OPACITY
 
-				outColor.a = NODE_OPACITY;
+		outColor.a *= NODE_OPACITY;
 
-			#endif
+	#endif
 
-		}`
+}`
 	}
 };
 

+ 77 - 50
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -1,10 +1,14 @@
 import WebGPUNodeUniformsGroup from './WebGPUNodeUniformsGroup.js';
-import { FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform, ColorNodeUniform } from './WebGPUNodeUniform.js';
+import {
+	FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
+	ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
+} 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 GLPositionNode from '../../nodes/accessors/GLPositionNode.js';
 
 import ShaderLib from './ShaderLib.js';
 
@@ -14,11 +18,8 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		super( material, renderer );
 
-		this.bindingIndex = 2;
 		this.bindings = { vertex: [], fragment: [] };
-
-		this.attributeIndex = 1;
-		this.varyIndex = 0;
+		this.bindingsOffset = { vertex: 0, fragment: 0 };
 
 		this.uniformsGroup = {};
 
@@ -34,27 +35,21 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		// get shader
 
-		if ( material.isMeshBasicMaterial ) {
-
-			this.nativeShader = ShaderLib.meshBasic;
-
-		} else if ( material.isPointsMaterial ) {
+		this.nativeShader = ShaderLib.common;
 
-			this.nativeShader = ShaderLib.pointsBasic;
+		// parse inputs
 
-		} else if ( material.isLineBasicMaterial ) {
+		if ( material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
 
-			this.nativeShader = ShaderLib.lineBasic;
+			const glPositionNode = new GLPositionNode();
 
-		} else {
+			if ( material.positionNode !== undefined ) {
 
-			console.error( 'THREE.WebGPURenderer: Unknwon shader type.' );
+				glPositionNode.position = material.positionNode;
 
-		}
+			}
 
-		// parse inputs
-
-		if ( material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
+			this.addSlot( 'vertex', new NodeSlot( glPositionNode, 'GL_POSITION', 'vec4' ) );
 
 			if ( material.colorNode !== undefined ) {
 
@@ -78,23 +73,34 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getPropertyName( nodeUniform ) {
+	getPropertyName( node ) {
 
-		if ( nodeUniform.type === 'texture' ) {
+		if ( node.isNodeUniform ) {
 
-			return nodeUniform.name;
+			const name = node.name;
+			const type = node.type;
 
-		} else {
+			if ( type === 'texture' ) {
 
-			return `nodeUniforms.${nodeUniform.name}`;
+				return name;
+
+			} else {
+
+				return `nodeUniforms.${name}`;
+
+			}
 
 		}
 
+		return super.getPropertyName( node );
+
 	}
 
-	getBindings( shaderStage ) {
+	getBindings() {
+
+		const bindings = this.bindings;
 
-		return this.bindings[ shaderStage ];
+		return [ ...bindings.vertex, ...bindings.fragment ];
 
 	}
 
@@ -156,6 +162,14 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 					uniformGPU = new ColorNodeUniform( uniformNode );
 
+				} else if ( type === 'mat3' ) {
+
+					uniformGPU = new Matrix3NodeUniform( uniformNode );
+
+				} else if ( type === 'mat4' ) {
+
+					uniformGPU = new Matrix4NodeUniform( uniformNode );
+
 				} else {
 
 					throw new Error( `Uniform "${type}" not declared.` );
@@ -168,6 +182,12 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			nodeData.uniformGPU = uniformGPU;
 
+			if ( shaderStage === 'vertex' ) {
+
+				this.bindingsOffset[ 'fragment' ] = bindings.length;
+
+			}
+
 		}
 
 		return uniformNode;
@@ -178,28 +198,37 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		let snippet = '';
 
-		const attributes = this.attributes;
+		if ( shaderStage === 'vertex' ) {
 
-		let attributeIndex = this.attributeIndex;
-		let varyIndex = this.varyIndex;
+			const attributes = this.attributes;
 
-		for ( const name in attributes ) {
+			for ( let index = 0; index < attributes.length; index ++ ) {
 
-			const attribute = attributes[ name ];
+				const attribute = attributes[ index ];
 
-			const type = attribute.type;
-			const property = attribute.property;
+				snippet += `layout(location = ${index}) in ${attribute.type} ${attribute.name};`;
 
-			if ( shaderStage === 'vertex' ) {
+			}
 
-				snippet += `layout(location = ${attributeIndex ++}) in ${type} ${name};`;
-				snippet += `layout(location = ${varyIndex ++}) out ${type} ${property};`;
+		}
 
-			} else if ( shaderStage === 'fragment' ) {
+		return snippet;
 
-				snippet += `layout(location = ${varyIndex ++}) in ${type} ${property};`;
+	}
 
-			}
+	getVarysHeaderSnippet( shaderStage ) {
+
+		let snippet = '';
+
+		const varys = this.varys;
+
+		const ioStage = shaderStage === 'vertex' ? 'out' : 'in';
+
+		for ( let index = 0; index < varys.length; index ++ ) {
+
+			const vary = varys[ index ];
+
+			snippet += `layout(location = ${index}) ${ioStage} ${vary.type} ${vary.name};`;
 
 		}
 
@@ -207,19 +236,17 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getAttributesBodySnippet( /* shaderStage */ ) {
+	getVarysBodySnippet( shaderStage ) {
 
 		let snippet = '';
 
-		const attributes = this.attributes;
+		if ( shaderStage === 'vertex' ) {
 
-		for ( const name in attributes ) {
+			for ( const vary of this.varys ) {
 
-			const attribute = attributes[ name ];
+				snippet += `${vary.name} = ${vary.value};`;
 
-			const property = attribute.property;
-
-			snippet += `${property} = ${name};`;
+			}
 
 		}
 
@@ -234,14 +261,14 @@ class WebGPUNodeBuilder extends NodeBuilder {
 		let snippet = '';
 		let groupSnippet = '';
 
-		let bindingIndex = this.bindingIndex;
+		let index = this.bindingsOffset[ shaderStage ];
 
 		for ( const 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};`;
+				snippet += `layout(set = 0, binding = ${index ++}) uniform sampler ${uniform.name}_sampler;`;
+				snippet += `layout(set = 0, binding = ${index ++}) uniform texture2D ${uniform.name};`;
 
 			} else {
 
@@ -255,7 +282,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		if ( groupSnippet ) {
 
-			snippet += `layout(set = 0, binding = ${bindingIndex ++}) uniform NodeUniforms { ${groupSnippet} } nodeUniforms;`;
+			snippet += `layout(set = 0, binding = ${index ++}) uniform NodeUniforms { ${groupSnippet} } nodeUniforms;`;
 
 		}
 

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

@@ -13,16 +13,7 @@ class WebGPUNodeUniformsGroup extends WebGPUUniformsGroup {
 
 		this.setVisibility( shaderStageVisibility );
 
-		//this.setOnBeforeUpdate( this._onBeforeUpdate );
-
-	}
-	/*
-	_onBeforeUpdate( object, camera ) {
-
-		const material = object.material;
-
 	}
-	*/
 
 }
 

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

@@ -29,9 +29,9 @@ class WebGPUNodes {
 
 	}
 
-	remove( object ) {
+	remove( material ) {
 
-		this.builders.delete( object );
+		this.builders.delete( material );
 
 	}
 
@@ -41,17 +41,20 @@ class WebGPUNodes {
 
 	}
 
-	update( object/*, camera*/ ) {
+	update( object, camera ) {
 
 		const material = object.material;
 
 		const nodeBuilder = this.get( material );
+		const nodeFrame = this.nodeFrame;
 
-		this.nodeFrame.material = object.material;
+		nodeFrame.material = material;
+		nodeFrame.camera = camera;
+		nodeFrame.object = object;
 
 		for ( const node of nodeBuilder.updateNodes ) {
 
-			this.nodeFrame.updateNode( node );
+			nodeFrame.updateNode( node );
 
 		}
 

+ 2 - 4
examples/webgpu_compute.html

@@ -21,7 +21,7 @@
 			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 PositionNode from './jsm/renderers/nodes/accessors/PositionNode.js';
 			import ColorNode from './jsm/renderers/nodes/inputs/ColorNode.js';
 			import OperatorNode from './jsm/renderers/nodes/math/OperatorNode.js';
 
@@ -156,10 +156,8 @@
 					'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 ) ) );
+				pointsMaterial.colorNode = new OperatorNode( '+', new PositionNode(), new ColorNode( new THREE.Color( 0x0000FF ) ) );
 
 				const mesh = new THREE.Points( pointsGeometry, pointsMaterial );
 				scene.add( mesh );

+ 24 - 7
examples/webgpu_sandbox.html

@@ -25,6 +25,7 @@
 			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 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';
@@ -51,7 +52,7 @@
 				scene = new THREE.Scene();
 				scene.background = new THREE.Color( 0x222222 );
 
-				// textured mesh
+				// textures
 
 				const textureLoader = new THREE.TextureLoader();
 				const texture = textureLoader.load( './textures/uv_grid_opengl.jpg' );
@@ -59,15 +60,19 @@
 				texture.wrapT = THREE.RepeatWrapping;
 				texture.name = 'uv_grid';
 
+				const textureDisplace = textureLoader.load( './textures/transition/transition1.png' );
+				textureDisplace.wrapS = THREE.RepeatWrapping;
+				textureDisplace.wrapT = THREE.RepeatWrapping;
+
 				// compressed texture
 
 				const ddsLoader = new DDSLoader();
 				const dxt5Texture = ddsLoader.load( './textures/compressed/explosion_dxt5_mip.dds' );
 
-				//
+				// box mesh
 
 				const geometryBox = new THREE.BoxGeometry();
-				const materialBox = new THREE.MeshBasicMaterial( { map: texture } );
+				const materialBox = new THREE.MeshBasicMaterial();
 
 				const timerNode = new TimerNode();
 
@@ -85,6 +90,20 @@
 				box.position.set( 0, 1, 0 );
 				scene.add( box );
 
+				// displace example
+
+				const geometrySphere = new THREE.SphereGeometry( .5, 64, 64 );
+				const materialSphere = new THREE.MeshBasicMaterial();
+
+				const displaceScaled = new OperatorNode( '*', new TextureNode( textureDisplace ), new FloatNode( .2 ) );
+
+				materialSphere.colorNode = new TextureNode( textureDisplace );
+				materialSphere.positionNode = new OperatorNode( '+', new PositionNode(), displaceScaled );
+
+				const sphere = new THREE.Mesh( geometrySphere, materialSphere );
+				sphere.position.set( - 2, - 1, 0 );
+				scene.add( sphere );
+
 				// data texture
 
 				const geometryPlane = new THREE.PlaneBufferGeometry();
@@ -121,9 +140,7 @@
 				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 ) );
+				materialPoints.colorNode = new OperatorNode( '*', new PositionNode(), new FloatNode( 3 ).setConst( true ) );
 
 				const pointCloud = new THREE.Points( geometryPoints, materialPoints );
 				pointCloud.position.set( 2, - 1, 0 );
@@ -141,7 +158,7 @@
 				geometryLine.setAttribute( 'color', geometryLine.getAttribute( 'position' ) );
 
 				const materialLine = new THREE.LineBasicMaterial();
-				materialLine.colorNode = new AttributeNode( 'vec3', 'color' );
+				materialLine.colorNode = new AttributeNode( 'color', 'vec3' );
 
 				const line = new THREE.Line( geometryLine, materialLine );
 				line.position.set( 2, 1, 0 );