Browse Source

WebGPU: WGSL FunctionNode support (#22715)

* fix get context keywords

* prevent initial spaces

* add WGSLNodeFunction

* add ShaderNode and WGSLNode examples
sunag 3 years ago
parent
commit
415b99fa4c

+ 1 - 1
examples/jsm/renderers/nodes/core/CodeNode.js

@@ -32,7 +32,7 @@ class CodeNode extends Node {
 
 		if ( this.useKeywords === true ) {
 
-			const contextKeywords = builder.getContextValue( 'keywords' );
+			const contextKeywords = builder.context.keywords;
 
 			if ( contextKeywords !== undefined ) {
 

+ 2 - 0
examples/jsm/renderers/nodes/parsers/GLSLNodeFunction.js

@@ -8,6 +8,8 @@ const pragmaMain = '#pragma main';
 
 const parse = ( source ) => {
 
+	source = source.trim();
+
 	const pragmaMainIndex = source.indexOf( pragmaMain );
 
 	const mainCode = pragmaMainIndex !== - 1 ? source.substr( pragmaMainIndex + pragmaMain.length ) : source;

+ 89 - 0
examples/jsm/renderers/nodes/parsers/WGSLNodeFunction.js

@@ -0,0 +1,89 @@
+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 propertiesRegexp = /[a-z_0-9]+/ig;
+
+const parse = ( source ) => {
+
+	source = source.trim();
+
+	const declaration = source.match( declarationRegexp );
+
+	if ( declaration !== null && declaration.length === 4 ) {
+
+		// tokenizer
+
+		const inputsCode = declaration[ 2 ];
+		const propsMatches = [];
+
+		let nameMatch = null;
+
+		while ( ( nameMatch = propertiesRegexp.exec( inputsCode ) ) !== null ) {
+
+			propsMatches.push( nameMatch );
+
+		}
+
+		// parser
+
+		const inputs = [];
+
+		let i = 0;
+
+		while ( i < propsMatches.length ) {
+
+			const name = propsMatches[ i ++ ][ 0 ];
+			const type = propsMatches[ i ++ ][ 0 ];
+
+			propsMatches[ i ++ ][ 0 ]; // precision
+
+			inputs.push( new NodeFunctionInput( type, name ) );
+
+		}
+
+		//
+
+		const blockCode = source.substring( declaration[ 0 ].length );
+
+		const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
+		const type = declaration[ 3 ];
+
+		return {
+			type,
+			inputs,
+			name,
+			inputsCode,
+			blockCode
+		};
+
+	} else {
+
+		throw new Error( 'FunctionNode: Function is not a WGSL code.' );
+
+	}
+
+};
+
+class WGSLNodeFunction extends NodeFunction {
+
+	constructor( source ) {
+
+		const { type, inputs, name, inputsCode, blockCode } = parse( source );
+
+		super( type, inputs, name );
+
+		this.inputsCode = inputsCode;
+		this.blockCode = blockCode;
+
+	}
+
+	getCode( name = this.name ) {
+
+		return `fn ${ name } ( ${ this.inputsCode.trim() } ) -> ${ this.type }` + this.blockCode;
+
+	}
+
+}
+
+export default WGSLNodeFunction;

+ 14 - 0
examples/jsm/renderers/nodes/parsers/WGSLNodeParser.js

@@ -0,0 +1,14 @@
+import NodeParser from '../core/NodeParser.js';
+import WGSLNodeFunction from './WGSLNodeFunction.js';
+
+class WGSLNodeParser extends NodeParser {
+
+	parseFunction( source ) {
+
+		return new WGSLNodeFunction( source );
+
+	}
+
+}
+
+export default WGSLNodeParser;

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

@@ -21,6 +21,7 @@ import ModelViewProjectionNode from '../../nodes/accessors/ModelViewProjectionNo
 import SkinningNode from '../../nodes/accessors/SkinningNode.js';
 import LightContextNode from '../../nodes/lights/LightContextNode.js';
 import OperatorNode from '../../nodes/math/OperatorNode.js';
+import WGSLNodeParser from '../../nodes/parsers/WGSLNodeParser.js';
 
 const wgslTypeLib = {
 	float: 'f32',
@@ -68,7 +69,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	constructor( object, renderer, lightNode = null ) {
 
-		super( object, renderer );
+		super( object, renderer, new WGSLNodeParser() );
 
 		this.lightNode = lightNode;
 

+ 40 - 0
examples/webgpu_materials.html

@@ -30,6 +30,7 @@
 			import { TeapotGeometry } from './jsm/geometries/TeapotGeometry.js';
 
 			import * as Nodes from './jsm/renderers/nodes/Nodes.js';
+			import { ShaderNode, vec3, dot } from './jsm/renderers/nodes/ShaderNode.js';
 
 			import Stats from './jsm/libs/stats.module.js';
 
@@ -80,6 +81,10 @@
 
 				let material;
 
+				//
+				//	BASIC
+				//
+
 				// PositionNode.LOCAL
 				material = new Nodes.MeshBasicNodeMaterial();
 				material.colorNode = new Nodes.PositionNode( Nodes.PositionNode.LOCAL );
@@ -107,6 +112,7 @@
 
 				// Opacity
 				material = new Nodes.MeshBasicNodeMaterial();
+				material.colorNode = new Nodes.ColorNode( new THREE.Color( 0x0099FF ) );
 				material.opacityNode = new Nodes.TextureNode( texture );
 				material.transparent = true;
 				materials.push( material );
@@ -118,7 +124,41 @@
 				material.alphaTestNode = new Nodes.FloatNode( 0.5 );
 				materials.push( material );
 
+				//
+				//	ADVANCED
+				//
+
+				// Custom ShaderNode ( desaturate filter )
+
+				const desaturateShaderNode = new Nodes.ShaderNode( ( input ) => {
+
+					return dot( vec3( 0.299, 0.587, 0.114 ), input.color.xyz );
+
+				} );
+
+				material = new Nodes.MeshBasicNodeMaterial();
+				material.colorNode = desaturateShaderNode( { color: new Nodes.TextureNode( texture ) } );
+				materials.push( material );
+
+				// Custom WGSL ( desaturate filter )
+
+				const desaturateWGSLNode = new Nodes.FunctionNode( `
+					fn desaturate( color:vec3<f32> ) -> vec3<f32> {
+
+						let lum = vec3<f32>( 0.299, 0.587, 0.114 );
+
+						return vec3<f32>( dot( lum, color ) );
+
+					}
+				` );
+
+				material = new Nodes.MeshBasicNodeMaterial();
+				material.colorNode = desaturateWGSLNode.call( { color: new Nodes.TextureNode( texture ) } );
+				materials.push( material );
+
+				//
 				// Geometry
+				//
 
 				const geometry = new TeapotGeometry( 50, 18 );