瀏覽代碼

NodeMaterial: Basic BSDFs of MeshStandardMaterial and NodeBuilder simplification (#22398)

* new NodeMaterial system updates

* update custom lighting model example

* fix surface normal input
sunag 3 年之前
父節點
當前提交
8f046622a4
共有 36 個文件被更改,包括 1368 次插入352 次删除
  1. 9 5
      examples/jsm/renderers/nodes/accessors/MaterialNode.js
  2. 2 2
      examples/jsm/renderers/nodes/accessors/Object3DNode.js
  3. 3 0
      examples/jsm/renderers/nodes/consts/MathConsts.js
  4. 3 5
      examples/jsm/renderers/nodes/core/AttributeNode.js
  5. 1 1
      examples/jsm/renderers/nodes/core/CodeNode.js
  6. 10 8
      examples/jsm/renderers/nodes/core/ContextNode.js
  7. 24 0
      examples/jsm/renderers/nodes/core/ExpressionNode.js
  8. 2 2
      examples/jsm/renderers/nodes/core/FunctionCallNode.js
  9. 5 13
      examples/jsm/renderers/nodes/core/Node.js
  10. 174 64
      examples/jsm/renderers/nodes/core/NodeBuilder.js
  11. 18 1
      examples/jsm/renderers/nodes/core/NodeKeywords.js
  12. 1 2
      examples/jsm/renderers/nodes/core/NodeVar.js
  13. 1 2
      examples/jsm/renderers/nodes/core/NodeVary.js
  14. 82 0
      examples/jsm/renderers/nodes/core/StructNode.js
  15. 75 0
      examples/jsm/renderers/nodes/core/StructVarNode.js
  16. 36 0
      examples/jsm/renderers/nodes/core/TempNode.js
  17. 13 10
      examples/jsm/renderers/nodes/core/VarNode.js
  18. 3 5
      examples/jsm/renderers/nodes/core/VaryNode.js
  19. 124 0
      examples/jsm/renderers/nodes/display/ColorSpaceNode.js
  20. 99 0
      examples/jsm/renderers/nodes/display/NormalMapNode.js
  21. 139 34
      examples/jsm/renderers/nodes/functions/BSDFs.js
  22. 99 0
      examples/jsm/renderers/nodes/functions/EncodingFunctions.js
  23. 2 2
      examples/jsm/renderers/nodes/functions/MathFunctions.js
  24. 41 6
      examples/jsm/renderers/nodes/inputs/TextureNode.js
  25. 23 24
      examples/jsm/renderers/nodes/lights/LightContextNode.js
  26. 11 12
      examples/jsm/renderers/nodes/lights/LightNode.js
  27. 45 0
      examples/jsm/renderers/nodes/lights/PhysicalMaterialContextNode.js
  28. 118 31
      examples/jsm/renderers/nodes/math/MathNode.js
  29. 4 10
      examples/jsm/renderers/nodes/math/OperatorNode.js
  30. 91 23
      examples/jsm/renderers/webgpu/nodes/ShaderLib.js
  31. 71 68
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  32. 4 4
      examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js
  33. 二進制
      examples/screenshots/webgpu_lights_custom.jpg
  34. 二進制
      examples/screenshots/webgpu_lights_selective.jpg
  35. 4 13
      examples/webgpu_lights_custom.html
  36. 31 5
      examples/webgpu_lights_selective.html

+ 9 - 5
examples/jsm/renderers/nodes/accessors/MaterialNode.js

@@ -9,6 +9,8 @@ class MaterialNode extends Node {
 	static OPACITY = 'opacity';
 	static SPECULAR = 'specular';
 	static SHININESS = 'shininess';
+	static ROUGHNESS = 'roughness';
+	static METALNESS = 'metalness';
 
 	constructor( scope = MaterialNode.COLOR ) {
 
@@ -21,7 +23,7 @@ class MaterialNode extends Node {
 	getType( builder ) {
 
 		const scope = this.scope;
-		const material = builder.getContextParameter( 'material' );
+		const material = builder.getContextValue( 'material' );
 
 		if ( scope === MaterialNode.COLOR ) {
 
@@ -35,7 +37,7 @@ class MaterialNode extends Node {
 
 			return 'vec3';
 
-		} else if ( scope === MaterialNode.SHININESS ) {
+		} else if ( scope === MaterialNode.SHININESS || scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS ) {
 
 			return 'float';
 
@@ -45,7 +47,7 @@ class MaterialNode extends Node {
 
 	generate( builder, output ) {
 
-		const material = builder.getContextParameter( 'material' );
+		const material = builder.getContextValue( 'material' );
 		const scope = this.scope;
 
 		let node = null;
@@ -96,9 +98,11 @@ class MaterialNode extends Node {
 
 			}
 
-		} else if ( scope === MaterialNode.SHININESS ) {
+		} else {
 
-			node = new MaterialReferenceNode( 'shininess', 'float' );
+			const type = this.getType( builder );
+
+			node = new MaterialReferenceNode( scope, type );
 
 		}
 

+ 2 - 2
examples/jsm/renderers/nodes/accessors/Object3DNode.js

@@ -92,7 +92,7 @@ class Object3DNode extends Node {
 
 				if ( inputNode === null || inputNode.isMatrix4Node !== true ) {
 
-					inputNode = new Matrix4Node( null );
+					inputNode = new Matrix4Node( /*null*/ );
 
 				}
 
@@ -100,7 +100,7 @@ class Object3DNode extends Node {
 
 				if ( inputNode === null || inputNode.isMatrix3Node !== true ) {
 
-					inputNode = new Matrix3Node( null );
+					inputNode = new Matrix3Node( /*null*/ );
 
 				}
 

+ 3 - 0
examples/jsm/renderers/nodes/consts/MathConsts.js

@@ -2,3 +2,6 @@ import ConstNode from '../core/ConstNode.js';
 
 export const PI = new ConstNode( '3.141592653589793', 'float', 'PI' );
 export const RECIPROCAL_PI = new ConstNode( '0.3183098861837907', 'float', 'RECIPROCAL_PI' );
+export const EPSILON = new ConstNode( '1e-6', 'float', 'EPSILON' );
+
+export const DEFAULT_SPECULAR_COEFFICIENT = new ConstNode( '0.04', 'float', 'DEFAULT_SPECULAR_COEFFICIENT' );

+ 3 - 5
examples/jsm/renderers/nodes/core/AttributeNode.js

@@ -1,4 +1,5 @@
 import Node from './Node.js';
+import VaryNode from './VaryNode.js';
 
 class AttributeNode extends Node {
 
@@ -43,16 +44,13 @@ class AttributeNode extends Node {
 
 			if ( nodeVary === undefined ) {
 
-				nodeVary = builder.getVaryFromNode( this, attribute.type );
-				nodeVary.snippet = attributeName;
+				nodeVary = new VaryNode( this );
 
 				nodeData.nodeVary = nodeVary;
 
 			}
 
-			const varyName = builder.getPropertyName( nodeVary );
-
-			return builder.format( varyName, attribute.type, output );
+			return nodeVary.build( builder, output );
 
 		}
 

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

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

+ 10 - 8
examples/jsm/renderers/nodes/core/ContextNode.js

@@ -2,29 +2,29 @@ import Node from './Node.js';
 
 class ContextNode extends Node {
 
-	constructor( node, parameters = {} ) {
+	constructor( node, type, context = {} ) {
 
-		super();
+		super( type );
 
 		this.node = node;
 
-		this.parameters = parameters;
+		this.context = context;
 
 		Object.defineProperty( this, 'isContextNode', { value: true } );
 
 	}
 
-	setParameter( name, value ) {
+	setContextValue( name, value ) {
 
-		this.parameters[ name ] = value;
+		this.context[ name ] = value;
 
 		return this;
 
 	}
 
-	getParameter( name ) {
+	getContextValue( name ) {
 
-		return this.parameters[ name ];
+		return this.context[ name ];
 
 	}
 
@@ -36,9 +36,11 @@ class ContextNode extends Node {
 
 	generate( builder, output ) {
 
+		const type = this.getType( builder );
+	
 		const previousContext = builder.getContext();
 
-		builder.setContext( Object.assign( {}, builder.context, this.parameters ) );
+		builder.setContext( Object.assign( {}, builder.context, this.context ) );
 
 		const snippet = this.node.build( builder, output );
 

+ 24 - 0
examples/jsm/renderers/nodes/core/ExpressionNode.js

@@ -0,0 +1,24 @@
+import Node from './Node.js';
+
+class ExpressionNode extends Node {
+
+	constructor( snipped = '', type = null ) {
+
+		super( type );
+
+		this.snipped = snipped;
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+		const snipped = this.snipped;
+
+		return builder.format( `( ${ snipped } )`, type, output );
+
+	}
+
+}
+
+export default ExpressionNode;

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

@@ -1,6 +1,6 @@
-import Node from './Node.js';
+import TempNode from './TempNode.js';
 
-class FunctionCallNode extends Node {
+class FunctionCallNode extends TempNode {
 
 	constructor( functionNode = null, parameters = {} ) {
 

+ 5 - 13
examples/jsm/renderers/nodes/core/Node.js

@@ -22,29 +22,21 @@ class Node {
 
 	}
 
-	update( /*frame*/ ) {
+	getTypeLength( builder ) {
 
-		console.warn( 'Abstract function.' );
+		return builder.getTypeLength( this.getType( builder ) );
 
 	}
 
-	generate( /*builder, output*/ ) {
+	update( /*frame*/ ) {
 
 		console.warn( 'Abstract function.' );
 
 	}
 
-	buildStage( builder, shaderStage, output = null ) {
-
-		const oldShaderStage = builder.shaderStage;
-
-		builder.shaderStage = shaderStage;
-
-		const snippet = this.build( builder, output );
-
-		builder.shaderStage = oldShaderStage;
+	generate( /*builder, output*/ ) {
 
-		return snippet;
+		console.warn( 'Abstract function.' );
 
 	}
 

+ 174 - 64
examples/jsm/renderers/nodes/core/NodeBuilder.js

@@ -21,11 +21,11 @@ class NodeBuilder {
 
 		this.slots = { vertex: [], fragment: [] };
 		this.defines = { vertex: {}, fragment: {} };
-		this.uniforms = { vertex: [], fragment: [] };
-		this.nodeCodes = { vertex: [], fragment: [] };
-		this.vars = { vertex: [], fragment: [] };
+		this.uniforms = { vertex: [], fragment: [], index: 0 };
+		this.codes = { vertex: [], fragment: [] };
 		this.attributes = [];
 		this.varys = [];
+		this.vars = { vertex: [], fragment: [] };
 		this.flow = { code: '' };
 
 		this.context = {
@@ -36,6 +36,7 @@ class NodeBuilder {
 		this.nodesData = new WeakMap();
 
 		this.shaderStage = null;
+		this.slot = null;
 
 	}
 
@@ -81,13 +82,25 @@ class NodeBuilder {
 
 	}
 
-	getContextParameter( name ) {
+	getContextValue( name ) {
 
 		return this.context[ name ];
 
 	}
 
-	getTexture( /* textureProperty, uvSnippet */ ) {
+	getTexture( /* textureProperty, uvSnippet, biasSnippet = null */ ) {
+
+		console.warn( 'Abstract function.' );
+
+	}
+
+	getCubeTexture( /* textureProperty, uvSnippet, biasSnippet = null */ ) {
+
+		console.warn( 'Abstract function.' );
+
+	}
+
+	getPMREM( texture ) {
 
 		console.warn( 'Abstract function.' );
 
@@ -155,6 +168,28 @@ class NodeBuilder {
 
 	}
 
+	getTextureEncodingFromMap( map ) {
+
+		let encoding;
+
+		if ( map && map.isTexture ) {
+
+			encoding = map.encoding;
+
+		} else if ( map && map.isWebGLRenderTarget ) {
+
+			encoding = map.texture.encoding;
+
+		} else {
+
+			encoding = LinearEncoding;
+
+		}
+
+		return encoding;
+
+	}
+
 	getVectorType( type ) {
 
 		if ( type === 'color' ) return 'vec3';
@@ -177,18 +212,24 @@ class NodeBuilder {
 
 	getTypeLength( type ) {
 
-		type = this.getVectorType( type );
+		const vecType = this.getVectorType( type );
 
-		if ( type === 'float' ) return 1;
-		if ( type === 'vec2' ) return 2;
-		if ( type === 'vec3' ) return 3;
-		if ( type === 'vec4' ) return 4;
+		if ( vecType === 'float' ) return 1;
+		if ( vecType === 'vec2' ) return 2;
+		if ( vecType === 'vec3' ) return 3;
+		if ( vecType === 'vec4' ) return 4;
 
 		return 0;
 
 	}
 
-	getDataFromNode( node, shaderStage = null ) {
+	getVectorFromMatrix( type ) {
+
+		return 'vec' + type.substr( 3 );
+
+	}
+
+	getDataFromNode( node, shaderStage = this.shaderStage ) {
 
 		let nodeData = this.nodesData.get( node );
 
@@ -200,7 +241,7 @@ class NodeBuilder {
 
 		}
 
-		return shaderStage ? nodeData[ shaderStage ] : nodeData;
+		return shaderStage !== null ? nodeData[ shaderStage ] : nodeData;
 
 	}
 
@@ -212,12 +253,11 @@ class NodeBuilder {
 
 		if ( nodeUniform === undefined ) {
 
-			const uniforms = this.uniforms[ shaderStage ];
-			const index = uniforms.length;
+			const index = this.uniforms.index ++;
 
 			nodeUniform = new NodeUniform( 'nodeUniform' + index, type, node );
 
-			uniforms.push( nodeUniform );
+			this.uniforms[ shaderStage ].push( nodeUniform );
 
 			nodeData.uniform = nodeUniform;
 
@@ -252,7 +292,7 @@ class NodeBuilder {
 
 	getVaryFromNode( node, type ) {
 
-		const nodeData = this.getDataFromNode( node );
+		const nodeData = this.getDataFromNode( node, null );
 
 		let nodeVary = nodeData.vary;
 
@@ -281,12 +321,12 @@ class NodeBuilder {
 
 		if ( nodeCode === undefined ) {
 
-			const nodeCodes = this.nodeCodes[ shaderStage ];
-			const index = nodeCodes.length;
+			const codes = this.codes[ shaderStage ];
+			const index = codes.length;
 
 			nodeCode = new NodeCode( 'nodeCode' + index, type );
 
-			nodeCodes.push( nodeCode );
+			codes.push( nodeCode );
 
 			nodeData.code = nodeCode;
 
@@ -296,31 +336,37 @@ class NodeBuilder {
 
 	}
 
-	/*
-	analyzeNode( node ) {
+	addFlowCode( code ) {
+
+		if ( ! /;\s*$/.test( code ) ) {
+
+			code += ';';
+
+		}
 
+		this.flow.code += code + ' ';
 
 	}
-	*/
 
-	addFlowCode( code ) {
+	flowSlot( slot, shaderStage = this.shaderStage ) {
 
-		if ( ! /;\s*$/.test( code ) ) {
+		this.slot = slot;
 
-			code += '; ';
+		const flowData = this.flowNode( slot.node, slot.output );
 
-		}
+		this.define( shaderStage, `NODE_CODE_${slot.name}`, flowData.code );
+		this.define( shaderStage, `NODE_${slot.name}`, flowData.result );
 
-		this.flow.code += code;
+		this.slot = null;
 
 	}
 
-	flowNode( node, output ) {
+	flowNode( node, output = null ) {
 
 		const previousFlow = this.flow;
 
 		const flow = {
-			code: previousFlow.code,
+			code: '',
 		};
 
 		this.flow = flow;
@@ -333,9 +379,33 @@ class NodeBuilder {
 
 	}
 
-	_buildDefines( shader ) {
+	flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) {
 
-		const defines = this.defines[ shader ];
+		const previousShaderStage = this.shaderStage;
+
+		this.setShaderStage( shaderStage );
+
+		const flowData = this.flowNode( node, output );
+
+		if ( propertyName !== null ) {
+
+			flowData.code += `${propertyName} = ${flowData.result}; `;
+
+		}
+
+		const shaderStageCode = this.defines[ shaderStage ][ 'NODE_CODE' ] + flowData.code;
+
+		this.define( shaderStage, 'NODE_CODE', shaderStageCode );
+
+		this.setShaderStage( previousShaderStage );
+
+		return flowData;
+
+	}
+
+	getDefines( shaderStage ) {
+
+		const defines = this.defines[ shaderStage ];
 
 		let code = '';
 
@@ -349,55 +419,65 @@ class NodeBuilder {
 
 	}
 
-	getAttributesBodySnippet( /*shaderStage*/ ) {
+	getAttributes( shaderStage ) {
 
-		console.warn( 'Abstract function.' );
+		let snippet = '';
 
-	}
+		if ( shaderStage === 'vertex' ) {
 
-	getAttributesHeaderSnippet( /*shaderStage*/ ) {
+			const attributes = this.attributes;
 
-		console.warn( 'Abstract function.' );
+			for ( let index = 0; index < attributes.length; index ++ ) {
 
-	}
+				const attribute = attributes[ index ];
 
-	getVarysHeaderSnippet( /*shaderStage*/ ) {
+				snippet += `layout(location = ${index}) in ${attribute.type} ${attribute.name}; `;
 
-		console.warn( 'Abstract function.' );
+			}
+
+		}
+
+		return snippet;
 
 	}
 
-	getVarysBodySnippet( /*shaderStage*/ ) {
+	getVarys( /*shaderStage*/ ) {
 
 		console.warn( 'Abstract function.' );
 
 	}
 
-	getVarsHeaderSnippet( /*shaderStage*/ ) {
+	getVars( shaderStage ) {
 
-		console.warn( 'Abstract function.' );
+		let snippet = '';
 
-	}
+		const vars = this.vars[ shaderStage ];
 
-	getVarsBodySnippet( /*shaderStage*/ ) {
+		for ( let index = 0; index < vars.length; index ++ ) {
 
-		console.warn( 'Abstract function.' );
+			const variable = vars[ index ];
+
+			snippet += `${variable.type} ${variable.name}; `;
+
+		}
+
+		return snippet;
 
 	}
 
-	getUniformsHeaderSnippet( /*shaderStage*/ ) {
+	getUniforms( /*shaderStage*/ ) {
 
 		console.warn( 'Abstract function.' );
 
 	}
 
-	getUniformsHeaderCodes( shaderStage ) {
+	getCodes( shaderStage ) {
 
-		const nodeCodes = this.nodeCodes[ shaderStage ];
+		const codes = this.codes[ shaderStage ];
 
 		let code = '';
 
-		for ( const nodeCode of nodeCodes ) {
+		for ( const nodeCode of codes ) {
 
 			code += nodeCode.code + '\n';
 
@@ -413,6 +493,18 @@ class NodeBuilder {
 
 	}
 
+	getShaderStage() {
+
+		return this.shaderStage;
+
+	}
+
+	setShaderStage( shaderStage ) {
+
+		this.shaderStage = shaderStage;
+
+	}
+
 	build() {
 
 		const shaderStages = [ 'vertex', 'fragment' ];
@@ -420,38 +512,56 @@ class NodeBuilder {
 
 		for ( const shaderStage of shaderStages ) {
 
-			this.shaderStage = shaderStage;
+			this.setShaderStage( shaderStage );
+
+			this.define( shaderStage, 'NODE_CODE', '' );
 
 			const slots = this.slots[ shaderStage ];
 
 			for ( const slot of slots ) {
 
-				const flowData = this.flowNode( slot.node, slot.output );
-
-				this.define( shaderStage, `NODE_CODE_${slot.name}`, flowData.code );
-				this.define( shaderStage, `NODE_${slot.name}`, flowData.result );
+				this.flowSlot( slot, shaderStage );
 
 			}
 
 		}
 
-		this.shaderStage = null;
+		this.setShaderStage( null );
 
 		for ( const 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_HEADER_VARYS', this.getVarysHeaderSnippet( shaderStage ) );
+			const defines = this.getDefines( shaderStage );
+			const uniforms = this.getUniforms( shaderStage );
+			const attributes = this.getAttributes( shaderStage );
+			const varys = this.getVarys( shaderStage );
+			const vars = this.getVars( shaderStage );
+			const codes = this.getCodes( shaderStage );
+
+			shaderData[ shaderStage ] = `
+				// <node_builder>
+
+				#define NODE_MATERIAL
+
+				// defines
+				${defines}
+
+				// uniforms
+				${uniforms}
+
+				// attributes
+				${attributes}
 
-			this.define( shaderStage, 'NODE_BODY_VARYS', this.getVarysBodySnippet( shaderStage ) );
-			this.define( shaderStage, 'NODE_BODY_VARS', this.getVarsBodySnippet( shaderStage ) );
+				// varys
+				${varys}
 
-			let headerCode = '';
+				// vars
+				${vars}
 
-			headerCode += this.getVarsHeaderSnippet( shaderStage ) + '\n';
-			headerCode += this.getUniformsHeaderCodes( shaderStage );
+				// codes
+				${codes}
 
-			shaderData[ shaderStage ] = this._buildDefines( shaderStage ) + '\n' + headerCode;
+				// </node_builder>
+				`;
 
 		}
 

+ 18 - 1
examples/jsm/renderers/nodes/core/NodeKeywords.js

@@ -3,13 +3,14 @@ import PropertyNode from './PropertyNode.js';
 import PositionNode from '../accessors/PositionNode.js';
 import NormalNode from '../accessors/NormalNode.js';
 
-import { PI, RECIPROCAL_PI } from '../consts/MathConsts.js';
+import { PI, RECIPROCAL_PI, EPSILON } from '../consts/MathConsts.js';
 import { saturateMacro, whiteComplementMacro } from '../functions/MathFunctions.js';
 
 class NodeKeywords {
 
 	static PI = 'PI';
 	static RECIPROCAL_PI = 'RECIPROCAL_PI';
+	static EPSILON = 'EPSILON';
 
 	static Saturate = 'saturate';
 	static WhiteComplement = 'whiteComplement';
@@ -31,6 +32,11 @@ class NodeKeywords {
 
 	static MaterialDiffuseColor = 'MaterialDiffuseColor';
 
+	// STANDARD
+	static MaterialRoughness = 'MaterialRoughness';
+	static MaterialMetalness = 'MaterialMetalness';
+
+	// PHONG
 	static MaterialSpecularShininess = 'MaterialSpecularShininess';
 	static MaterialSpecularColor = 'MaterialSpecularColor';
 
@@ -40,6 +46,7 @@ class NodeKeywords {
 			// consts
 			NodeKeywords.PI,
 			NodeKeywords.RECIPROCAL_PI,
+			NodeKeywords.EPSILON,
 			// variadic macros
 			NodeKeywords.Saturate,
 			NodeKeywords.WhiteComplement,
@@ -53,6 +60,8 @@ class NodeKeywords {
 			NodeKeywords.NormalView,
 			// vars -> float
 			NodeKeywords.MaterialSpecularShininess,
+			NodeKeywords.MaterialRoughness,
+			NodeKeywords.MaterialMetalness,
 			// vars -> vec3
 			NodeKeywords.Irradiance,
 			NodeKeywords.ReflectedLightIndirectDiffuse,
@@ -88,6 +97,12 @@ class NodeKeywords {
 
 					break;
 
+				case NodeKeywords.EPSILON:
+
+					node = EPSILON;
+
+					break;
+
 				case NodeKeywords.Saturate:
 
 					node = saturateMacro;
@@ -144,6 +159,8 @@ class NodeKeywords {
 
 				// floats properties
 				case NodeKeywords.MaterialSpecularShininess:
+				case NodeKeywords.MaterialRoughness:
+				case NodeKeywords.MaterialMetalness:
 
 					node = new PropertyNode( name, 'float' );
 

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

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

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

@@ -1,10 +1,9 @@
 class NodeVary {
 
-	constructor( name, type, snippet = '' ) {
+	constructor( name, type ) {
 
 		this.name = name;
 		this.type = type;
-		this.snippet = snippet;
 
 	}
 

+ 82 - 0
examples/jsm/renderers/nodes/core/StructNode.js

@@ -0,0 +1,82 @@
+import CodeNode from './CodeNode.js';
+import StructVarNode from './StructVarNode.js';
+
+class StructNode extends CodeNode {
+
+	constructor( inputs = {}, name = '' ) {
+
+		super();
+
+		this.inputs = inputs;
+		this.name = name;
+
+	}
+
+	getType( builder ) {
+
+		if ( this.name !== '' ) {
+
+			return this.name;
+
+		} else {
+
+			const codeNode = builder.getCodeFromNode( this, 'code' );
+
+			return codeNode.name;
+
+		}
+
+	}
+
+	create( inputs = {} ) {
+
+		return new StructVarNode( this, inputs );
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+		const inputs = this.inputs;
+
+		const shaderStage = builder.getShaderStage();
+
+		let code = `struct ${type} {\n`;
+
+		for ( const inputName in inputs ) {
+
+			const inputType = inputs[ inputName ];
+
+			code += `\t${inputType} ${inputName};\n`;
+
+		}
+
+		code += `};`;
+
+		this.code = code;
+
+		super.generate( builder, output );
+
+		if ( output === 'var' ) {
+
+			const nodeData = builder.getDataFromNode( this );
+
+			if ( nodeData.index === undefined ) {
+
+				nodeData.index = 0;
+
+			}
+
+			return `structVar${nodeData.index ++}`;
+
+		} else {
+
+			return code;
+
+		}
+
+	}
+
+}
+
+export default StructNode;

+ 75 - 0
examples/jsm/renderers/nodes/core/StructVarNode.js

@@ -0,0 +1,75 @@
+import Node from './Node.js';
+import FloatNode from '../inputs/FloatNode.js';
+
+const zeroValue = new FloatNode( 0 ).setConst( true );
+
+class StructVarNode extends Node {
+
+	constructor( struct, inputs = {} ) {
+
+		super();
+
+		this.struct = struct;
+		this.inputs = inputs;
+
+	}
+
+	getType( builder ) {
+
+		return this.struct.getType( builder );
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		const struct = this.struct;
+
+		const inputs = this.inputs;
+		const structInputs = this.struct.inputs;
+
+		const nodeData = builder.getDataFromNode( this );
+
+		let property = nodeData.property;
+
+		if ( property === undefined ) {
+
+			property = struct.build( builder, 'var' );
+
+			const inputsSnippets = [];
+
+			for ( const inputName in structInputs ) {
+
+				const inputType = structInputs[ inputName ];
+				const input = inputs[ inputName ];
+
+				let inputSnippet = null;
+
+				if ( input !== undefined ) {
+
+					inputSnippet = input.build( builder, inputType );
+
+				} else {
+
+					inputSnippet = zeroValue.build( builder, inputType );
+
+				}
+
+				inputsSnippets.push( inputSnippet );
+
+			}
+
+			builder.addFlowCode( `${type} ${property} = ${type}( ${inputsSnippets.join( ', ' )} )` );
+
+			nodeData.property = property;
+
+		}
+
+		return builder.format( property, type, output );
+
+	}
+
+}
+
+export default StructVarNode;

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

@@ -0,0 +1,36 @@
+import Node from './Node.js';
+
+class TempNode extends Node {
+
+	constructor( type ) {
+
+		super( type );
+
+	}
+
+	build( builder, output ) {
+
+		const type = builder.getVectorType( this.getType( builder ) );
+
+		if ( type !== 'void' ) {
+
+			const nodeVar = builder.getVarFromNode( this, type );
+			const propertyName = builder.getPropertyName( nodeVar );
+
+			const snippet = super.build( builder, type );
+
+			builder.addFlowCode( `${propertyName} = ${snippet}` );
+
+			return builder.format( propertyName, type, output );
+
+		} else {
+
+			return super.build( builder, type );
+
+		}
+
+	}
+
+}
+
+export default TempNode;

+ 13 - 10
examples/jsm/renderers/nodes/core/VarNode.js

@@ -2,9 +2,9 @@ import Node from './Node.js';
 
 class VarNode extends Node {
 
-	constructor( value, name = '' ) {
+	constructor( value, name = '', type = null ) {
 
-		super();
+		super( type );
 
 		this.value = value;
 		this.name = name;
@@ -13,26 +13,29 @@ class VarNode extends Node {
 
 	getType( builder ) {
 
-		return this.value.getType( builder );
+		return this.type || this.value.getType( builder );
 
 	}
 
 	generate( builder, output ) {
 
-		const snippet = this.value.build( builder );
+		const type = builder.getVectorType( this.type || this.getType( builder ) );
+		const name = this.name;
+		const value = this.value;
 
-		const type = this.getType( builder );
+		const nodeVar = builder.getVarFromNode( this, type );
 
-		const nodeVary = builder.getVarFromNode( this, type );
-		nodeVary.snippet = snippet;
+		const snippet = value.build( builder, type );
 
-		if ( this.name !== '' ) {
+		if ( name !== '' ) {
 
-			nodeVary.name = this.name;
+			nodeVar.name = name;
 
 		}
 
-		const propertyName = builder.getPropertyName( nodeVary );
+		const propertyName = builder.getPropertyName( nodeVar );
+
+		builder.addFlowCode( `${propertyName} = ${snippet}` );
 
 		return builder.format( propertyName, type, output );
 

+ 3 - 5
examples/jsm/renderers/nodes/core/VaryNode.js

@@ -23,14 +23,12 @@ class VaryNode extends Node {
 
 		const type = this.getType( builder );
 
-		// force nodeVary.snippet work in vertex stage
-		const snippet = this.value.buildStage( builder, NodeShaderStage.Vertex, type );
-
 		const nodeVary = builder.getVaryFromNode( this, type );
-		nodeVary.snippet = snippet;
-
 		const propertyName = builder.getPropertyName( nodeVary );
 
+		// force nodeVary.snippet work in vertex stage
+		const flowData = builder.flowNodeFromShaderStage( NodeShaderStage.Vertex, this.value, type, propertyName );
+
 		return builder.format( propertyName, type, output );
 
 	}

+ 124 - 0
examples/jsm/renderers/nodes/display/ColorSpaceNode.js

@@ -0,0 +1,124 @@
+import TempNode from '../core/Node.js';
+import CodeNode from '../core/CodeNode.js';
+import * as EncodingFunctions from '../functions/EncodingFunctions.js';
+
+import { LinearEncoding, sRGBEncoding, RGBEEncoding, RGBM7Encoding, RGBM16Encoding,
+	RGBDEncoding, GammaEncoding, LogLuvEncoding } from 'three';
+
+function getEncodingComponents ( encoding ) {
+
+	switch ( encoding ) {
+
+		case LinearEncoding:
+			return [ 'Linear' ];
+		case sRGBEncoding:
+			return [ 'sRGB' ];
+		case RGBEEncoding:
+			return [ 'RGBE' ];
+		case RGBM7Encoding:
+			return [ 'RGBM', new FloatNode( 7.0 ).setConst( true ) ];
+		case RGBM16Encoding:
+			return [ 'RGBM', new FloatNode( 16.0 ).setConst( true ) ];
+		case RGBDEncoding:
+			return [ 'RGBD', new FloatNode( 256.0 ).setConst( true ) ];
+		case GammaEncoding:
+			return [ 'Gamma', new CodeNode( 'float( GAMMA_FACTOR )' ) ];
+		case LogLuvEncoding:
+			return [ 'LogLuv' ];
+
+	}
+
+}
+
+class ColorSpaceNode extends TempNode {
+
+	static LINEAR_TO_LINEAR = 'LinearToLinear';
+
+	static GAMMA_TO_LINEAR = 'GammaToLinear';
+	static LINEAR_TO_GAMMA = 'LinearToGamma';
+
+	static SRGB_TO_LINEAR = 'sRGBToLinear';
+	static LINEAR_TO_SRGB = 'LinearTosRGB';
+
+	static RGBE_TO_LINEAR = 'RGBEToLinear';
+	static LINEAR_TO_RGBE = 'LinearToRGBE';
+
+	static RGBM_TO_LINEAR = 'RGBMToLinear';
+	static LINEAR_TO_RGBM = 'LinearToRGBM';
+
+	static RGBD_TO_LINEAR = 'RGBDToLinear';
+	static LINEAR_TO_RGBD = 'LinearToRGBD';
+
+	static LINEAR_TO_LOG_LUV = 'LinearToLogLuv';
+	static LOG_LUV_TO_LINEAR = 'LogLuvToLinear';
+
+	constructor( method, input ) {
+
+		super( 'vec4' );
+
+		this.method = method;
+
+		this.input = input;
+		this.factor = null;
+
+	}
+
+	fromEncoding( encoding ) {
+
+		const components = getEncodingComponents( encoding );
+
+		this.method = 'LinearTo' + components[ 0 ];
+		this.factor = components[ 1 ];
+
+		return this;
+
+	}
+
+	fromDecoding( encoding ) {
+
+		const components = getEncodingComponents( encoding );
+
+		this.method = components[ 0 ] + 'ToLinear';
+		this.factor = components[ 1 ];
+
+		return this;
+
+	}
+
+	generate( builder, output ) {
+
+		const method = this.method;
+		const input = this.input;
+
+		if ( method !== ColorSpaceNode.LINEAR_TO_LINEAR ) {
+
+			const nodeData = builder.getDataFromNode( this );
+
+			let encodingFunctionCallNode = nodeData.encodingFunctionCallNode;
+
+			if (encodingFunctionCallNode === undefined) {
+
+				const encodingFunctionNode = EncodingFunctions[ method ];
+
+				encodingFunctionCallNode = encodingFunctionNode.call( {
+					value: input,
+					factor: this.factor
+				} );
+
+				nodeData.encodingFunctionCallNode = encodingFunctionCallNode;
+
+			}
+
+			return encodingFunctionCallNode.build( builder, output );
+
+		} else {
+
+			return input.build( builder, output );
+
+		}
+
+	}
+
+}
+
+export default ColorSpaceNode;

+ 99 - 0
examples/jsm/renderers/nodes/display/NormalMapNode.js

@@ -0,0 +1,99 @@
+import PositionNode from '../accessors/PositionNode.js';
+import NormalNode from '../accessors/NormalNode.js';
+import UVNode from '../accessors/UVNode.js';
+import MathNode from '../math/MathNode.js';
+import OperatorNode from '../math/OperatorNode.js';
+import FloatNode from '../inputs/FloatNode.js';
+import TempNode from '../core/TempNode.js';
+import FunctionNode from '../core/FunctionNode.js';
+import ModelNode from '../accessors/ModelNode.js';
+
+import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three';
+
+// Normal Mapping Without Precomputed Tangents
+// http://www.thetenthplanet.de/archives/1180
+
+export const perturbNormal2Arb = new FunctionNode( `
+vec3 ( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection, const in vec2 uv ) {
+
+	// Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988
+
+	vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );
+	vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );
+	vec2 st0 = dFdx( uv.st );
+	vec2 st1 = dFdy( uv.st );
+
+	vec3 N = surf_norm; // normalized
+
+	vec3 q1perp = cross( q1, N );
+	vec3 q0perp = cross( N, q0 );
+
+	vec3 T = q1perp * st0.x + q0perp * st1.x;
+	vec3 B = q1perp * st0.y + q0perp * st1.y;
+
+	float det = max( dot( T, T ), dot( B, B ) );
+	float scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );
+
+	return normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );
+
+}` );
+
+class NormalMapNode extends TempNode {
+
+	constructor( value ) {
+
+		super( 'vec3' );
+
+		this.value = value;
+
+		this.normalMapType = TangentSpaceNormalMap;
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+		const normalMapType = this.normalMapType;
+
+		const nodeData = builder.getDataFromNode( this );
+
+		const normalOP = new OperatorNode( '*', this.value, new FloatNode( 2.0 ).setConst( true ) );
+		const normalMap = new OperatorNode( '-', normalOP, new FloatNode( 1.0 ).setConst( true ) );
+
+		if ( normalMapType === ObjectSpaceNormalMap ) {
+
+			const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), normalMap );
+
+			const normal = new MathNode( MathNode.NORMALIZE, vertexNormalNode );
+
+			return normal.build( builder, output );
+
+		} else if ( normalMapType === TangentSpaceNormalMap ) {
+
+			let perturbNormal2ArbCall = nodeData.perturbNormal2ArbCall;
+
+			if (perturbNormal2ArbCall === undefined) {
+
+				perturbNormal2ArbCall = perturbNormal2Arb.call( {
+					eye_pos: new PositionNode( PositionNode.VIEW ),
+					surf_norm: new NormalNode( NormalNode.VIEW ),
+					mapN: normalMap,
+					faceDirection: new FloatNode( 1.0 ).setConst( true ),
+					uv: new UVNode()
+				} );
+
+				nodeData.perturbNormal2ArbCall = perturbNormal2ArbCall;
+
+			}
+
+			const snippet = perturbNormal2ArbCall.build( builder, output );
+			
+			return builder.format( snippet, type, output );
+			
+		}
+
+	}
+
+}
+
+export default NormalMapNode;

+ 139 - 34
examples/jsm/renderers/nodes/functions/BSDFs.js

@@ -1,7 +1,8 @@
 import FunctionNode from '../core/FunctionNode.js';
+import { pow2 } from './MathFunctions.js';
 
 export const F_Schlick = new FunctionNode( `
-vec3 F_Schlick( const in vec3 f0, const in vec3 f90, const in float dotVH ) {
+vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH  ) {
 
 	// Original approximation by Christophe Schlick '94
 	// float fresnel = pow( 1.0 - dotVH, 5.0 );
@@ -24,13 +25,6 @@ float G_BlinnPhong_Implicit() {
 
 }` ); // validated
 
-export const D_BlinnPhong = new FunctionNode( `
-float D_BlinnPhong( const in float shininess, const in float dotNH ) {
-
-	return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );
-
-}` ); // validated
-
 export const BRDF_Lambert = new FunctionNode( `
 vec3 BRDF_Lambert( const in vec3 diffuseColor ) {
 
@@ -38,24 +32,6 @@ vec3 BRDF_Lambert( const in vec3 diffuseColor ) {
 
 }` ); // validated
 
-export const BRDF_BlinnPhong = new FunctionNode( `
-vec3 BRDF_BlinnPhong( vec3 lightDirection, vec3 specularColor, float shininess ) {
-
-	vec3 halfDir = normalize( lightDirection + PositionViewDirection );
-
-	float dotNH = saturate( dot( NormalView, halfDir ) );
-	float dotVH = saturate( dot( PositionViewDirection, halfDir ) );
-
-	vec3 F = F_Schlick( specularColor, vec3( 1.0 ), dotVH );
-
-	float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ );
-
-	float D = D_BlinnPhong( shininess, dotNH );
-
-	return F * ( G * D );
-
-}` ).setIncludes( [ F_Schlick, G_BlinnPhong_Implicit, D_BlinnPhong ] ); // validated
-
 export const punctualLightIntensityToIrradianceFactor = new FunctionNode( `
 float punctualLightIntensityToIrradianceFactor( float lightDistance, float cutoffDistance, float decayExponent ) {
 
@@ -88,23 +64,152 @@ float punctualLightIntensityToIrradianceFactor( float lightDistance, float cutof
 
 #endif
 
-}` );
+}` ).setIncludes( [ pow2 ] );
+
+//
+//	BLINN PHONG
+//
+
+export const D_BlinnPhong = new FunctionNode( `
+float D_BlinnPhong( const in float shininess, const in float dotNH ) {
+
+	return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );
+
+}` ); // validated
+
+export const BRDF_BlinnPhong = new FunctionNode( `
+vec3 BRDF_BlinnPhong( vec3 lightDirection, vec3 specularColor, float shininess ) {
+
+	vec3 halfDir = normalize( lightDirection + PositionViewDirection );
+
+	float dotNH = saturate( dot( NormalView, halfDir ) );
+	float dotVH = saturate( dot( PositionViewDirection, halfDir ) );
+
+	vec3 F = F_Schlick( specularColor, 1.0, dotVH );
+
+	float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ );
+
+	float D = D_BlinnPhong( shininess, dotNH );
+
+	return F * ( G * D );
+
+}` ).setIncludes( [ F_Schlick, G_BlinnPhong_Implicit, D_BlinnPhong ] ); // validated
 
 export const RE_Direct_BlinnPhong = new FunctionNode( `
-void RE_Direct_BlinnPhong( vec3 lightDirection, vec3 lightColor ) {
+void RE_Direct_BlinnPhong( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) {
 
 	float dotNL = saturate( dot( NormalView, lightDirection ) );
 	vec3 irradiance = dotNL * lightColor;
 
-	ReflectedLightDirectDiffuse += irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb );
+#ifndef PHYSICALLY_CORRECT_LIGHTS
+
+		irradiance *= PI; // punctual light
+
+#endif
+
+	reflectedLight.directDiffuse += irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb );
 
-	ReflectedLightDirectSpecular += irradiance * BRDF_BlinnPhong( lightDirection, MaterialSpecularColor, MaterialSpecularShininess );
+	reflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( lightDirection, MaterialSpecularColor, MaterialSpecularShininess );
 
 }` ).setIncludes( [ BRDF_Lambert, BRDF_BlinnPhong ] );
 
-export const RE_IndirectDiffuse_BlinnPhong = new FunctionNode( `
-void RE_IndirectDiffuse_BlinnPhong( ) {
+export const BlinnPhongLightingModel = new FunctionNode( `
+void ( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) {
+
+	RE_Direct_BlinnPhong( reflectedLight, lightDirection, lightColor );
+
+}` ).setIncludes( [ RE_Direct_BlinnPhong ] );
+
+//
+// STANDARD
+//
+
+// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
+// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+export const V_GGX_SmithCorrelated = new FunctionNode( `
+float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {
+
+	float a2 = pow2( alpha );
+
+	float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );
+	float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );
 
-	ReflectedLightIndirectDiffuse += Irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb );
+	return 0.5 / max( gv + gl, EPSILON );
+
+}` ).setIncludes( [ pow2 ] );
+
+// Microfacet Models for Refraction through Rough Surfaces - equation (33)
+// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
+// alpha is "roughness squared" in Disney’s reparameterization
+export const D_GGX = new FunctionNode( `
+float D_GGX( const in float alpha, const in float dotNH ) {
+
+	float a2 = pow2( alpha );
+
+	float denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0; // avoid alpha = 0 with dotNH = 1
+
+	return RECIPROCAL_PI * a2 / pow2( denom );
+
+}` ).setIncludes( [ pow2 ] );
+
+// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility
+export const BRDF_Specular_GGX = new FunctionNode( `
+vec3 BRDF_Specular_GGX( vec3 lightDirection, const in vec3 f0, const in float f90, const in float roughness ) {
+
+	float alpha = pow2( roughness ); // UE4's roughness
+
+	vec3 halfDir = normalize( lightDirection + PositionViewDirection );
+
+	float dotNL = saturate( dot( TransformedNormalView, lightDirection ) );
+	float dotNV = saturate( dot( TransformedNormalView, PositionViewDirection ) );
+	float dotNH = saturate( dot( TransformedNormalView, halfDir ) );
+	float dotVH = saturate( dot( PositionViewDirection, halfDir ) );
 
-}` ).setIncludes( [ BRDF_Lambert ] );
+	vec3 F = F_Schlick( f0, f90, dotVH );
+
+	float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );
+
+	float D = D_GGX( alpha, dotNH );
+
+	return F * ( V * D );
+
+}` ).setIncludes( [ pow2, F_Schlick, V_GGX_SmithCorrelated, D_GGX ] ); // validated
+
+export const RE_Direct_Physical = new FunctionNode( `
+void RE_Direct_Physical( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) {
+
+	float dotNL = saturate( dot( TransformedNormalView, lightDirection ) );
+	vec3 irradiance = dotNL * lightColor;
+
+#ifndef PHYSICALLY_CORRECT_LIGHTS
+
+		irradiance *= PI; // punctual light
+
+#endif
+
+	reflectedLight.directDiffuse += irradiance * BRDF_Lambert( MaterialDiffuseColor.rgb );
+
+	reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX( lightDirection, MaterialSpecularColor, 1.0, MaterialRoughness );
+
+}` ).setIncludes( [ BRDF_Lambert, BRDF_Specular_GGX ] );
+
+export const PhysicalLightingModel = new FunctionNode( `
+void ( inout ReflectedLight reflectedLight, vec3 lightDirection, vec3 lightColor ) {
+
+	RE_Direct_Physical( reflectedLight, lightDirection, lightColor );
+
+}` ).setIncludes( [ RE_Direct_Physical ] );
+
+// utils
+
+// Trowbridge-Reitz distribution to Mip level, following the logic of http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html
+export const getSpecularMIPLevel = new FunctionNode( `
+float ( const in float roughness, const in float maxMIPLevelScalar ) {
+
+	float sigma = PI * roughness * roughness / ( 1.0 + roughness );
+	float desiredMIPLevel = maxMIPLevelScalar + log2( sigma );
+
+	// clamp to allowable LOD ranges.
+	return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
+
+}` );

+ 99 - 0
examples/jsm/renderers/nodes/functions/EncodingFunctions.js

@@ -0,0 +1,99 @@
+import CodeNode from '../core/CodeNode.js';
+import FunctionNode from '../core/FunctionNode.js';
+
+export const LinearToLinear = new FunctionNode( `
+vec4 ( in vec4 value ) {
+	return value;
+}` );
+
+export const GammaToLinear = new FunctionNode( `
+vec4 ( in vec4 value, in float gammaFactor ) {
+	return vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );
+}` );
+
+export const LinearToGamma = new FunctionNode( `
+vec4 ( in vec4 value, in float gammaFactor ) {
+	return vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );
+}` );
+
+export const sRGBToLinear = new FunctionNode( `
+vec4 ( in vec4 value ) {
+	return vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );
+}` );
+
+export const LinearTosRGB = new FunctionNode( `
+vec4 ( in vec4 value ) {
+	return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
+}` );
+
+export const RGBEToLinear = new FunctionNode( `
+vec4 ( in vec4 value ) {
+	return vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );
+}` );
+
+export const LinearToRGBE = new FunctionNode( `
+vec4 ( in vec4 value ) {
+	float maxComponent = max( max( value.r, value.g ), value.b );
+	float fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );
+	return vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );
+	// return vec4( value.brg, ( 3.0 + 128.0 ) / 256.0 );
+}` );
+
+// reference: http://iwasbeingirony.blogspot.ca/2010/06/difference-between-rgbm-and-rgbd.html
+export const RGBMToLinear = new FunctionNode( `
+vec4 ( in vec4 value, in float maxRange ) {
+	return vec4( value.rgb * value.a * maxRange, 1.0 );
+}` );
+
+export const LinearToRGBM = new FunctionNode( `
+vec4 ( in vec4 value, in float maxRange ) {
+	float maxRGB = max( value.r, max( value.g, value.b ) );
+	float M = clamp( maxRGB / maxRange, 0.0, 1.0 );
+	M = ceil( M * 255.0 ) / 255.0;
+	return vec4( value.rgb / ( M * maxRange ), M );
+}` );
+
+// reference: http://iwasbeingirony.blogspot.ca/2010/06/difference-between-rgbm-and-rgbd.html
+export const RGBDToLinear = new FunctionNode( `
+vec4 ( in vec4 value, in float maxRange ) {
+	return vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );
+}` );
+
+export const LinearToRGBD = new FunctionNode( `
+vec4 ( in vec4 value, in float maxRange ) {
+	float maxRGB = max( value.r, max( value.g, value.b ) );
+	float D = max( maxRange / maxRGB, 1.0 );
+	// NOTE: The implementation with min causes the shader to not compile on
+	// a common Alcatel A502DL in Chrome 78/Android 8.1. Some research suggests
+	// that the chipset is Mediatek MT6739 w/ IMG PowerVR GE8100 GPU.
+	// D = min( floor( D ) / 255.0, 1.0 );
+	D = clamp( floor( D ) / 255.0, 0.0, 1.0 );
+	return vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );
+}` );
+
+// LogLuv reference: http://graphicrants.blogspot.ca/2009/04/rgbm-color-encoding.html
+export const cLogLuvM = new CodeNode( 'const mat3 cLogLuvMNode = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );' );
+export const LinearToLogLuv = new FunctionNode( `
+vec4 ( in vec4 value ) {
+	vec3 Xp_Y_XYZp = cLogLuvMNode * value.rgb;
+	Xp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );
+	vec4 vResult;
+	vResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;
+	float Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;
+	vResult.w = fract( Le );
+	vResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;
+	return vResult;
+}` ).setIncludes( [ cLogLuvM ] );
+
+// Inverse M matrix, for decoding
+export const cLogLuvInverseM = new CodeNode( 'const mat3 cLogLuvInverseMNode = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );' );
+export const LogLuvToLinear = new FunctionNode( `
+vec4 ( in vec4 value ) {
+	float Le = value.z * 255.0 + value.w;
+	vec3 Xp_Y_XYZp;
+	Xp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );
+	Xp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;
+	Xp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;
+	vec3 vRGB = cLogLuvInverseMNode * Xp_Y_XYZp.rgb;
+	return vec4( max( vRGB, 0.0 ), 1.0 );
+}` ).setIncludes( [ cLogLuvInverseM ] );

+ 2 - 2
examples/jsm/renderers/nodes/functions/MathFunctions.js

@@ -7,14 +7,14 @@ export const saturateMacro = new CodeNode( '#define saturate(a) clamp( a, 0.0, 1
 export const whiteComplementMacro = new CodeNode( '#define whiteComplement(a) ( 1.0 - saturate( a ) )' );
 
 export const transformDirection = new FunctionNode( `
-vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
+vec3 ( in vec3 dir, in mat4 matrix ) {
 
 	return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
 
 }` );
 
 export const inverseTransformDirection = new FunctionNode( `
-vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {
+vec3 ( in vec3 dir, in mat4 matrix ) {
 
 	// dir can be either a direction vector or a normal vector
 	// upper-left 3x3 of matrix is assumed to be orthogonal

+ 41 - 6
examples/jsm/renderers/nodes/inputs/TextureNode.js

@@ -1,27 +1,62 @@
 import InputNode from '../core/InputNode.js';
+import ExpressionNode from '../core/ExpressionNode.js';
 import UVNode from '../accessors/UVNode.js';
+import ColorSpaceNode from '../display/ColorSpaceNode.js';
 
 class TextureNode extends InputNode {
 
-	constructor( value = null, uv = new UVNode() ) {
+	constructor( value = null, uv = new UVNode(), bias = null ) {
 
 		super( 'texture' );
 
 		this.value = value;
 		this.uv = uv;
+		this.bias = bias;
 
 	}
 
 	generate( builder, output ) {
 
-		const type = this.getType( builder );
+		if ( output === 'sampler2D' ) {
 
-		const textureProperty = super.generate( builder, type );
-		const uvSnippet = this.uv.build( builder, 'vec2' );
+			return super.generate( builder, output );
 
-		const textureCallSnippet = builder.getTexture( textureProperty, uvSnippet );
+		} else {
 
-		return builder.format( textureCallSnippet, type, output );
+			const nodeData = builder.getDataFromNode( this );
+
+			let colorSpace = nodeData.colorSpace;
+
+			if ( colorSpace === undefined ) {
+
+				const type = this.getType( builder );
+
+				const textureProperty = super.generate( builder, type );
+
+				const uvSnippet = this.uv.build( builder, 'vec2' );
+				const bias = this.bias;
+
+				let biasSnippet = null;
+
+				if ( bias !== null ) {
+
+					biasSnippet = bias.build( builder, 'float' );
+
+				}
+
+				const textureCallSnippet = builder.getTexture( textureProperty, uvSnippet, biasSnippet );
+
+				colorSpace = new ColorSpaceNode();
+				colorSpace.input = new ExpressionNode( textureCallSnippet, 'vec4' );
+				colorSpace.fromDecoding( builder.getTextureEncodingFromMap( this.value ) );
+
+				nodeData.colorSpace = colorSpace;
+
+			}
+
+			return colorSpace.build( builder, output );
+
+		}
 
 	}
 

+ 23 - 24
examples/jsm/renderers/nodes/lights/LightContextNode.js

@@ -1,17 +1,17 @@
 import ContextNode from '../core/ContextNode.js';
-import { RE_Direct_BlinnPhong, RE_IndirectDiffuse_BlinnPhong } from '../functions/BSDFs.js';
+import StructNode from '../core/StructNode.js';
+import { PhysicalLightingModel, BlinnPhongLightingModel } from '../functions/BSDFs.js';
+
+const reflectedLightStruct = new StructNode( {
+	directDiffuse: 'vec3',
+	directSpecular: 'vec3'
+}, 'ReflectedLight' );
 
 class LightContextNode extends ContextNode {
 
 	constructor( node ) {
 
-		super( node );
-
-	}
-
-	getType( /*builder*/ ) {
-
-		return 'vec3';
+		super( node, 'vec3' );
 
 	}
 
@@ -21,37 +21,36 @@ class LightContextNode extends ContextNode {
 
 		const material = builder.material;
 
-		let RE_Direct = null;
-		let RE_IndirectDiffuse = null;
+		let lightingModel = null;
 
-		if ( material.isMeshPhongMaterial === true ) {
+		if ( material.isMeshStandardMaterial === true ) {
 
-			RE_Direct = RE_Direct_BlinnPhong;
-			RE_IndirectDiffuse = RE_IndirectDiffuse_BlinnPhong;
+			lightingModel = PhysicalLightingModel;
+
+		} else if ( material.isMeshPhongMaterial === true ) {
+
+			lightingModel = BlinnPhongLightingModel;
 
 		}
 
-		if ( RE_Direct !== null ) {
+		const reflectedLightNode = reflectedLightStruct.create();
+		const reflectedLight = reflectedLightNode.build( builder, 'var' );
 
-			this.setParameter( 'RE_Direct', RE_Direct );
-			this.setParameter( 'RE_IndirectDiffuse', RE_IndirectDiffuse );
+		this.setContextValue( 'reflectedLight', reflectedLightNode );
 
-		}
+		if ( lightingModel !== null ) {
 
-		const resetTotalLight = 'Irradiance = vec3( 0.0 ); ReflectedLightDirectDiffuse = vec3( 0.0 ); ReflectedLightDirectSpecular = vec3( 0.0 );';
-		const resultTotalLight = 'ReflectedLightDirectDiffuse + ReflectedLightDirectSpecular';
+			this.setContextValue( 'lightingModel', lightingModel );
 
-		// include keywords
+		}
 
-		builder.getContextParameter( 'keywords' ).include( builder, resetTotalLight );
+		const totalLightSnippet = `( ${reflectedLight}.directDiffuse + ${reflectedLight}.directSpecular )`;
 
 		// add code
 
-		builder.addFlowCode( resetTotalLight );
-
 		super.generate( builder, output );
 
-		return builder.format( resultTotalLight, type, output );
+		return builder.format( totalLightSnippet, type, output );
 
 	}
 

+ 11 - 12
examples/jsm/renderers/nodes/lights/LightNode.js

@@ -56,22 +56,21 @@ class LightNode extends Node {
 
 		this.lightPositionView.object3d = this.light;
 
-		const directFunctionNode = builder.getContextParameter( 'RE_Direct' );
-		const indirectDiffuseFunctionNode = builder.getContextParameter( 'RE_IndirectDiffuse' );
+		const lightingModelFunctionNode = builder.getContextValue( 'lightingModel' );
 
-		const directFunctionCallNode = directFunctionNode.call( {
-			lightDirection: this.lightDirection,
-			lightColor: this.lightColor
-		} );
+		if ( lightingModelFunctionNode !== undefined ) {
 
-		const indirectDiffuseFunctionCallNode = indirectDiffuseFunctionNode.call( {
-			lightDirection: this.lightDirection,
-			lightColor: this.lightColor
-		} );
+			const reflectedLightStructNode = builder.getContextValue( 'reflectedLight' );
+
+			const lightingModelCallNode = lightingModelFunctionNode.call( {
+				lightDirection: this.lightDirection,
+				lightColor: this.lightColor,
+				reflectedLight:	reflectedLightStructNode
+			} );
 
-		builder.addFlowCode( directFunctionCallNode.build( builder ) );
+			builder.addFlowCode( lightingModelCallNode.build( builder ) );
 
-		builder.addFlowCode( indirectDiffuseFunctionCallNode.build( builder ) );
+		}
 
 		return this.color.build( builder, output );
 

+ 45 - 0
examples/jsm/renderers/nodes/lights/PhysicalMaterialContextNode.js

@@ -0,0 +1,45 @@
+import ContextNode from '../core/ContextNode.js';
+import NormalNode from '../accessors/NormalNode.js';
+import ExpressionNode from '../core/ExpressionNode.js';
+import FloatNode from '../inputs/FloatNode.js';
+
+class PhysicalMaterialContextNode extends ContextNode {
+
+	static RADIANCE = 'radiance';
+	static IRRADIANCE = 'irradiance';
+
+	constructor( scope, node ) {
+
+		super( node, 'vec3' );
+
+		this.scope = scope;
+
+	}
+
+	generate( builder, output ) {
+
+		const scope = this.scope;
+
+		let roughness = null;
+
+		if ( scope === PhysicalMaterialContextNode.RADIANCE ) {
+
+			roughness = new ExpressionNode( 'roughnessFactor', 'float' );
+
+		} else if ( scope === PhysicalMaterialContextNode.IRRADIANCE ) {
+
+			roughness = new FloatNode( 1.0 ).setConst( true );
+
+			this.setContextValue( 'uv', new NormalNode( NormalNode.WORLD ) );
+
+		}
+
+		this.setContextValue( 'roughness', roughness );
+
+		return super.generate( builder, output );
+
+	}
+
+}
+
+export default PhysicalMaterialContextNode;

+ 118 - 31
examples/jsm/renderers/nodes/math/MathNode.js

@@ -1,12 +1,55 @@
-import Node from '../core/Node.js';
-
-class MathNode extends Node {
-
+import TempNode from '../core/Node.js';
+
+class MathNode extends TempNode {
+
+	// 1 input
+
+	static RAD = 'radians';
+	static DEG = 'degrees';
+	static EXP = 'exp';
+	static EXP2 = 'exp2';
+	static LOG = 'log';
+	static LOG2 = 'log2';
+	static SQRT = 'sqrt';
+	static INV_SQRT = 'inversesqrt';
+	static FLOOR = 'floor';
+	static CEIL = 'ceil';
 	static NORMALIZE = 'normalize';
-	static NEGATE = 'negate';
+	static FRACT = 'fract';
+	static SATURATE = 'saturate';
+	static SIN = 'sin';
+	static COS = 'cos';
+	static TAN = 'tan';
+	static ASIN = 'asin';
+	static ACOS = 'acos';
+	static ATAN = 'atan';
+	static ABS = 'abs';
+	static SIGN = 'sign';
 	static LENGTH = 'length';
+	static NEGATE = 'negate';
+	static INVERT = 'invert';
+
+	// 2 inputs
+
+	static MIN = 'min';
+	static MAX = 'max';
+	static MOD = 'mod';
+	static STEP = 'step';
+	static REFLECT = 'reflect';
+	static DISTANCE = 'distance';
+	static DOT = 'dot';
+	static CROSS = 'cross';
+	static POW = 'pow';
+
+	// 3 inputs
 
-	constructor( method, a, b = null ) {
+	static MIX = 'mix';
+	static CLAMP = 'clamp';
+	static REFRACT = 'refract';
+	static SMOOTHSTEP = 'smoothstep';
+	static FACEFORWARD = 'faceforward';
+
+	constructor( method, a, b = null, c = null ) {
 
 		super();
 
@@ -14,28 +57,31 @@ class MathNode extends Node {
 
 		this.a = a;
 		this.b = b;
+		this.c = c;
 
 	}
 
 	getInputType( builder ) {
 
-		const typeA = this.a.getType( builder );
+		const aLen = this.a.getTypeLength( builder );
+		const bLen = this.b ? this.b.getTypeLength( builder ) : 0;
+		const cLen = this.c ? this.c.getTypeLength( builder ) : 0;
 
-		if ( this.b !== null ) {
+		if ( aLen > bLen && aLen > cLen ) {
 
-			const typeB = this.b.getType( builder );
+			return this.a.getType( builder );
 
-			if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
+		} else if ( bLen > cLen ) {
 
-				// anytype x anytype: use the greater length vector
+			return this.b.getType( builder );
 
-				return typeB;
+		} else if ( cLen > aLen ) {
 
-			}
+			this.c.getType( builder )
 
 		}
 
-		return typeA;
+		return this.a.getType( builder );
 
 	}
 
@@ -43,14 +89,11 @@ class MathNode extends Node {
 
 		const method = this.method;
 
-		if ( method === MathNode.LENGTH ) {
+		if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) {
 
 			return 'float';
 
-		} else if (
-			method === MathNode.TRANSFORM_DIRETION ||
-			method === MathNode.INVERSE_TRANSFORM_DIRETION
-		) {
+		} else if (method === MathNode.CROSS) {
 
 			return 'vec3';
 
@@ -65,33 +108,77 @@ class MathNode extends Node {
 	generate( builder, output ) {
 
 		const method = this.method;
-		const type = this.getInputType( builder );
-
-		const a = this.a.build( builder, type );
-		let b = null;
 
-		if ( this.b !== null ) {
+		const type = this.getType( builder );
+		const inputType = this.getInputType( builder );
 
-			b = this.b.build( builder, type );
+		if ( method === MathNode.NEGATE ) {
 
-		}
+			return builder.format( '( -' + this.a.build( builder, inputType ) + ' )', type, output );
 
-		if ( b !== null ) {
+		} else if ( method === MathNode.INVERT ) {
 
-			return builder.format( `${method}( ${a}, ${b} )`, type, output );
+			return builder.format( '( 1.0 - ' + this.a.build( builder, inputType ) + ' )', type, output );
 
 		} else {
 
-			if ( method === MathNode.NEGATE ) {
+			const params = [];
+
+			if ( method === MathNode.CROSS ) {
+
+				params.push(
+					this.a.build( builder, type ),
+					this.b.build( builder, type )
+				);
 
-				return builder.format( `( -${a} )`, type, output );
+			} else if ( method === MathNode.STEP ) {
+
+				params.push(
+					this.b.build( builder, this.a.getTypeLength( builder ) === 1 ? 'float' : inputType ),
+					this.b.build( builder, inputType )
+				);
+
+			} else if ( method === MathNode.MIN || method === MathNode.MAX || method === MathNode.MOD ) {
+
+				params.push(
+					this.a.build( builder, inputType ),
+					this.b.build( builder, this.b.getTypeLength( builder ) === 1 ? 'float' : inputType )
+				);
+
+			} else if ( method === MathNode.REFRACT ) {
+
+				params.push(
+					this.a.build( builder, inputType ),
+					this.b.build( builder, inputType ),
+					this.c.build( builder, 'float' )
+				);
+
+			} else if ( method === MathNode.MIX ) {
+
+				params.push(
+					this.a.build( builder, inputType ),
+					this.b.build( builder, inputType ),
+					this.c.build( builder, this.c.getTypeLength( builder ) === 1 ? 'float' : inputType )
+				);
 
 			} else {
 
-				return builder.format( `${method}( ${a} )`, type, output );
+				params.push( this.a.build( builder, inputType ) );
+
+				if ( this.c !== null ) {
+
+					params.push( this.b.build( builder, inputType ), this.c.build( builder, inputType ) );
+
+				} else if ( this.b !== null ) {
+
+					params.push( this.b.build( builder, inputType ) );
+
+				}
 
 			}
 
+			return builder.format( `${method}( ${params.join(', ')} )`, type, output );
+
 		}
 
 	}

+ 4 - 10
examples/jsm/renderers/nodes/math/OperatorNode.js

@@ -1,6 +1,6 @@
-import Node from '../core/Node.js';
+import TempNode from '../core/TempNode.js';
 
-class OperatorNode extends Node {
+class OperatorNode extends TempNode {
 
 	constructor( op, a, b ) {
 
@@ -42,12 +42,6 @@ class OperatorNode extends Node {
 
 	}
 
-	getVectorFromMatrix( type ) {
-
-		return 'vec' + type.substr( 3 );
-
-	}
-
 	generate( builder, output ) {
 
 		let typeA = this.a.getType( builder );
@@ -59,13 +53,13 @@ class OperatorNode extends Node {
 
 			// matrix x vector
 
-			type = typeB = this.getVectorFromMatrix( typeA );
+			type = typeB = builder.getVectorFromMatrix( typeA );
 
 		} else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) {
 
 			// vector x matrix
 
-			type = typeB = this.getVectorFromMatrix( typeB );
+			type = typeB = builder.getVectorFromMatrix( typeB );
 
 		} else {
 

+ 91 - 23
examples/jsm/renderers/webgpu/nodes/ShaderLib.js

@@ -5,15 +5,11 @@ const ShaderLib = {
 		vertexShader:
 			`#version 450
 
-			NODE_HEADER_ATTRIBUTES
-			NODE_HEADER_UNIFORMS
-			NODE_HEADER_VARYS
-
 			void main(){
 
-				NODE_BODY_VARYS
-				NODE_BODY_VARS
+				NODE_CODE
 
+				NODE_CODE_MVP
 				gl_Position = NODE_MVP;
 
 			}`,
@@ -21,15 +17,11 @@ const ShaderLib = {
 		fragmentShader:
 			`#version 450
 
-			NODE_HEADER_ATTRIBUTES
-			NODE_HEADER_UNIFORMS
-			NODE_HEADER_VARYS
-
 			layout(location = 0) out vec4 outColor;
 
 			void main() {
 
-				NODE_BODY_VARS
+				NODE_CODE
 
 				MaterialDiffuseColor = vec4( 1.0 );
 
@@ -51,6 +43,7 @@ const ShaderLib = {
 
 				#ifdef NODE_ALPHA_TEST
 
+					NODE_CODE_ALPHA_TEST
 					if ( MaterialDiffuseColor.a < NODE_ALPHA_TEST ) discard;
 
 				#endif
@@ -77,15 +70,11 @@ const ShaderLib = {
 		vertexShader:
 			`#version 450
 
-			NODE_HEADER_ATTRIBUTES
-			NODE_HEADER_UNIFORMS
-			NODE_HEADER_VARYS
-
 			void main(){
 
-				NODE_BODY_VARYS
-				NODE_BODY_VARS
+				NODE_CODE
 
+				NODE_CODE_MVP
 				gl_Position = NODE_MVP;
 
 			}`,
@@ -93,15 +82,11 @@ const ShaderLib = {
 		fragmentShader:
 			`#version 450
 
-			NODE_HEADER_ATTRIBUTES
-			NODE_HEADER_UNIFORMS
-			NODE_HEADER_VARYS
-
 			layout(location = 0) out vec4 outColor;
 
 			void main() {
 
-				NODE_BODY_VARS
+				NODE_CODE
 
 				MaterialDiffuseColor = vec4( 1.0 );
 				MaterialSpecularColor = vec3( 1.0 );
@@ -115,6 +100,7 @@ const ShaderLib = {
 
 				#ifdef NODE_ALPHA_TEST
 
+					NODE_CODE_ALPHA_TEST
 					if ( MaterialDiffuseColor.a < NODE_ALPHA_TEST ) discard;
 
 				#endif
@@ -140,7 +126,89 @@ const ShaderLib = {
 
 			}`
 
-	}
+	},
+
+	standard: {
+
+		vertexShader:
+			`#version 450
+
+			void main(){
+
+				NODE_CODE
+
+				NODE_CODE_MVP
+				gl_Position = NODE_MVP;
+
+			}`,
+
+		fragmentShader:
+			`#version 450
+
+			layout(location = 0) out vec4 outColor;
+
+			void main() {
+
+				NODE_CODE
+
+				MaterialDiffuseColor = vec4( 1.0 );
+				MaterialMetalness = 1.0;
+				MaterialRoughness = 1.0;
+
+				#ifdef NODE_COLOR
+
+					NODE_CODE_COLOR
+
+					MaterialDiffuseColor = NODE_COLOR;
+
+				#endif
+
+				#ifdef NODE_OPACITY
+
+					NODE_CODE_OPACITY
+
+					MaterialDiffuseColor.a *= NODE_OPACITY;
+
+				#endif
+
+				#ifdef NODE_ALPHA_TEST
+
+					NODE_CODE_ALPHA_TEST
+					if ( MaterialDiffuseColor.a < NODE_ALPHA_TEST ) discard;
+
+				#endif
+
+				NODE_CODE_METALNESS
+				MaterialMetalness = NODE_METALNESS;
+
+				NODE_CODE_ROUGHNESS
+				MaterialRoughness = NODE_ROUGHNESS;
+
+				#ifdef NODE_NORMAL
+
+					NODE_CODE_NORMAL
+					TransformedNormalView = NODE_NORMAL;
+
+				#endif
+
+				MaterialDiffuseColor.rgb = MaterialDiffuseColor.rgb * ( 1.0 - MaterialMetalness );
+
+				#ifdef NODE_LIGHT
+
+					NODE_CODE_LIGHT
+
+					outColor.rgb = NODE_LIGHT;
+					outColor.a = MaterialDiffuseColor.a;
+
+				#else
+
+					outColor = MaterialDiffuseColor;
+
+				#endif
+
+			}`
+
+	},
 
 };
 

+ 71 - 68
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -7,18 +7,22 @@ import WebGPUNodeSampler from './WebGPUNodeSampler.js';
 import { WebGPUNodeSampledTexture } from './WebGPUNodeSampledTexture.js';
 
 import NodeSlot from '../../nodes/core/NodeSlot.js';
+import VarNode from '../../nodes/core/VarNode.js';
 import NodeBuilder from '../../nodes/core/NodeBuilder.js';
 import MaterialNode from '../../nodes/accessors/MaterialNode.js';
+import NormalNode from '../../nodes/accessors/NormalNode.js';
 import ModelViewProjectionNode from '../../nodes/accessors/ModelViewProjectionNode.js';
 import LightContextNode from '../../nodes/lights/LightContextNode.js';
 import ShaderLib from './ShaderLib.js';
 
 class WebGPUNodeBuilder extends NodeBuilder {
 
-	constructor( material, renderer ) {
+	constructor( material, renderer, lightNode = null ) {
 
 		super( material, renderer );
 
+		this.lightNode = lightNode;
+
 		this.bindings = { vertex: [], fragment: [] };
 		this.bindingsOffset = { vertex: 0, fragment: 0 };
 
@@ -38,7 +42,11 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		let shader = null;
 
-		if ( material.isMeshPhongMaterial ) {
+		if ( material.isMeshStandardMaterial ) {
+
+			shader = ShaderLib.standard;
+
+		} else if ( material.isMeshPhongMaterial ) {
 
 			shader = ShaderLib.phong;
 
@@ -52,10 +60,17 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		// parse inputs
 
-		if ( material.isMeshPhongMaterial || material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
+		if ( material.isMeshStandardMaterial || material.isMeshPhongMaterial || material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
 
 			const mvpNode = new ModelViewProjectionNode();
-			const lightNode = material.lightNode;
+
+			let lightNode = material.lightNode;
+
+			if ( lightNode === undefined && this.lightNode && this.lightNode.hasLights === true ) {
+
+				lightNode = this.lightNode;
+
+			}
 
 			if ( material.positionNode !== undefined ) {
 
@@ -95,7 +110,43 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			}
 
-			if ( material.isMeshPhongMaterial ) {
+			if ( material.isMeshStandardMaterial ) {
+
+				if ( material.metalnessNode !== undefined ) {
+
+					this.addSlot( 'fragment', new NodeSlot( material.metalnessNode, 'METALNESS', 'float' ) );
+
+				} else {
+
+					this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.METALNESS ), 'METALNESS', 'float' ) );
+
+				}
+
+				if ( material.roughnessNode !== undefined ) {
+
+					this.addSlot( 'fragment', new NodeSlot( material.roughnessNode, 'ROUGHNESS', 'float' ) );
+
+				} else {
+
+					this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.ROUGHNESS ), 'ROUGHNESS', 'float' ) );
+
+				}
+
+				let normalNode = null;
+
+				if ( material.normalNode !== undefined ) {
+
+					normalNode = material.normalNode;
+
+				} else {
+
+					normalNode = new NormalNode( NormalNode.VIEW );
+
+				}
+
+				this.addSlot( 'fragment', new NodeSlot( new VarNode( normalNode, 'TransformedNormalView', 'vec3' ), 'NORMAL', 'vec3' ) );
+
+			} else if ( material.isMeshPhongMaterial ) {
 
 				if ( material.specularNode !== undefined ) {
 
@@ -119,7 +170,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			}
 
-			if ( lightNode !== undefined ) {
+			if ( lightNode && lightNode.isNode ) {
 
 				const lightContextNode = new LightContextNode( lightNode );
 
@@ -131,9 +182,17 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getTexture( textureProperty, uvSnippet ) {
+	getTexture( textureProperty, uvSnippet, biasSnippet = null ) {
+
+		if ( biasSnippet !== null ) {
+
+			return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet}, ${biasSnippet} )`;
+
+		} else {
+
+			return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet} )`;
 
-		return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet} )`;
+		}
 
 	}
 
@@ -258,7 +317,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getAttributesHeaderSnippet( shaderStage ) {
+	getAttributes( shaderStage ) {
 
 		let snippet = '';
 
@@ -280,7 +339,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getVarysHeaderSnippet( shaderStage ) {
+	getVarys( shaderStage ) {
 
 		let snippet = '';
 
@@ -300,63 +359,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	getVarysBodySnippet( shaderStage ) {
-
-		let snippet = '';
-
-		if ( shaderStage === 'vertex' ) {
-
-			for ( const vary of this.varys ) {
-
-				snippet += `${vary.name} = ${vary.snippet}; `;
-
-			}
-
-		}
-
-		return snippet;
-
-	}
-
-	getVarsHeaderSnippet( shaderStage ) {
-
-		let snippet = '';
-
-		const vars = this.vars[ shaderStage ];
-
-		for ( let index = 0; index < vars.length; index ++ ) {
-
-			const variable = vars[ index ];
-
-			snippet += `${variable.type} ${variable.name}; `;
-
-		}
-
-		return snippet;
-
-	}
-
-	getVarsBodySnippet( shaderStage ) {
-
-		let snippet = '';
-
-		const vars = this.vars[ shaderStage ];
-
-		for ( const variable of vars ) {
-
-			if ( variable.snippet !== '' ) {
-
-				snippet += `${variable.name} = ${variable.snippet}; `;
-
-			}
-
-		}
-
-		return snippet;
-
-	}
-
-	getUniformsHeaderSnippet( shaderStage ) {
+	getUniforms( shaderStage ) {
 
 		const uniforms = this.uniforms[ shaderStage ];
 
@@ -409,7 +412,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	build() {
 
-		const keywords = this.getContextParameter( 'keywords' );
+		const keywords = this.getContextValue( 'keywords' );
 
 		for ( const shaderStage of [ 'vertex', 'fragment' ] ) {
 

+ 4 - 4
examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js

@@ -13,13 +13,13 @@ class WebGPUNodes {
 
 	}
 
-	get( object ) {
+	get( object, lightNode ) {
 
 		let nodeBuilder = this.builders.get( object );
 
 		if ( nodeBuilder === undefined ) {
 
-			nodeBuilder = new WebGPUNodeBuilder( object.material, this.renderer ).build();
+			nodeBuilder = new WebGPUNodeBuilder( object.material, this.renderer, lightNode ).build();
 
 			this.builders.set( object, nodeBuilder );
 
@@ -41,11 +41,11 @@ class WebGPUNodes {
 
 	}
 
-	update( object, camera ) {
+	update( object, camera, lightNode ) {
 
 		const material = object.material;
 
-		const nodeBuilder = this.get( object );
+		const nodeBuilder = this.get( object, lightNode );
 		const nodeFrame = this.nodeFrame;
 
 		nodeFrame.material = material;

二進制
examples/screenshots/webgpu_lights_custom.jpg


二進制
examples/screenshots/webgpu_lights_selective.jpg


+ 4 - 13
examples/webgpu_lights_custom.html

@@ -89,26 +89,17 @@
 
 				// custom lighting model
 
-				const RE_Direct = new FunctionNode( `
-					void ( vec3 lightColor ) {
+				const customLightingModel = new FunctionNode( `
+					void ( inout ReflectedLight reflectedLight, vec3 lightColor ) {
 
 						// lightColor returns the light color with the intensity calculated
 
-						ReflectedLightDirectDiffuse += lightColor;
-
-					}` );
-
-
-				const RE_IndirectDiffuse = new FunctionNode( `
-					void ( ) {
-
-						//ReflectedLightIndirectDiffuse += vec3( 0.0 );
+						reflectedLight.directDiffuse += lightColor;
 
 					}` );
 
 				const lightingModelContext = new ContextNode( allLightsNode );
-				lightingModelContext.setParameter( 'RE_Direct', RE_Direct );
-				lightingModelContext.setParameter( 'RE_IndirectDiffuse', RE_IndirectDiffuse );
+				lightingModelContext.setContextValue( 'lightingModel', customLightingModel );
 
 				materialPoints.lightNode = lightingModelContext;
 

+ 31 - 5
examples/webgpu_lights_selective.html

@@ -27,6 +27,8 @@
 
 			import Stats from './jsm/libs/stats.module.js';
 
+			import { GUI } from './jsm/libs/dat.gui.module.js';
+
 			import { OrbitControls } from './jsm/controls/OrbitControls.js';
 			import { TeapotGeometry } from './jsm/geometries/TeapotGeometry.js';
 
@@ -34,6 +36,8 @@
 			import WebGPU from './jsm/renderers/webgpu/WebGPU.js';
 
 			import LightsNode from './jsm/renderers/nodes/lights/LightsNode.js';
+			import TextureNode from './jsm/renderers/nodes/inputs/TextureNode.js';
+			import NormalMapNode from './jsm/renderers/nodes/display/NormalMapNode.js';
 
 			let camera, scene, renderer,
 				light1, light2, light3, light4,
@@ -58,6 +62,18 @@
 
 				const sphere = new THREE.SphereGeometry( 0.5, 16, 8 );
 
+				//textures
+
+				const textureLoader = new THREE.TextureLoader();
+
+				const normalMapTexture = textureLoader.load( './textures/water/Water_1_M_Normal.jpg' );
+				normalMapTexture.wrapS = THREE.RepeatWrapping;
+				normalMapTexture.wrapT = THREE.RepeatWrapping;
+
+				const alphaTexture = textureLoader.load( './textures/roughness_map.jpg' );
+				alphaTexture.wrapS = THREE.RepeatWrapping;
+				alphaTexture.wrapT = THREE.RepeatWrapping;
+
 				//lights
 
 				light1 = new THREE.PointLight( 0xff0040, 2, 100 );
@@ -86,22 +102,25 @@
 
 				const geometryTeapot = new TeapotGeometry( 8, 18 );
 
-				const leftObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) );
+				const leftObject = new THREE.Mesh( geometryTeapot, new THREE.MeshStandardMaterial( { color: 0x555555 } ) );
 				leftObject.material.lightNode = redLightNode;
+				leftObject.material.roughnessNode = new TextureNode( alphaTexture );
+				leftObject.material.metalness = 0;
 				leftObject.position.x = - 30;
 				scene.add( leftObject );
 
-				const centerObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) );
+				const centerObject = new THREE.Mesh( geometryTeapot, new THREE.MeshStandardMaterial( { color: 0x555555 } ) );
 				centerObject.material.lightNode = allLightsNode;
+				centerObject.material.normalNode = new NormalMapNode( new TextureNode( normalMapTexture ) );
+				centerObject.material.roughness = .5;
 				scene.add( centerObject );
 
-				const rightObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) );
+				const rightObject = new THREE.Mesh( geometryTeapot, new THREE.MeshStandardMaterial( { color: 0x555555 } ) );
 				rightObject.material.lightNode = blueLightNode;
+				rightObject.material.metalnessNode = new TextureNode( alphaTexture );
 				rightObject.position.x = 30;
 				scene.add( rightObject );
 
-				leftObject.material.shininess = centerObject.material.shininess = rightObject.material.shininess = 70;
-
 				leftObject.rotation.y = centerObject.rotation.y = rightObject.rotation.y = Math.PI * - 0.5;
 				leftObject.position.y = centerObject.position.y = rightObject.position.y = - 10;
 
@@ -125,6 +144,13 @@
 
 				window.addEventListener( 'resize', onWindowResize );
 
+				//gui
+
+				const gui = new GUI();
+
+				gui.add( centerObject.material, 'roughness', 0, 1, 0.01 );
+				gui.add( centerObject.material, 'metalness', 0, 1, 0.01 );
+
 				return renderer.init();
 
 			}