瀏覽代碼

WebGPU & NodeMaterial: ComputeNode (#23905)

* add ComputeNode

* fix float function parameters, support to void function

* add StorageBufferNode

* add compute(), timer(), compute(), storage() and func() elements

* WebGPU: add StorageBufferNode and ComputeNode support and cleanup

* update webgpu_compute example

* cleanup

* fix ToneMappingNode in non-physical material

* fixes and cleanup

* fixes and ignore tonemapping in non physical material

* update snapshots
sunag 3 年之前
父節點
當前提交
f3e92d7172
共有 34 個文件被更改,包括 392 次插入247 次删除
  1. 9 1
      examples/jsm/nodes/Nodes.js
  2. 21 0
      examples/jsm/nodes/accessors/StorageBufferNode.js
  3. 21 18
      examples/jsm/nodes/core/NodeBuilder.js
  4. 47 0
      examples/jsm/nodes/gpgpu/ComputeNode.js
  5. 7 3
      examples/jsm/nodes/materials/MeshStandardNodeMaterial.js
  6. 1 7
      examples/jsm/nodes/materials/NodeMaterial.js
  7. 12 4
      examples/jsm/nodes/parsers/WGSLNodeFunction.js
  8. 12 0
      examples/jsm/nodes/shadernode/ShaderNodeElements.js
  9. 19 4
      examples/jsm/nodes/shadernode/ShaderNodeUtils.js
  10. 10 4
      examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js
  11. 7 6
      examples/jsm/renderers/webgpu/WebGPUBindings.js
  12. 43 0
      examples/jsm/renderers/webgpu/WebGPUBuffer.js
  13. 12 6
      examples/jsm/renderers/webgpu/WebGPUComputePipelines.js
  14. 2 1
      examples/jsm/renderers/webgpu/WebGPURenderPipelines.js
  15. 11 7
      examples/jsm/renderers/webgpu/WebGPURenderer.js
  16. 4 7
      examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js
  17. 4 31
      examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js
  18. 85 26
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  19. 0 20
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js
  20. 二進制
      examples/screenshots/webgpu_compute.jpg
  21. 二進制
      examples/screenshots/webgpu_depth_texture.jpg
  22. 二進制
      examples/screenshots/webgpu_instance_mesh.jpg
  23. 二進制
      examples/screenshots/webgpu_instance_uniform.jpg
  24. 二進制
      examples/screenshots/webgpu_lights_custom.jpg
  25. 二進制
      examples/screenshots/webgpu_lights_selective.jpg
  26. 二進制
      examples/screenshots/webgpu_materials.jpg
  27. 二進制
      examples/screenshots/webgpu_nodes_playground.jpg
  28. 二進制
      examples/screenshots/webgpu_rtt.jpg
  29. 二進制
      examples/screenshots/webgpu_sandbox.jpg
  30. 二進制
      examples/screenshots/webgpu_skinning.jpg
  31. 二進制
      examples/screenshots/webgpu_skinning_instancing.jpg
  32. 二進制
      examples/screenshots/webgpu_skinning_points.jpg
  33. 65 101
      examples/webgpu_compute.html
  34. 0 1
      examples/webgpu_nodes_playground.html

+ 9 - 1
examples/jsm/nodes/Nodes.js

@@ -44,6 +44,9 @@ import SkinningNode from './accessors/SkinningNode.js';
 import TextureNode from './accessors/TextureNode.js';
 import UVNode from './accessors/UVNode.js';
 
+// gpgpu
+import ComputeNode from './gpgpu/ComputeNode.js';
+
 // display
 import ColorSpaceNode from './display/ColorSpaceNode.js';
 import NormalMapNode from './display/NormalMapNode.js';
@@ -118,6 +121,9 @@ const nodeLib = {
 	VarNode,
 	VaryNode,
 
+	// compute
+	ComputeNode,
+
 	// accessors
 	BufferNode,
 	CameraNode,
@@ -210,6 +216,9 @@ export {
 	VarNode,
 	VaryNode,
 
+	// compute
+	ComputeNode,
+
 	// accessors
 	BufferNode,
 	CameraNode,
@@ -265,5 +274,4 @@ export {
 	NodeLoader,
 	NodeObjectLoader,
 	NodeMaterialLoader
-
 };

+ 21 - 0
examples/jsm/nodes/accessors/StorageBufferNode.js

@@ -0,0 +1,21 @@
+import BufferNode from './BufferNode.js';
+
+class StorageBufferNode extends BufferNode {
+
+	constructor( value, bufferType, bufferCount = 0 ) {
+
+		super( value, bufferType, bufferCount );
+
+	}
+
+	getInputType( /*builder*/ ) {
+
+		return 'storageBuffer';
+
+	}
+
+}
+
+StorageBufferNode.prototype.isStorageBufferNode = true;
+
+export default StorageBufferNode;

+ 21 - 18
examples/jsm/nodes/core/NodeBuilder.js

@@ -8,9 +8,18 @@ import { NodeUpdateType } from './constants.js';
 
 import { REVISION, LinearEncoding } from 'three';
 
-export const shaderStages = [ 'fragment', 'vertex' ];
+export const defaultShaderStages = [ 'fragment', 'vertex' ];
+export const shaderStages = [ ...defaultShaderStages, 'compute' ];
 export const vector = [ 'x', 'y', 'z', 'w' ];
 
+const typeFromLength = new Map();
+typeFromLength.set( 1, 'float' );
+typeFromLength.set( 2, 'vec2' );
+typeFromLength.set( 3, 'vec3' );
+typeFromLength.set( 4, 'vec4' );
+typeFromLength.set( 9, 'mat3' );
+typeFromLength.set( 16, 'mat4' );
+
 const toFloat = ( value ) => {
 
 	value = Number( value );
@@ -24,7 +33,7 @@ class NodeBuilder {
 	constructor( object, renderer, parser ) {
 
 		this.object = object;
-		this.material = object.material;
+		this.material = object.material || null;
 		this.renderer = renderer;
 		this.parser = parser;
 
@@ -34,14 +43,15 @@ class NodeBuilder {
 
 		this.vertexShader = null;
 		this.fragmentShader = null;
+		this.computeShader = null;
 
-		this.flowNodes = { vertex: [], fragment: [] };
-		this.flowCode = { vertex: '', fragment: '' };
-		this.uniforms = { vertex: [], fragment: [], index: 0 };
-		this.codes = { vertex: [], fragment: [] };
+		this.flowNodes = { vertex: [], fragment: [], compute: [] };
+		this.flowCode = { vertex: '', fragment: '', compute: [] };
+		this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 };
+		this.codes = { vertex: [], fragment: [], compute: [] };
 		this.attributes = [];
 		this.varys = [];
-		this.vars = { vertex: [], fragment: [] };
+		this.vars = { vertex: [], fragment: [], compute: [] };
 		this.flow = { code: '' };
 		this.stack = [];
 
@@ -330,16 +340,9 @@ class NodeBuilder {
 
 	}
 
-	getTypeFromLength( type ) {
-
-		if ( type === 1 ) return 'float';
-		if ( type === 2 ) return 'vec2';
-		if ( type === 3 ) return 'vec3';
-		if ( type === 4 ) return 'vec4';
-		if ( type === 9 ) return 'mat3';
-		if ( type === 16 ) return 'mat4';
+	getTypeFromLength( length ) {
 
-		return 0;
+		return typeFromLength.get( length );
 
 	}
 
@@ -369,7 +372,7 @@ class NodeBuilder {
 
 		if ( nodeData === undefined ) {
 
-			nodeData = { vertex: {}, fragment: {} };
+			nodeData = { vertex: {}, fragment: {}, compute: {} };
 
 			this.nodesData.set( node, nodeData );
 
@@ -592,7 +595,7 @@ class NodeBuilder {
 
 	getHash() {
 
-		return this.vertexShader + this.fragmentShader;
+		return this.vertexShader + this.fragmentShader + this.computeShader;
 
 	}
 

+ 47 - 0
examples/jsm/nodes/gpgpu/ComputeNode.js

@@ -0,0 +1,47 @@
+import Node from '../core/Node.js';
+import { NodeUpdateType } from '../core/constants.js';
+
+class ComputeNode extends Node {
+
+	constructor( dispatchCount, workgroupSize = [ 64 ] ) {
+
+		super( 'void' );
+
+		this.updateType = NodeUpdateType.Object;
+
+		this.dispatchCount = dispatchCount;
+		this.workgroupSize = workgroupSize;
+
+		this.computeNode = null;
+
+	}
+
+	update( { renderer } ) {
+
+		renderer.compute( this );
+
+	}
+
+	generate( builder ) {
+
+		const { shaderStage } = builder;
+
+		if ( shaderStage === 'compute' ) {
+
+			const snippet = this.computeNode.build( builder, 'void' );
+
+			if ( snippet !== '' ) {
+
+				builder.addFlowCode( snippet );
+
+			}
+
+		}
+
+	}
+
+}
+
+ComputeNode.prototype.isComputeNode = true;
+
+export default ComputeNode;

+ 7 - 3
examples/jsm/nodes/materials/MeshStandardNodeMaterial.js

@@ -1,7 +1,7 @@
 import NodeMaterial from './NodeMaterial.js';
 import {
 	float, vec3, vec4,
-	assign, label, mul, invert, mix,
+	context, assign, label, mul, invert, mix,
 	normalView,
 	materialRoughness, materialMetalness
 } from '../shadernode/ShaderNodeElements.js';
@@ -61,9 +61,13 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 	generateLight( builder, { diffuseColorNode, lightNode } ) {
 
-		const outgoingLightNode = super.generateLight( builder, { diffuseColorNode, lightNode, lightingModelNode: PhysicalLightingModel } );
+		let outgoingLightNode = super.generateLight( builder, { diffuseColorNode, lightNode, lightingModelNode: PhysicalLightingModel } );
 
-		// @TODO: add IBL code here
+		// TONE MAPPING
+
+		const renderer = builder.renderer;
+
+		if ( renderer.toneMappingNode ) outgoingLightNode = context( renderer.toneMappingNode, { color: outgoingLightNode } );
 
 		return outgoingLightNode;
 

+ 1 - 7
examples/jsm/nodes/materials/NodeMaterial.js

@@ -4,7 +4,7 @@ import ExpressionNode from '../core/ExpressionNode.js';
 import {
 	float, vec3, vec4,
 	assign, label, mul, add, bypass,
-	positionLocal, skinning, instance, modelViewProjection, context, lightContext, colorSpace,
+	positionLocal, skinning, instance, modelViewProjection, lightContext, colorSpace,
 	materialAlphaTest, materialColor, materialOpacity
 } from '../shadernode/ShaderNodeElements.js';
 
@@ -112,16 +112,10 @@ class NodeMaterial extends ShaderMaterial {
 
 	generateOutput( builder, { diffuseColorNode, outgoingLightNode } ) {
 
-		const { renderer } = builder;
-
 		// OUTPUT
 
 		let outputNode = vec4( outgoingLightNode, diffuseColorNode.a );
 
-		// TONE MAPPING
-
-		if ( renderer.toneMappingNode ) outputNode = context( renderer.toneMappingNode, { color: outputNode } );
-
 		// ENCODING
 
 		outputNode = colorSpace( outputNode, builder.renderer.outputEncoding );

+ 12 - 4
examples/jsm/nodes/parsers/WGSLNodeFunction.js

@@ -1,9 +1,13 @@
 import NodeFunction from '../core/NodeFunction.js';
 import NodeFunctionInput from '../core/NodeFunctionInput.js';
 
-const declarationRegexp = /^fn\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*\-\>\s*([a-z_0-9]+)?/i;
+const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+)?/i;
 const propertiesRegexp = /[a-z_0-9]+/ig;
 
+const wgslTypeLib = {
+	f32: 'float'
+};
+
 const parse = ( source ) => {
 
 	source = source.trim();
@@ -36,7 +40,9 @@ const parse = ( source ) => {
 			// default
 
 			const name = propsMatches[ i ++ ][ 0 ];
-			const type = propsMatches[ i ++ ][ 0 ];
+			let type = propsMatches[ i ++ ][ 0 ];
+
+			type = wgslTypeLib[ type ] || type;
 
 			// precision
 
@@ -54,7 +60,7 @@ const parse = ( source ) => {
 		const blockCode = source.substring( declaration[ 0 ].length );
 
 		const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
-		const type = declaration[ 3 ];
+		const type = declaration[ 3 ] || 'void';
 
 		return {
 			type,
@@ -87,7 +93,9 @@ class WGSLNodeFunction extends NodeFunction {
 
 	getCode( name = this.name ) {
 
-		return `fn ${ name } ( ${ this.inputsCode.trim() } ) -> ${ this.type }` + this.blockCode;
+		const type = this.type !== 'void' ? '-> ' + this.type : '';
+
+		return `fn ${ name } ( ${ this.inputsCode.trim() } ) ${ type }` + this.blockCode;
 
 	}
 

+ 12 - 0
examples/jsm/nodes/shadernode/ShaderNodeElements.js

@@ -6,9 +6,11 @@ import UniformNode from '../core/UniformNode.js';
 import BypassNode from '../core/BypassNode.js';
 import InstanceIndexNode from '../core/InstanceIndexNode.js';
 import ContextNode from '../core/ContextNode.js';
+import FunctionNode from '../core/FunctionNode.js';
 
 // accessor nodes
 import BufferNode from '../accessors/BufferNode.js';
+import StorageBufferNode from '../accessors/StorageBufferNode.js';
 import CameraNode from '../accessors/CameraNode.js';
 import MaterialNode from '../accessors/MaterialNode.js';
 import ModelNode from '../accessors/ModelNode.js';
@@ -20,6 +22,9 @@ import TextureNode from '../accessors/TextureNode.js';
 import UVNode from '../accessors/UVNode.js';
 import InstanceNode from '../accessors/InstanceNode.js';
 
+// gpgpu
+import ComputeNode from '../gpgpu/ComputeNode.js';
+
 // math nodes
 import OperatorNode from '../math/OperatorNode.js';
 import CondNode from '../math/CondNode.js';
@@ -29,6 +34,7 @@ import MathNode from '../math/MathNode.js';
 import ArrayElementNode from '../utils/ArrayElementNode.js';
 import ConvertNode from '../utils/ConvertNode.js';
 import JoinNode from '../utils/JoinNode.js';
+import TimerNode from '../utils/TimerNode.js';
 
 // other nodes
 import ColorSpaceNode from '../display/ColorSpaceNode.js';
@@ -109,9 +115,15 @@ export const join = ( ...params ) => nodeObject( new JoinNode( nodeArray( params
 export const uv = ( ...params ) => nodeObject( new UVNode( ...params ) );
 export const attribute = ( ...params ) => nodeObject( new AttributeNode( ...params ) );
 export const buffer = ( ...params ) => nodeObject( new BufferNode( ...params ) );
+export const storage = ( ...params ) => nodeObject( new StorageBufferNode( ...params ) );
 export const texture = ( ...params ) => nodeObject( new TextureNode( ...params ) );
 export const sampler = ( texture ) => nodeObject( new ConvertNode( texture.isNode === true ? texture : new TextureNode( texture ), 'sampler' ) );
 
+export const timer = ( ...params ) => nodeObject( new TimerNode( ...params ) );
+
+export const compute = ( ...params ) => nodeObject( new ComputeNode( ...params ) );
+export const func = ( ...params ) => nodeObject( new FunctionNode( ...params ) );
+
 export const cond = nodeProxy( CondNode );
 
 export const add = nodeProxy( OperatorNode, '+' );

+ 19 - 4
examples/jsm/nodes/shadernode/ShaderNodeUtils.js

@@ -141,13 +141,28 @@ const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null ) {
 
 export const ShaderNodeScript = function ( jsFunc ) {
 
-	return { call: ( inputs, builder ) => {
+	//@TODO: Move this to Node extended class
 
-		inputs = new ShaderNodeObjects( inputs );
+	const self =
+	{
+		build: ( builder ) => {
 
-		return new ShaderNodeObject( jsFunc( inputs, builder ) );
+			self.call( {}, builder );
 
-	} };
+			return '';
+
+		},
+
+		call: ( inputs, builder ) => {
+
+			inputs = new ShaderNodeObjects( inputs );
+
+			return new ShaderNodeObject( jsFunc( inputs, builder ) );
+
+		}
+	};
+
+	return self;
 
 };
 

+ 10 - 4
examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js

@@ -1,4 +1,4 @@
-import NodeBuilder, { shaderStages } from 'three-nodes/core/NodeBuilder.js';
+import NodeBuilder, { defaultShaderStages } from 'three-nodes/core/NodeBuilder.js';
 import NodeFrame from 'three-nodes/core/NodeFrame.js';
 import SlotNode from './SlotNode.js';
 import GLSLNodeParser from 'three-nodes/parsers/GLSLNodeParser.js';
@@ -85,6 +85,12 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 		}
 
+		if ( material.isMeshStandardNodeMaterial !== true ) {
+
+			this.replaceCode( 'fragment', getIncludeSnippet( 'tonemapping_fragment' ), '' );
+
+		}
+
 		// parse inputs
 
 		if ( material.colorNode && material.colorNode.isNode ) {
@@ -329,7 +335,7 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 		const shaderData = {};
 
-		for ( const shaderStage of shaderStages ) {
+		for ( const shaderStage of defaultShaderStages ) {
 
 			const uniforms = this.getUniforms( shaderStage );
 			const attributes = this.getAttributes( shaderStage );
@@ -515,7 +521,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 
 		}
 
-		for ( const shaderStage of shaderStages ) {
+		for ( const shaderStage of defaultShaderStages ) {
 
 			this.addCodeAfterSnippet(
 				shaderStage,
@@ -529,7 +535,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 
 	_addUniforms() {
 
-		for ( const shaderStage of shaderStages ) {
+		for ( const shaderStage of defaultShaderStages ) {
 
 			// uniforms
 

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

@@ -30,9 +30,9 @@ class WebGPUBindings {
 
 			// setup (static) binding layout and (dynamic) binding group
 
-			const renderPipeline = this.renderPipelines.get( object );
+			const pipeline = object.isNode ? this.computePipelines.get( object ) : this.renderPipelines.get( object ).pipeline;
 
-			const bindLayout = renderPipeline.pipeline.getBindGroupLayout( 0 );
+			const bindLayout = pipeline.getBindGroupLayout( 0 );
 			const bindGroup = this._createBindGroup( bindings, bindLayout );
 
 			data = {
@@ -108,12 +108,12 @@ class WebGPUBindings {
 			if ( binding.isUniformBuffer ) {
 
 				const buffer = binding.getBuffer();
-				const bufferGPU = binding.bufferGPU;
-
 				const needsBufferWrite = binding.update();
 
 				if ( needsBufferWrite === true ) {
 
+					const bufferGPU = binding.bufferGPU;
+
 					this.device.queue.writeBuffer( bufferGPU, 0, buffer, 0 );
 
 				}
@@ -121,6 +121,7 @@ class WebGPUBindings {
 			} else if ( binding.isStorageBuffer ) {
 
 				const attribute = binding.attribute;
+
 				this.attributes.update( attribute, false, binding.usage );
 
 			} else if ( binding.isSampler ) {
@@ -243,8 +244,8 @@ class WebGPUBindings {
 		}
 
 		return this.device.createBindGroup( {
-			layout: layout,
-			entries: entries
+			layout,
+			entries
 		} );
 
 	}

+ 43 - 0
examples/jsm/renderers/webgpu/WebGPUBuffer.js

@@ -0,0 +1,43 @@
+import WebGPUBinding from './WebGPUBinding.js';
+import { getFloatLength } from './WebGPUBufferUtils.js';
+
+class WebGPUBuffer extends WebGPUBinding {
+
+	constructor( name, type, buffer = null ) {
+
+		super( name );
+
+		this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT;
+		this.type = type;
+		this.visibility = GPUShaderStage.VERTEX;
+
+		this.usage = GPUBufferUsage.COPY_DST;
+
+		this.buffer = buffer;
+		this.bufferGPU = null; // set by the renderer
+
+	}
+
+	getByteLength() {
+
+		return getFloatLength( this.buffer.byteLength );
+
+	}
+
+	getBuffer() {
+
+		return this.buffer;
+
+	}
+
+	update() {
+
+		return true;
+
+	}
+
+}
+
+WebGPUBuffer.prototype.isBuffer = true;
+
+export default WebGPUBuffer;

+ 12 - 6
examples/jsm/renderers/webgpu/WebGPUComputePipelines.js

@@ -2,9 +2,10 @@ import WebGPUProgrammableStage from './WebGPUProgrammableStage.js';
 
 class WebGPUComputePipelines {
 
-	constructor( device ) {
+	constructor( device, nodes ) {
 
 		this.device = device;
+		this.nodes = nodes;
 
 		this.pipelines = new WeakMap();
 		this.stages = {
@@ -13,9 +14,9 @@ class WebGPUComputePipelines {
 
 	}
 
-	get( param ) {
+	get( computeNode ) {
 
-		let pipeline = this.pipelines.get( param );
+		let pipeline = this.pipelines.get( computeNode );
 
 		// @TODO: Reuse compute pipeline if possible, introduce WebGPUComputePipeline
 
@@ -23,8 +24,13 @@ class WebGPUComputePipelines {
 
 			const device = this.device;
 
+			// get shader
+
+			const nodeBuilder = this.nodes.get( computeNode );
+			const computeShader = nodeBuilder.computeShader;
+
 			const shader = {
-				computeShader: param.shader
+				computeShader
 			};
 
 			// programmable stage
@@ -33,7 +39,7 @@ class WebGPUComputePipelines {
 
 			if ( stageCompute === undefined ) {
 
- 				stageCompute = new WebGPUProgrammableStage( device, shader.computeShader, 'compute' );
+ 				stageCompute = new WebGPUProgrammableStage( device, computeShader, 'compute' );
 
 				this.stages.compute.set( shader, stageCompute );
 
@@ -43,7 +49,7 @@ class WebGPUComputePipelines {
 				compute: stageCompute.stage
 			} );
 
-			this.pipelines.set( param, pipeline );
+			this.pipelines.set( computeNode, pipeline );
 
 		}
 

+ 2 - 1
examples/jsm/renderers/webgpu/WebGPURenderPipelines.js

@@ -24,7 +24,6 @@ class WebGPURenderPipelines {
 	get( object ) {
 
 		const device = this.device;
-		const material = object.material;
 
 		const cache = this._getCache( object );
 
@@ -32,6 +31,8 @@ class WebGPURenderPipelines {
 
 		if ( this._needsUpdate( object, cache ) ) {
 
+			const material = object.material;
+
 			// release previous cache
 
 			if ( cache.currentPipeline !== undefined ) {

+ 11 - 7
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -186,7 +186,7 @@ class WebGPURenderer {
 		this._textures = new WebGPUTextures( device, this._properties, this._info );
 		this._objects = new WebGPUObjects( this._geometries, this._info );
 		this._nodes = new WebGPUNodes( this, this._properties );
-		this._computePipelines = new WebGPUComputePipelines( device );
+		this._computePipelines = new WebGPUComputePipelines( device, this._nodes );
 		this._renderPipelines = new WebGPURenderPipelines( this, device, parameters.sampleCount, this._nodes );
 		this._bindings = this._renderPipelines.bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes, this._nodes );
 		this._renderLists = new WebGPURenderLists();
@@ -611,26 +611,30 @@ class WebGPURenderer {
 
 	}
 
-	compute( computeParams ) {
+	compute( ...computeNodes ) {
 
 		const device = this._device;
 		const cmdEncoder = device.createCommandEncoder( {} );
 		const passEncoder = cmdEncoder.beginComputePass();
 
-		for ( const param of computeParams ) {
+		for ( const computeNode of computeNodes ) {
 
 			// pipeline
 
-			const pipeline = this._computePipelines.get( param );
+			const pipeline = this._computePipelines.get( computeNode );
 			passEncoder.setPipeline( pipeline );
 
+			// node
+
+			//this._nodes.update( computeNode );
+
 			// bind group
 
-			const bindGroup = this._bindings.getForCompute( param ).group;
-			this._bindings.update( param );
+			const bindGroup = this._bindings.get( computeNode ).group;
+			this._bindings.update( computeNode );
 			passEncoder.setBindGroup( 0, bindGroup );
 
-			passEncoder.dispatch( param.num );
+			passEncoder.dispatch( computeNode.dispatchCount );
 
 		}
 

+ 4 - 7
examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js

@@ -1,18 +1,15 @@
-import WebGPUBinding from './WebGPUBinding.js';
+import WebGPUBuffer from './WebGPUBuffer.js';
 import { GPUBindingType } from './constants.js';
 
-class WebGPUStorageBuffer extends WebGPUBinding {
+class WebGPUStorageBuffer extends WebGPUBuffer {
 
 	constructor( name, attribute ) {
 
-		super( name );
+		super( name, GPUBindingType.StorageBuffer, attribute.array );
 
-		this.type = GPUBindingType.StorageBuffer;
-
-		this.usage = GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST;
+		this.usage |= GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE;
 
 		this.attribute = attribute;
-		this.bufferGPU = null; // set by the renderer
 
 	}
 

+ 4 - 31
examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js

@@ -1,40 +1,13 @@
-import WebGPUBinding from './WebGPUBinding.js';
-import { getFloatLength } from './WebGPUBufferUtils.js';
-
+import WebGPUBuffer from './WebGPUBuffer.js';
 import { GPUBindingType } from './constants.js';
 
-class WebGPUUniformBuffer extends WebGPUBinding {
+class WebGPUUniformBuffer extends WebGPUBuffer {
 
 	constructor( name, buffer = null ) {
 
-		super( name );
-
-		this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT;
-		this.type = GPUBindingType.UniformBuffer;
-		this.visibility = GPUShaderStage.VERTEX;
-
-		this.usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
-
-		this.buffer = buffer;
-		this.bufferGPU = null; // set by the renderer
-
-	}
-
-	getByteLength() {
-
-		return getFloatLength( this.buffer.byteLength );
-
-	}
-
-	getBuffer() {
-
-		return this.buffer;
-
-	}
-
-	update() {
+		super( name, GPUBindingType.UniformBuffer, buffer );
 
-		return true;
+		this.usage |= GPUBufferUsage.UNIFORM;
 
 	}
 

+ 85 - 26
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -1,4 +1,4 @@
-import WebGPUNodeUniformsGroup from './WebGPUNodeUniformsGroup.js';
+import WebGPUUniformsGroup from '../WebGPUUniformsGroup.js';
 import {
 	FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
 	ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
@@ -7,6 +7,7 @@ import WebGPUNodeSampler from './WebGPUNodeSampler.js';
 import { WebGPUNodeSampledTexture, WebGPUNodeSampledCubeTexture } from './WebGPUNodeSampledTexture.js';
 
 import WebGPUUniformBuffer from '../WebGPUUniformBuffer.js';
+import WebGPUStorageBuffer from '../WebGPUStorageBuffer.js';
 import { getVectorLength, getStrideLength } from '../WebGPUBufferUtils.js';
 
 import NodeBuilder from 'three-nodes/core/NodeBuilder.js';
@@ -16,6 +17,12 @@ import CodeNode from 'three-nodes/core/CodeNode.js';
 
 import { NodeMaterial } from 'three-nodes/materials/Materials.js';
 
+const gpuShaderStageLib = {
+	'vertex': GPUShaderStage.VERTEX,
+	'fragment': GPUShaderStage.FRAGMENT,
+	'compute': GPUShaderStage.COMPUTE
+};
+
 const supports = {
 	instance: true
 };
@@ -93,8 +100,8 @@ class WebGPUNodeBuilder extends NodeBuilder {
 		this.lightNode = null;
 		this.fogNode = null;
 
-		this.bindings = { vertex: [], fragment: [] };
-		this.bindingsOffset = { vertex: 0, fragment: 0 };
+		this.bindings = { vertex: [], fragment: [], compute: [] };
+		this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 };
 
 		this.uniformsGroup = {};
 
@@ -104,7 +111,17 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	build() {
 
-		NodeMaterial.fromMaterial( this.material ).build( this );
+		const { object, material } = this;
+
+		if ( material !== null ) {
+
+			NodeMaterial.fromMaterial( material ).build( this );
+
+		} else {
+
+			this.addFlow( 'compute', object );
+
+		}
 
 		return super.build();
 
@@ -201,7 +218,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 				return name;
 
-			} else if ( type === 'buffer' ) {
+			} else if ( type === 'buffer' || type === 'storageBuffer' ) {
 
 				return `NodeBuffer_${node.node.id}.${name}`;
 
@@ -221,7 +238,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		const bindings = this.bindings;
 
-		return [ ...bindings.vertex, ...bindings.fragment ];
+		return this.material !== null ? [ ...bindings.vertex, ...bindings.fragment ] : bindings.compute;
 
 	}
 
@@ -270,9 +287,11 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 				}
 
-			} else if ( type === 'buffer' ) {
+			} else if ( type === 'buffer' || type === 'storageBuffer' ) {
 
-				const buffer = new WebGPUUniformBuffer( 'NodeBuffer_' + node.id, node.value );
+				const bufferClass = type === 'storageBuffer' ? WebGPUStorageBuffer : WebGPUUniformBuffer;
+				const buffer = new bufferClass( 'NodeBuffer_' + node.id, node.value );
+				buffer.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 				// add first textures in sequence and group for last
 				const lastBinding = bindings[ bindings.length - 1 ];
@@ -288,7 +307,8 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 				if ( uniformsGroup === undefined ) {
 
-					uniformsGroup = new WebGPUNodeUniformsGroup( shaderStage );
+					uniformsGroup = new WebGPUUniformsGroup( 'nodeUniforms' );
+					uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 					this.uniformsGroup[ shaderStage ] = uniformsGroup;
 
@@ -348,11 +368,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		this.builtins.add( 'instance_index' );
 
-		if ( shaderStage === 'vertex' ) {
-
-			return 'instanceIndex';
-
-		}
+		return 'instanceIndex';
 
 	}
 
@@ -360,9 +376,13 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		const snippets = [];
 
-		if ( shaderStage === 'vertex' ) {
+		if ( shaderStage === 'vertex' || shaderStage === 'compute' ) {
+
+			if ( shaderStage === 'compute' ) {
 
-			if ( this.builtins.has( 'instance_index' ) ) {
+				snippets.push( `@builtin( global_invocation_id ) id : vec3<u32>` );
+
+			} else if ( this.builtins.has( 'instance_index' ) ) {
 
 				snippets.push( `@builtin( instance_index ) instanceIndex : u32` );
 
@@ -477,15 +497,17 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 				bindingSnippets.push( `@group( 0 ) @binding( ${index ++} ) var ${uniform.name} : texture_cube<f32>;` );
 
-			} else if ( uniform.type === 'buffer' ) {
+			} else if ( uniform.type === 'buffer' || uniform.type === 'storageBuffer' ) {
 
 				const bufferNode = uniform.node;
 				const bufferType = this.getType( bufferNode.bufferType );
 				const bufferCount = bufferNode.bufferCount;
 
-				const bufferSnippet = `\t${uniform.name} : array< ${bufferType}, ${bufferCount} >\n`;
+				const bufferCountSnippet = bufferCount > 0 ? ', ' + bufferCount : '';
+				const bufferSnippet = `\t${uniform.name} : array< ${bufferType}${bufferCountSnippet} >\n`;
+				const bufferAccessMode = bufferNode.isStorageBufferNode ? 'storage,read_write' : 'uniform';
 
-				bufferSnippets.push( this._getWGSLUniforms( 'NodeBuffer_' + bufferNode.id, bufferSnippet, index ++ ) );
+				bufferSnippets.push( this._getWGSLStructBinding( 'NodeBuffer_' + bufferNode.id, bufferSnippet, bufferAccessMode, index ++ ) );
 
 			} else {
 
@@ -512,7 +534,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		if ( groupSnippets.length > 0 ) {
 
-			code += this._getWGSLUniforms( 'NodeUniforms', groupSnippets.join( ',\n' ), index ++ );
+			code += this._getWGSLStructBinding( 'NodeUniforms', groupSnippets.join( ',\n' ), 'uniform', index ++ );
 
 		}
 
@@ -522,7 +544,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	buildCode() {
 
-		const shadersData = { fragment: {}, vertex: {} };
+		const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} };
 
 		for ( const shaderStage in shadersData ) {
 
@@ -548,7 +570,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 				flow += `${ flowSlotData.code }\n\t`;
 
-				if ( node === mainNode ) {
+				if ( node === mainNode && shaderStage !== 'compute' ) {
 
 					flow += '// FLOW RESULT\n\t';
 
@@ -579,8 +601,16 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		}
 
-		this.vertexShader = this._getWGSLVertexCode( shadersData.vertex );
-		this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment );
+		if ( this.material !== null ) {
+
+			this.vertexShader = this._getWGSLVertexCode( shadersData.vertex );
+			this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment );
+
+		} else {
+
+			this.computeShader = this._getWGSLComputeCode( shadersData.compute, ( this.object.workgroupSize || [ 64 ] ).join( ', ' ) );
+
+		}
 
 	}
 
@@ -679,6 +709,35 @@ fn main( ${shaderData.varys} ) -> @location( 0 ) vec4<f32> {
 	// flow
 	${shaderData.flow}
 
+}
+`;
+
+	}
+
+	_getWGSLComputeCode( shaderData, workgroupSize ) {
+
+		return `${ this.getSignature() }
+// system
+var<private> instanceIndex : u32;
+
+// uniforms
+${shaderData.uniforms}
+
+// codes
+${shaderData.codes}
+
+@stage( compute ) @workgroup_size( ${workgroupSize} )
+fn main( ${shaderData.attributes} ) {
+
+	// system
+	instanceIndex = id.x * 3u;
+
+	// vars
+	${shaderData.vars}
+
+	// flow
+	${shaderData.flow}
+
 }
 `;
 
@@ -693,14 +752,14 @@ ${vars}
 
 	}
 
-	_getWGSLUniforms( name, vars, binding = 0, group = 0 ) {
+	_getWGSLStructBinding( name, vars, access, binding = 0, group = 0 ) {
 
 		const structName = name + 'Struct';
 		const structSnippet = this._getWGSLStruct( structName, vars );
 
 		return `${structSnippet}
 @binding( ${binding} ) @group( ${group} )
-var<uniform> ${name} : ${structName};`;
+var<${access}> ${name} : ${structName};`;
 
 	}
 

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

@@ -1,20 +0,0 @@
-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 );
-
-	}
-
-}
-
-export default WebGPUNodeUniformsGroup;

二進制
examples/screenshots/webgpu_compute.jpg


二進制
examples/screenshots/webgpu_depth_texture.jpg


二進制
examples/screenshots/webgpu_instance_mesh.jpg


二進制
examples/screenshots/webgpu_instance_uniform.jpg


二進制
examples/screenshots/webgpu_lights_custom.jpg


二進制
examples/screenshots/webgpu_lights_selective.jpg


二進制
examples/screenshots/webgpu_materials.jpg


二進制
examples/screenshots/webgpu_nodes_playground.jpg


二進制
examples/screenshots/webgpu_rtt.jpg


二進制
examples/screenshots/webgpu_sandbox.jpg


二進制
examples/screenshots/webgpu_skinning.jpg


二進制
examples/screenshots/webgpu_skinning_instancing.jpg


二進制
examples/screenshots/webgpu_skinning_points.jpg


+ 65 - 101
examples/webgpu_compute.html

@@ -28,23 +28,23 @@
 			import * as THREE from 'three';
 			import * as Nodes from 'three-nodes/Nodes.js';
 
+			import {
+				compute,
+				color, add, uniform, element, storage, func,
+				assign, float, mul,
+				positionLocal, instanceIndex
+			} from 'three-nodes/Nodes.js';
+
 			import { GUI } from './jsm/libs/lil-gui.module.min.js';
 
 			import WebGPU from './jsm/capabilities/WebGPU.js';
 			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
 
-			import WebGPUStorageBuffer from './jsm/renderers/webgpu/WebGPUStorageBuffer.js';
-			import WebGPUUniformBuffer from './jsm/renderers/webgpu/WebGPUUniformBuffer.js';
-			import * as WebGPUBufferUtils from './jsm/renderers/webgpu/WebGPUBufferUtils.js';
-			import WebGPUUniformsGroup from './jsm/renderers/webgpu/WebGPUUniformsGroup.js';
-			import { Vector2Uniform } from './jsm/renderers/webgpu/WebGPUUniform.js';
-
 			let camera, scene, renderer;
-			let pointer;
-			let scaleUniformBuffer;
-			const scaleVector = new THREE.Vector3( 1, 1, 1 );
+			let computeNode;
 
-			const computeParams = [];
+			const pointer = new THREE.Vector2( - 10.0, - 10.0 ); // Out of bounds first
+			const scaleVector = new THREE.Vector2( 1, 1 );
 
 			init().then( animate ).catch( error );
 
@@ -62,10 +62,11 @@
 				camera.position.z = 1;
 
 				scene = new THREE.Scene();
-				scene.background = new THREE.Color( 0x000000 );
+
+				// initialize particles
 
 				const particleNum = 65000; // 16-bit limit
-				const particleSize = 4; // 16-byte stride align
+				const particleSize = 3; // vec3
 
 				const particleArray = new Float32Array( particleNum * particleSize );
 				const velocityArray = new Float32Array( particleNum * particleSize );
@@ -74,81 +75,25 @@
 
 					const r = Math.random() * 0.01 + 0.0005;
 					const degree = Math.random() * 360;
+
 					velocityArray[ i + 0 ] = r * Math.sin( degree * Math.PI / 180 );
 					velocityArray[ i + 1 ] = r * Math.cos( degree * Math.PI / 180 );
 
 				}
 
-				const particleBuffer = new WebGPUStorageBuffer( 'particle', new THREE.BufferAttribute( particleArray, particleSize ) );
-				const velocityBuffer = new WebGPUStorageBuffer( 'velocity', new THREE.BufferAttribute( velocityArray, particleSize ) );
-
-				const scaleUniformLength = WebGPUBufferUtils.getVectorLength( 2, 3 ); // two vector3 for array
-
-				scaleUniformBuffer = new WebGPUUniformBuffer( 'scaleUniform', new Float32Array( scaleUniformLength ) );
-
-				pointer = new THREE.Vector2( - 10.0, - 10.0 ); // Out of bounds first
-
-				const pointerGroup = new WebGPUUniformsGroup( 'mouseUniforms' ).addUniform(
-					new Vector2Uniform( 'pointer', pointer )
-				);
-
-				// Object keys need follow the binding shader sequence
-
-				const computeBindings = [
-					particleBuffer,
-					velocityBuffer,
-					scaleUniformBuffer,
-					pointerGroup
-				];
-
-				const computeShader = `
-
-					//
-					// Buffer
-					//
-
-					struct Particle {
-						value : array< vec4<f32> >
-					};
-					@binding( 0 ) @group( 0 )
-					var<storage,read_write> particle : Particle;
-
-					struct Velocity {
-						value : array< vec4<f32> >
-					};
-					@binding( 1 ) @group( 0 )
-					var<storage,read_write> velocity : Velocity;
-
-					//
-					// Uniforms
-					//
-
-					struct Scale {
-						value : array< vec3<f32>, 2 >
-					};
-					@binding( 2 ) @group( 0 )
-					var<uniform> scaleUniform : Scale;
-
-					struct MouseUniforms {
-						pointer : vec2<f32>
-					};
-					@binding( 3 ) @group( 0 )
-					var<uniform> mouseUniforms : MouseUniforms;
+				// create buffers
 
-					@stage( compute ) @workgroup_size( 64 )
-					fn main( @builtin(global_invocation_id) id : vec3<u32> ) {
+				const particleBuffer = new THREE.BufferAttribute( particleArray, particleSize );
+				const velocityBuffer = new THREE.BufferAttribute( velocityArray, particleSize );
 
-						// get particle index
+				const particleBufferNode = storage( particleBuffer, 'vec3' );
+				const velocityBufferNode = storage( velocityBuffer, 'vec3' );
 
-						let index : u32 = id.x * 3u;
+				// create wgsl function
 
-						// update speed
+				const WGSLFnNode = func( `( pointer:vec2<f32>, limit:vec2<f32> ) {
 
-						var position : vec4<f32> = particle.value[ index ] + velocity.value[ index ];
-
-						// update limit
-
-						let limit : vec2<f32> = scaleUniform.value[ 0 ].xy;
+						var position = particle + velocity;
 
 						if ( abs( position.x ) >= limit.x ) {
 
@@ -162,7 +107,7 @@
 
 							}
 
-							velocity.value[ index ].x = - velocity.value[ index ].x;
+							velocity.x = - velocity.x;
 
 						}
 
@@ -178,17 +123,15 @@
 
 							}
 
-							velocity.value[ index ].y = - velocity.value[ index ].y;
+							velocity.y = - velocity.y ;
 
 						}
 
-						// update mouse
-
-						let POINTER_SIZE : f32 = .1;
+						let POINTER_SIZE = .1;
 
-						let dx : f32 = mouseUniforms.pointer.x - position.x;
-						let dy : f32 = mouseUniforms.pointer.y - position.y;
-						let distanceFromPointer : f32 = sqrt( dx * dx + dy * dy );
+						let dx = pointer.x - position.x;
+						let dy = pointer.y - position.y;
+						let distanceFromPointer = sqrt( dx * dx + dy * dy );
 
 						if ( distanceFromPointer <= POINTER_SIZE ) {
 
@@ -198,28 +141,51 @@
 
 						}
 
-						// update buffer
-
-						particle.value[ index ] = position;
+						particle = position;
 
 					}
+				` );
+
+				// define particle and velocity keywords in wgsl function
+				// it's used in case of needed change a global variable like this storageBuffer
+
+				const particleNode = element( particleBufferNode, instanceIndex );
+				const velocityNode = element( velocityBufferNode, instanceIndex );
+
+				WGSLFnNode.keywords[ 'particle' ] = particleNode;
+				WGSLFnNode.keywords[ 'velocity' ] = velocityNode;
+
+				// compute
+
+				computeNode = compute( particleNum );
 
-				`;
+				// Example 1: Calling a WGSL function
 
-				computeParams.push( {
-					num: particleNum,
-					shader: computeShader,
-					bindings: computeBindings
+				computeNode.computeNode = WGSLFnNode.call( {
+					pointer: uniform( pointer ),
+					limit: uniform( scaleVector )
 				} );
 
-				// Use a compute shader to animate the point cloud's vertex data.
+				// Example 2: Creating single storage assign
 
-				const pointsGeometry = new THREE.BufferGeometry().setAttribute(
-					'position', particleBuffer.attribute
-				);
+				//computeNode.computeNode = assign( particleNode, add( particleNode, velocityNode ) );
+
+				// Example 3: Creating multiples storage assign
+
+				/*computeNode.computeNode = new Nodes.ShaderNode( ( {}, builder ) => {
+
+					assign( particleNode, add( particleNode, velocityNode ) ).build( builder );
+					assign( velocityNode, mul( velocityNode, float( 0.99 ) ) ).build( builder );
+
+				} );/**/
+
+				// use a compute shader to animate the point cloud's vertex data.
+
+				const pointsGeometry = new THREE.BufferGeometry();
+				pointsGeometry.setAttribute( 'position', particleBuffer );
 
 				const pointsMaterial = new Nodes.PointsNodeMaterial();
-				pointsMaterial.colorNode = new Nodes.OperatorNode( '+', new Nodes.PositionNode(), new Nodes.UniformNode( new THREE.Color( 0xFFFFFF ) ) );
+				pointsMaterial.colorNode = add( positionLocal, color( 0xFFFFFF ) );
 
 				const mesh = new THREE.Points( pointsGeometry, pointsMaterial );
 				scene.add( mesh );
@@ -270,11 +236,9 @@
 
 				requestAnimationFrame( animate );
 
-				renderer.compute( computeParams );
+				renderer.compute( computeNode );
 				renderer.render( scene, camera );
 
-				scaleVector.toArray( scaleUniformBuffer.buffer, 0 );
-
 			}
 
 			function error( error ) {

+ 0 - 1
examples/webgpu_nodes_playground.html

@@ -73,7 +73,6 @@
 			let stats;
 			let camera, scene, renderer;
 			let model;
-			let nodeLights;
 
 			init().then( animate ).catch( error => console.error( error ) );