Browse Source

WebGPU: NodeMaterial BSDFs, revision and updates (#21322)

* NodeMaterial BSDFs, revision and updates (WIP)

* fix title

* add Math.LENGTH

* ContextNode, VarNode, NodeKeywords, LightNode (WIP) and updates

* a bit cleanup

* modify order

* PI AND RECIPROCAL_PI keywords

* Rename BSDFunctions.js -> BSDFs.js

* optimization: not recompute the world matrix

* use context material if need

* PropertyNode, texture test (WIP)

* Flow code support, LightsNode WIP

* multiples lights test, decay and distance

* fix light.intensity

* test light.intensity

* MaterialNode

* WebGPU: selective_lights example (bsdfs phong)

* cleanup

* add webgpu_selective_lights in example list

* cleanup

* use importmap

* update title

* fix example name and screenshot

* fix info text

* cleanup

* check if is texture

* cleanup example

* update screenshot
sunag 4 years ago
parent
commit
b3b0cfdaf4
46 changed files with 2241 additions and 216 deletions
  1. 1 0
      examples/files.json
  2. 14 28
      examples/jsm/renderers/nodes/accessors/CameraNode.js
  3. 106 0
      examples/jsm/renderers/nodes/accessors/MaterialNode.js
  4. 23 0
      examples/jsm/renderers/nodes/accessors/MaterialReferenceNode.js
  5. 4 88
      examples/jsm/renderers/nodes/accessors/ModelNode.js
  6. 1 1
      examples/jsm/renderers/nodes/accessors/ModelViewProjectionNode.js
  7. 4 4
      examples/jsm/renderers/nodes/accessors/NormalNode.js
  8. 129 0
      examples/jsm/renderers/nodes/accessors/Object3DNode.js
  9. 66 5
      examples/jsm/renderers/nodes/accessors/PositionNode.js
  10. 91 0
      examples/jsm/renderers/nodes/accessors/ReferenceNode.js
  11. 4 0
      examples/jsm/renderers/nodes/consts/MathConsts.js
  12. 5 5
      examples/jsm/renderers/nodes/core/AttributeNode.js
  13. 80 0
      examples/jsm/renderers/nodes/core/CodeNode.js
  14. 40 0
      examples/jsm/renderers/nodes/core/ConstNode.js
  15. 53 0
      examples/jsm/renderers/nodes/core/ContextNode.js
  16. 70 0
      examples/jsm/renderers/nodes/core/FunctionCallNode.js
  17. 194 0
      examples/jsm/renderers/nodes/core/FunctionNode.js
  18. 139 8
      examples/jsm/renderers/nodes/core/NodeBuilder.js
  19. 15 0
      examples/jsm/renderers/nodes/core/NodeCode.js
  20. 16 0
      examples/jsm/renderers/nodes/core/NodeFunctionInput.js
  21. 229 0
      examples/jsm/renderers/nodes/core/NodeKeywords.js
  22. 15 0
      examples/jsm/renderers/nodes/core/NodeVar.js
  23. 29 0
      examples/jsm/renderers/nodes/core/PropertyNode.js
  24. 43 0
      examples/jsm/renderers/nodes/core/VarNode.js
  25. 118 0
      examples/jsm/renderers/nodes/functions/BSDFs.js
  26. 47 0
      examples/jsm/renderers/nodes/functions/MathFunctions.js
  27. 1 1
      examples/jsm/renderers/nodes/inputs/ColorNode.js
  28. 1 1
      examples/jsm/renderers/nodes/inputs/Matrix3Node.js
  29. 1 1
      examples/jsm/renderers/nodes/inputs/Matrix4Node.js
  30. 1 1
      examples/jsm/renderers/nodes/inputs/TextureNode.js
  31. 1 1
      examples/jsm/renderers/nodes/inputs/Vector2Node.js
  32. 1 1
      examples/jsm/renderers/nodes/inputs/Vector3Node.js
  33. 1 1
      examples/jsm/renderers/nodes/inputs/Vector4Node.js
  34. 60 0
      examples/jsm/renderers/nodes/lights/LightContextNode.js
  35. 82 0
      examples/jsm/renderers/nodes/lights/LightNode.js
  36. 44 0
      examples/jsm/renderers/nodes/lights/LightsNode.js
  37. 42 31
      examples/jsm/renderers/nodes/math/MathNode.js
  38. 2 2
      examples/jsm/renderers/webgpu/WebGPUBindings.js
  39. 6 0
      examples/jsm/renderers/webgpu/WebGPUSampledTexture.js
  40. 6 0
      examples/jsm/renderers/webgpu/WebGPUSampler.js
  41. 116 22
      examples/jsm/renderers/webgpu/nodes/ShaderLib.js
  42. 117 15
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  43. 21 0
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampledTexture.js
  44. 21 0
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampler.js
  45. BIN
      examples/screenshots/webgpu_lights_selective.jpg
  46. 181 0
      examples/webgpu_lights_selective.html

+ 1 - 0
examples/files.json

@@ -318,6 +318,7 @@
 	"webgpu": [
 		"webgpu_compute",
 		"webgpu_instance_uniform",
+		"webgpu_lights_selective",
 		"webgpu_materials",
 		"webgpu_rtt",
 		"webgpu_sandbox"

+ 14 - 28
examples/jsm/renderers/nodes/accessors/CameraNode.js

@@ -1,37 +1,27 @@
-import Node from '../core/Node.js';
-import Vector3Node from '../inputs/Vector3Node.js';
+import Object3DNode from './Object3DNode.js';
 import Matrix4Node from '../inputs/Matrix4Node.js';
-import { NodeUpdateType } from '../core/constants.js';
 
-class CameraNode extends Node {
+class CameraNode extends Object3DNode {
 
-	static POSITION = 'position';
-	static PROJECTION = 'projection';
-	static VIEW = 'view';
+	static PROJECTION_MATRIX = 'projectionMatrix';
 
 	constructor( scope = CameraNode.POSITION ) {
 
-		super();
-
-		this.updateType = NodeUpdateType.Frame;
-
-		this.scope = scope;
-
-		this._inputNode = null;
+		super( scope );
 
 	}
 
-	getType() {
+	getType( builder ) {
 
 		const scope = this.scope;
 
-		if ( scope === CameraNode.PROJECTION || scope === CameraNode.VIEW ) {
+		if ( scope === CameraNode.PROJECTION_MATRIX ) {
 
 			return 'mat4';
 
 		}
 
-		return 'vec3';
+		return super.getType( builder );
 
 	}
 
@@ -41,17 +31,17 @@ class CameraNode extends Node {
 		const inputNode = this._inputNode;
 		const scope = this.scope;
 
-		if ( scope === CameraNode.PROJECTION ) {
+		if ( scope === CameraNode.PROJECTION_MATRIX ) {
 
 			inputNode.value = camera.projectionMatrix;
 
-		} else if ( scope === CameraNode.VIEW ) {
+		} else if ( scope === CameraNode.VIEW_MATRIX ) {
 
 			inputNode.value = camera.matrixWorldInverse;
 
-		} else if ( scope === CameraNode.POSITION ) {
+		} else {
 
-			camera.getWorldPosition( inputNode.value );
+			super.update( frame );
 
 		}
 
@@ -67,7 +57,7 @@ class CameraNode extends Node {
 
 			const scope = this.scope;
 
-			if ( scope === CameraNode.PROJECTION || scope === CameraNode.VIEW ) {
+			if ( scope === CameraNode.PROJECTION_MATRIX ) {
 
 				if ( inputNode === null || inputNode.isMatrix4Node !== true ) {
 
@@ -75,13 +65,9 @@ class CameraNode extends Node {
 
 				}
 
-			} else if ( scope === CameraNode.POSITION ) {
+			} else {
 
-				if ( inputNode === null || inputNode.isVector3Node !== true ) {
-
-					inputNode = new Vector3Node();
-
-				}
+				return super.generate( builder, output );
 
 			}
 

+ 106 - 0
examples/jsm/renderers/nodes/accessors/MaterialNode.js

@@ -0,0 +1,106 @@
+import Node from '../core/Node.js';
+import OperatorNode from '../math/OperatorNode.js';
+import MaterialReferenceNode from './MaterialReferenceNode.js';
+
+class MaterialNode extends Node {
+
+	static COLOR = 'color';
+	static OPACITY = 'opacity';
+	static SPECULAR = 'specular';
+	static SHININESS = 'shininess';
+
+	constructor( scope = MaterialNode.COLOR ) {
+
+		super();
+
+		this.scope = scope;
+
+	}
+
+	getType( builder ) {
+
+		const scope = this.scope;
+		const material = builder.getContextParameter( 'material' );
+
+		if ( scope === MaterialNode.COLOR ) {
+
+			return material.map !== null ? 'vec4' : 'vec3';
+
+		} else if ( scope === MaterialNode.OPACITY ) {
+
+			return 'float';
+
+		} else if ( scope === MaterialNode.SPECULAR ) {
+
+			return 'vec3';
+
+		} else if ( scope === MaterialNode.SHININESS ) {
+
+			return 'float';
+
+		}
+
+	}
+
+	generate( builder, output ) {
+
+		const material = builder.getContextParameter( 'material' );
+		const scope = this.scope;
+
+		let node = null;
+
+		if ( scope === MaterialNode.COLOR ) {
+
+			const colorNode = new MaterialReferenceNode( 'color', 'color' );
+
+			if ( material.map !== null && material.map !== undefined && material.map.isTexture === true ) {
+
+				node = new OperatorNode( '*', colorNode, new MaterialReferenceNode( 'map', 'texture' ) );
+
+			} else {
+
+				node = colorNode;
+
+			}
+
+		} else if ( scope === MaterialNode.OPACITY ) {
+
+			const opacityNode = new MaterialReferenceNode( 'opacity', 'float' );
+
+			if ( material.alphaMap !== null && material.alphaMap !== undefined && material.alphaMap.isTexture === true ) {
+
+				node = new OperatorNode( '*', opacityNode, new MaterialReferenceNode( 'alphaMap', 'texture' ) );
+
+			} else {
+
+				node = opacityNode;
+
+			}
+
+		} else if ( scope === MaterialNode.SPECULAR ) {
+
+			const specularColorNode = new MaterialReferenceNode( 'specular', 'color' );
+
+			if ( material.specularMap !== null && material.specularMap !== undefined && material.specularMap.isTexture === true ) {
+
+				node = new OperatorNode( '*', specularColorNode, new MaterialReferenceNode( 'specularMap', 'texture' ) );
+
+			} else {
+
+				node = specularColorNode;
+
+			}
+
+		} else if ( scope === MaterialNode.SHININESS ) {
+
+			node = new MaterialReferenceNode( 'shininess', 'float' );
+
+		}
+
+		return node.build( builder, output );
+
+	}
+
+}
+
+export default MaterialNode;

+ 23 - 0
examples/jsm/renderers/nodes/accessors/MaterialReferenceNode.js

@@ -0,0 +1,23 @@
+import ReferenceNode from './ReferenceNode.js';
+
+class MaterialReferenceNode extends ReferenceNode {
+
+	constructor( property, inputType, material = null ) {
+
+		super( property, inputType, material );
+
+		this.material = material;
+
+	}
+
+	update( frame ) {
+
+		this.object = this.material !== null ? this.material : frame.material;
+
+		super.update( frame );
+
+	}
+
+}
+
+export default MaterialReferenceNode;

+ 4 - 88
examples/jsm/renderers/nodes/accessors/ModelNode.js

@@ -1,94 +1,10 @@
-import Node from '../core/Node.js';
-import Matrix4Node from '../inputs/Matrix4Node.js';
-import Matrix3Node from '../inputs/Matrix3Node.js';
-import { NodeUpdateType } from '../core/constants.js';
+import Object3DNode from './Object3DNode.js';
 
-class ModelNode extends Node {
+class ModelNode extends Object3DNode {
 
-	static VIEW = 'view';
-	static NORMAL = 'normal';
+	constructor( scope = ModelNode.VIEW_MATRIX ) {
 
-	constructor( scope = ModelNode.VIEW ) {
-
-		super();
-
-		this.scope = scope;
-
-		this.updateType = NodeUpdateType.Object;
-
-		this._inputNode = null;
-
-	}
-
-	getType() {
-
-		const scope = this.scope;
-
-		if ( scope === ModelNode.VIEW ) {
-
-			return 'mat4';
-
-		} else if ( scope === ModelNode.NORMAL ) {
-
-			return 'mat3';
-
-		}
-
-	}
-
-	update( frame ) {
-
-		const object = frame.object;
-		const inputNode = this._inputNode;
-		const scope = this.scope;
-
-		if ( scope === ModelNode.VIEW ) {
-
-			inputNode.value = object.modelViewMatrix;
-
-		} else if ( scope === ModelNode.NORMAL ) {
-
-			inputNode.value = object.normalMatrix;
-
-		}
-
-	}
-
-	generate( builder, output ) {
-
-		const nodeData = builder.getDataFromNode( this );
-
-		let inputNode = this._inputNode;
-
-		if ( nodeData.inputNode === undefined ) {
-
-			const scope = this.scope;
-
-			if ( scope === ModelNode.VIEW ) {
-
-				if ( inputNode === null || inputNode.isMatrix4Node !== true ) {
-
-					inputNode = new Matrix4Node( null );
-
-				}
-
-			} else if ( scope === ModelNode.NORMAL ) {
-
-				if ( inputNode === null || inputNode.isMatrix3Node !== true ) {
-
-					inputNode = new Matrix3Node( null );
-
-				}
-
-			}
-
-			this._inputNode = inputNode;
-
-			nodeData.inputNode = inputNode;
-
-		}
-
-		return inputNode.build( builder, output );
+		super( scope );
 
 	}
 

+ 1 - 1
examples/jsm/renderers/nodes/accessors/ModelViewProjectionNode.js

@@ -12,7 +12,7 @@ class ModelViewProjectionNode extends Node {
 
 		this.position = position;
 
-		this._mvpMatrix = new OperatorNode( '*', new CameraNode( CameraNode.PROJECTION ), new ModelNode( ModelNode.VIEW ) );
+		this._mvpMatrix = new OperatorNode( '*', new CameraNode( CameraNode.PROJECTION_MATRIX ), new ModelNode( ModelNode.VIEW_MATRIX ) );
 
 	}
 

+ 4 - 4
examples/jsm/renderers/nodes/accessors/NormalNode.js

@@ -5,6 +5,7 @@ import ModelNode from '../accessors/ModelNode.js';
 import CameraNode from '../accessors/CameraNode.js';
 import OperatorNode from '../math/OperatorNode.js';
 import MathNode from '../math/MathNode.js';
+import { inverseTransformDirection } from '../functions/MathFunctions.js';
 
 class NormalNode extends Node {
 
@@ -44,8 +45,7 @@ class NormalNode extends Node {
 
 			if ( viewNormalNode === undefined ) {
 
-				const unnormalizedWNNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL ), localNormalNode );
-				const vertexNormalNode = new MathNode( MathNode.NORMALIZE, unnormalizedWNNode );
+				const vertexNormalNode = new OperatorNode( '*', new ModelNode( ModelNode.NORMAL_MATRIX ), localNormalNode );
 
 				viewNormalNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) );
 
@@ -61,9 +61,9 @@ class NormalNode extends Node {
 
 			if ( worldNormalNode === undefined ) {
 
-				const vertexNormalNode = new MathNode( MathNode.INVERSE_TRANSFORM_DIRETION, new NormalNode( NormalNode.VIEW ), new CameraNode( CameraNode.VIEW ) );
+				const vertexNormalNode = inverseTransformDirection.call( { dir: new NormalNode( NormalNode.VIEW ), matrix: new CameraNode( CameraNode.VIEW_MATRIX ) } );
 
-				worldNormalNode = new VaryNode( vertexNormalNode );
+				worldNormalNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexNormalNode ) );
 
 				nodeData.worldNormalNode = worldNormalNode;
 

+ 129 - 0
examples/jsm/renderers/nodes/accessors/Object3DNode.js

@@ -0,0 +1,129 @@
+import Node from '../core/Node.js';
+import Matrix4Node from '../inputs/Matrix4Node.js';
+import Matrix3Node from '../inputs/Matrix3Node.js';
+import Vector3Node from '../inputs/Vector3Node.js';
+import { NodeUpdateType } from '../core/constants.js';
+
+class Object3DNode extends Node {
+
+	static VIEW_MATRIX = 'viewMatrix';
+	static NORMAL_MATRIX = 'normalMatrix';
+	static WORLD_MATRIX = 'worldMatrix';
+	static POSITION = 'position';
+	static VIEW_POSITION = 'viewPosition';
+
+	constructor( scope = Object3DNode.VIEW_MATRIX, object3d = null ) {
+
+		super();
+
+		this.scope = scope;
+		this.object3d = object3d;
+
+		this.updateType = NodeUpdateType.Object;
+
+		this._inputNode = null;
+
+	}
+
+	getType() {
+
+		const scope = this.scope;
+
+		if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) {
+
+			return 'mat4';
+
+		} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
+
+			return 'mat3';
+
+		} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) {
+
+			return 'vec3';
+
+		}
+
+	}
+
+	update( frame ) {
+
+		const object = this.object3d !== null ? this.object3d : frame.object;
+		const inputNode = this._inputNode;
+		const camera = frame.camera;
+		const scope = this.scope;
+
+		if ( scope === Object3DNode.VIEW_MATRIX ) {
+
+			inputNode.value = object.modelViewMatrix;
+
+		} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
+
+			inputNode.value = object.normalMatrix;
+
+		} else if ( scope === Object3DNode.WORLD_MATRIX ) {
+
+			inputNode.value = object.matrixWorld;
+
+		} else if ( scope === Object3DNode.POSITION ) {
+
+			inputNode.value.setFromMatrixPosition( object.matrixWorld );
+
+		} else if ( scope === Object3DNode.VIEW_POSITION ) {
+
+			inputNode.value.setFromMatrixPosition( object.matrixWorld );
+
+			inputNode.value.applyMatrix4( camera.matrixWorldInverse );
+
+		}
+
+	}
+
+	generate( builder, output ) {
+
+		const nodeData = builder.getDataFromNode( this );
+
+		let inputNode = this._inputNode;
+
+		if ( nodeData.inputNode === undefined ) {
+
+			const scope = this.scope;
+
+			if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) {
+
+				if ( inputNode === null || inputNode.isMatrix4Node !== true ) {
+
+					inputNode = new Matrix4Node( null );
+
+				}
+
+			} else if ( scope === Object3DNode.NORMAL_MATRIX ) {
+
+				if ( inputNode === null || inputNode.isMatrix3Node !== true ) {
+
+					inputNode = new Matrix3Node( null );
+
+				}
+
+			} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) {
+
+				if ( inputNode === null || inputNode.isVector3Node !== true ) {
+
+					inputNode = new Vector3Node();
+
+				}
+
+			}
+
+			this._inputNode = inputNode;
+
+			nodeData.inputNode = inputNode;
+
+		}
+
+		return inputNode.build( builder, output );
+
+	}
+
+}
+
+export default Object3DNode;

+ 66 - 5
examples/jsm/renderers/nodes/accessors/PositionNode.js

@@ -1,9 +1,17 @@
 import Node from '../core/Node.js';
 import AttributeNode from '../core/AttributeNode.js';
+import VaryNode from '../core/VaryNode.js';
+import ModelNode from '../accessors/ModelNode.js';
+import MathNode from '../math/MathNode.js';
+import OperatorNode from '../math/OperatorNode.js';
+import { transformDirection } from '../functions/MathFunctions.js';
 
 class PositionNode extends Node {
 
 	static LOCAL = 'local';
+	static WORLD = 'world';
+	static VIEW = 'view';
+	static VIEW_DIRECTION = 'viewDirection';
 
 	constructor( scope = PositionNode.POSITION ) {
 
@@ -17,18 +25,71 @@ class PositionNode extends Node {
 
 		const type = this.getType( builder );
 		const nodeData = builder.getDataFromNode( this, builder.shaderStage );
+		const scope = this.scope;
 
-		let positionNode = nodeData.positionNode;
+		let localPositionNode = nodeData.localPositionNode;
 
-		if ( positionNode === undefined ) {
+		if ( localPositionNode === undefined ) {
 
-			positionNode = new AttributeNode( 'position', 'vec3' );
+			localPositionNode = new AttributeNode( 'position', 'vec3' );
 
-			nodeData.positionNode = positionNode;
+			nodeData.localPositionNode = localPositionNode;
 
 		}
 
-		const positionSnipped = positionNode.build( builder, type );
+		let outputNode = localPositionNode;
+
+		if ( scope === PositionNode.WORLD ) {
+
+			let worldPositionNode = nodeData.worldPositionNode;
+
+			if ( worldPositionNode === undefined ) {
+
+				const vertexPositionNode = transformDirection.call( { dir: localPositionNode, matrix: new ModelNode( ModelNode.WORLD_MATRIX ) } );
+
+				worldPositionNode = new VaryNode( vertexPositionNode );
+
+				nodeData.worldPositionNode = worldPositionNode;
+
+			}
+
+			outputNode = worldPositionNode;
+
+		} else if ( scope === PositionNode.VIEW ) {
+
+			let viewPositionNode = nodeData.viewPositionNode;
+
+			if ( viewPositionNode === undefined ) {
+
+				const vertexPositionNode = new OperatorNode( '*', new ModelNode( ModelNode.VIEW_MATRIX ), localPositionNode );
+
+				viewPositionNode = new VaryNode( vertexPositionNode );
+
+				nodeData.viewPositionNode = viewPositionNode;
+
+			}
+
+			outputNode = viewPositionNode;
+
+		} else if ( scope === PositionNode.VIEW_DIRECTION ) {
+
+			let viewDirPositionNode = nodeData.viewDirPositionNode;
+
+			if ( viewDirPositionNode === undefined ) {
+
+				const vertexPositionNode = new MathNode( MathNode.NEGATE, new PositionNode( PositionNode.VIEW ) );
+
+				viewDirPositionNode = new MathNode( MathNode.NORMALIZE, new VaryNode( vertexPositionNode ) );
+
+				nodeData.viewDirPositionNode = viewDirPositionNode;
+
+			}
+
+			outputNode = viewDirPositionNode;
+
+		}
+
+		const positionSnipped = outputNode.build( builder, type );
 
 		return builder.format( positionSnipped, type, output );
 

+ 91 - 0
examples/jsm/renderers/nodes/accessors/ReferenceNode.js

@@ -0,0 +1,91 @@
+import Node from '../core/Node.js';
+import FloatNode from '../inputs/FloatNode.js';
+import Vector2Node from '../inputs/Vector2Node.js';
+import Vector3Node from '../inputs/Vector3Node.js';
+import Vector4Node from '../inputs/Vector4Node.js';
+import ColorNode from '../inputs/ColorNode.js';
+import TextureNode from '../inputs/TextureNode.js';
+import { NodeUpdateType } from '../core/constants.js';
+
+class ReferenceNode extends Node {
+
+	constructor( property, inputType, object = null ) {
+
+		super();
+
+		this.property = property;
+		this.inputType = inputType;
+
+		this.object = object;
+
+		this.node = null;
+
+		this.updateType = NodeUpdateType.Object;
+
+		this.setNodeType( inputType );
+
+	}
+
+	setNodeType( inputType ) {
+
+		let node = null;
+		let type = inputType;
+
+		if ( type === 'float' ) {
+
+			node = new FloatNode();
+
+		} else if ( type === 'vec2' ) {
+
+			node = new Vector2Node( null );
+
+		} else if ( type === 'vec3' ) {
+
+			node = new Vector3Node( null );
+
+		} else if ( type === 'vec4' ) {
+
+			node = new Vector4Node( null );
+
+		} else if ( type === 'color' ) {
+
+			node = new ColorNode( null );
+			type = 'vec3';
+
+		} else if ( type === 'texture' ) {
+
+			node = new TextureNode();
+			type = 'vec4';
+
+		}
+
+		this.node = node;
+		this.type = type;
+		this.inputType = inputType;
+
+	}
+
+	getNodeType() {
+
+		return this.inputType;
+
+	}
+
+	update( frame ) {
+
+		const object = this.object !== null ? this.object : frame.object;
+		const value = object[ this.property ];
+
+		this.node.value = value;
+
+	}
+
+	generate( builder, output ) {
+
+		return this.node.build( builder, output );
+
+	}
+
+}
+
+export default ReferenceNode;

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

@@ -0,0 +1,4 @@
+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' );

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

@@ -2,17 +2,17 @@ import Node from './Node.js';
 
 class AttributeNode extends Node {
 
-	constructor( name, type ) {
+	constructor( attributeName, type ) {
 
 		super( type );
 
-		this.name = name;
+		this._attributeName = attributeName;
 
 	}
 
-	setAttributeName( name ) {
+	setAttributeName( attributeName ) {
 
-		this.name = name;
+		this._attributeName = attributeName;
 
 		return this;
 
@@ -20,7 +20,7 @@ class AttributeNode extends Node {
 
 	getAttributeName( /*builder*/ ) {
 
-		return this.name;
+		return this._attributeName;
 
 	}
 

+ 80 - 0
examples/jsm/renderers/nodes/core/CodeNode.js

@@ -0,0 +1,80 @@
+import Node from './Node.js';
+
+class CodeNode extends Node {
+
+	constructor( code = '', type = 'code' ) {
+
+		super( type );
+
+		this.code = code;
+
+		this.useKeywords = false;
+
+		this._includes = [];
+
+		Object.defineProperty( this, 'isCodeNode', { value: true } );
+
+	}
+
+	setIncludes( includes ) {
+
+		this._includes = includes;
+
+		return this;
+
+	}
+
+	getIncludes( /*builder*/ ) {
+
+		return this._includes;
+
+	}
+
+	generate( builder, output ) {
+
+		if ( this.useKeywords === true ) {
+
+			const contextKeywords = builder.getContextParameter( 'keywords' );
+
+			if ( contextKeywords !== undefined ) {
+
+				const nodeData = builder.getDataFromNode( this, builder.shaderStage );
+
+				if ( nodeData.keywords === undefined ) {
+
+					nodeData.keywords = [];
+
+				}
+
+				if ( nodeData.keywords.indexOf( contextKeywords ) === - 1 ) {
+
+					contextKeywords.include( builder, this.code );
+
+					nodeData.keywords.push( contextKeywords );
+
+				}
+
+			}
+
+		}
+
+		const includes = this.getIncludes( builder );
+
+		for ( const include of includes ) {
+
+			include.build( builder );
+
+		}
+
+		const type = this.getType( builder );
+		const nodeCode = builder.getCodeFromNode( this, type );
+
+		nodeCode.code = this.code;
+
+		return nodeCode.code;
+
+	}
+
+}
+
+export default CodeNode;

+ 40 - 0
examples/jsm/renderers/nodes/core/ConstNode.js

@@ -0,0 +1,40 @@
+import CodeNode from './CodeNode.js';
+
+class ConstNode extends CodeNode {
+
+	constructor( code = '', type = '', name = '' ) {
+
+		super( code, type );
+
+		this.includes = [];
+
+		this.name = name;
+
+	}
+
+	generate( builder, output ) {
+
+		const code = super.generate( builder );
+
+		const type = this.getType( builder );
+		const nodeCode = builder.getCodeFromNode( this, type );
+
+		if ( this.name !== '' ) {
+
+			// use a custom property name
+
+			nodeCode.name = this.name;
+
+		}
+
+		const propertyName = builder.getPropertyName( nodeCode );
+
+		nodeCode.code = `#define ${propertyName} ${code}`;
+
+		return builder.format( propertyName, type, output );
+
+	}
+
+}
+
+export default ConstNode;

+ 53 - 0
examples/jsm/renderers/nodes/core/ContextNode.js

@@ -0,0 +1,53 @@
+import Node from './Node.js';
+
+class ContextNode extends Node {
+
+	constructor( node, parameters = {} ) {
+
+		super();
+
+		this.node = node;
+
+		this.parameters = parameters;
+
+		Object.defineProperty( this, 'isContextNode', { value: true } );
+
+	}
+
+	setParameter( name, value ) {
+
+		this.parameters[ name ] = value;
+
+		return this;
+
+	}
+
+	getParameter( name ) {
+
+		return this.parameters[ name ];
+
+	}
+
+	getType( builder ) {
+
+		return this.node.getType( builder );
+
+	}
+
+	generate( builder, output ) {
+
+		const previousContext = builder.getContext();
+
+		builder.setContext( Object.assign( {}, builder.context, this.parameters ) );
+
+		const snippet = this.node.build( builder, output );
+
+		builder.setContext( previousContext );
+
+		return snippet;
+
+	}
+
+}
+
+export default ContextNode;

+ 70 - 0
examples/jsm/renderers/nodes/core/FunctionCallNode.js

@@ -0,0 +1,70 @@
+import Node from './Node.js';
+
+class FunctionCallNode extends Node {
+
+	constructor( functionNode = null, parameters = {} ) {
+
+		super();
+
+		this.functionNode = functionNode;
+		this.parameters = parameters;
+
+	}
+
+	setParameters( parameters ) {
+
+		this.parameters = parameters;
+
+		return this;
+
+	}
+
+	getParameters() {
+
+		return this.parameters;
+
+	}
+
+	getType( builder ) {
+
+		return this.functionNode.getType( builder );
+
+	}
+
+	generate( builder, output ) {
+
+		const params = [];
+
+		const functionNode = this.functionNode;
+
+		const inputs = functionNode.getInputs( builder );
+		const parameters = this.parameters;
+
+		for ( const inputNode of inputs ) {
+
+			const node = parameters[ inputNode.name ];
+
+			if ( node !== undefined ) {
+
+				params.push( node.build( builder, inputNode.type ) );
+
+			} else {
+
+				throw new Error( `FunctionCallNode: Input '${inputNode.name}' not found in FunctionNode.` );
+
+			}
+
+		}
+
+		const type = this.getType( builder );
+		const functionName = functionNode.build( builder, 'property' );
+
+		const callSnippet = `${functionName}( ${params.join( ', ' )} )`;
+
+		return builder.format( callSnippet, type, output );
+
+	}
+
+}
+
+export default FunctionCallNode;

+ 194 - 0
examples/jsm/renderers/nodes/core/FunctionNode.js

@@ -0,0 +1,194 @@
+import CodeNode from './CodeNode.js';
+import NodeFunctionInput from './NodeFunctionInput.js';
+import FunctionCallNode from './FunctionCallNode.js';
+
+const declarationRegexp = /^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\((.*?)\)/i;
+const propertiesRegexp = /[a-z_0-9]+/ig;
+
+const pragmaMain = '#pragma main';
+
+class FunctionNode extends CodeNode {
+
+	constructor( code = '' ) {
+
+		super( code );
+
+		this.inputs = [];
+
+		this.name = '';
+		this.needsUpdate = true;
+
+		this.useKeywords = true;
+
+		this.presicion = '';
+
+		this._includeCode = '';
+		this._internalCode = '';
+
+	}
+
+	getType( /*builder*/ ) {
+
+		if ( this.needsUpdate === true ) {
+
+			this.parse();
+
+		}
+
+		return this.type;
+
+	}
+
+	getInputs( /*builder*/ ) {
+
+		if ( this.needsUpdate === true ) {
+
+			this.parse();
+
+		}
+
+		return this.inputs;
+
+	}
+
+	parse() {
+
+		const code = this.code;
+
+		const pragmaMainIndex = code.indexOf( pragmaMain );
+
+		const mainCode = pragmaMainIndex !== - 1 ? code.substr( pragmaMainIndex + pragmaMain.length ) : code;
+
+		const declaration = mainCode.match( declarationRegexp );
+
+		if ( declaration !== null && declaration.length === 5 ) {
+
+			// tokenizer
+
+			const paramsCode = declaration[ 4 ];
+			const propsMatches = [];
+
+			let nameMatch = null;
+
+			while ( ( nameMatch = propertiesRegexp.exec( paramsCode ) ) !== null ) {
+
+				propsMatches.push( nameMatch );
+
+			}
+
+			// parser
+
+			const inputs = [];
+
+			let i = 0;
+
+			while ( i < propsMatches.length ) {
+
+				const isConst = propsMatches[ i ][ 0 ] === 'const';
+
+				if ( isConst === true ) {
+
+					i ++;
+
+				}
+
+				let qualifier = propsMatches[ i ][ 0 ];
+
+				if ( qualifier === 'in' || qualifier === 'out' || qualifier === 'inout' ) {
+
+					i ++;
+
+				} else {
+
+					qualifier = '';
+
+				}
+
+				const type = propsMatches[ i ++ ][ 0 ];
+				const name = propsMatches[ i ++ ][ 0 ];
+
+				inputs.push( new NodeFunctionInput( type, name, qualifier, isConst ) );
+
+			}
+
+			const blockCode = mainCode.substring( declaration[ 0 ].length );
+
+			this.name = declaration[ 3 ] !== undefined ? declaration[ 3 ] : '';
+			this.type = declaration[ 2 ];
+
+			this.presicion = declaration[ 1 ] !== undefined ? declaration[ 1 ] : '';
+
+			this.inputs = inputs;
+
+			this._includeCode = pragmaMainIndex !== - 1 ? code.substr( 0, pragmaMainIndex ) : '';
+			this._internalCode = `( ${paramsCode} ) ${blockCode}`;
+
+		} else {
+
+			throw new Error( 'FunctionNode: Function is not a GLSL code.' );
+
+		}
+
+		this.code = code;
+
+		this.needsUpdate = false;
+
+	}
+
+	call( parameters = null ) {
+
+		return new FunctionCallNode( this, parameters );
+
+	}
+
+	generate( builder, output ) {
+
+		super.generate( builder );
+
+		const type = this.getType( builder );
+		const nodeCode = builder.getCodeFromNode( this, type );
+
+		if ( this.name !== '' ) {
+
+			// use a custom property name
+
+			nodeCode.name = this.name;
+
+		}
+
+		const propertyName = builder.getPropertyName( nodeCode );
+
+		const presicion = this.presicion;
+		const includeCode = this._includeCode;
+
+		let code = `${type} ${propertyName} ${this._internalCode}`;
+
+		if ( presicion !== '' ) {
+
+			code = `${presicion} ${code}`;
+
+		}
+
+		if ( includeCode !== '' ) {
+
+			code = `${includeCode} ${code}`;
+
+		}
+
+		nodeCode.code = code;
+
+		if ( output === 'property' ) {
+
+			return propertyName;
+
+		} else {
+
+			return builder.format( `${propertyName}()`, type, output );
+
+		}
+
+	}
+
+}
+
+export default FunctionNode;

+ 139 - 8
examples/jsm/renderers/nodes/core/NodeBuilder.js

@@ -1,6 +1,9 @@
 import NodeUniform from './NodeUniform.js';
 import NodeAttribute from './NodeAttribute.js';
 import NodeVary from './NodeVary.js';
+import NodeVar from './NodeVar.js';
+import NodeCode from './NodeCode.js';
+import NodeKeywords from './NodeKeywords.js';
 import { NodeUpdateType } from './constants.js';
 
 class NodeBuilder {
@@ -19,8 +22,16 @@ class NodeBuilder {
 		this.slots = { vertex: [], fragment: [] };
 		this.defines = { vertex: {}, fragment: {} };
 		this.uniforms = { vertex: [], fragment: [] };
+		this.nodeCodes = { vertex: [], fragment: [] };
+		this.vars = { vertex: [], fragment: [] };
 		this.attributes = [];
 		this.varys = [];
+		this.flow = { code: '' };
+
+		this.context = {
+			keywords: new NodeKeywords(),
+			material: material
+		};
 
 		this.nodesData = new WeakMap();
 
@@ -58,6 +69,24 @@ class NodeBuilder {
 
 	}
 
+	setContext( context ) {
+
+		this.context = context;
+
+	}
+
+	getContext() {
+
+		return this.context;
+
+	}
+
+	getContextParameter( name ) {
+
+		return this.context[ name ];
+
+	}
+
 	getTexture( /* textureProperty, uvSnippet */ ) {
 
 		console.warn( 'Abstract function.' );
@@ -72,7 +101,7 @@ class NodeBuilder {
 		if ( type === 'vec4' ) return `vec4( ${value.x}, ${value.y}, ${value.z}, ${value.w} )`;
 		if ( type === 'color' ) return `vec3( ${value.r}, ${value.g}, ${value.b} )`;
 
-		throw new Error( `Type '${type}' not found in generate constant attempt.` );
+		throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` );
 
 	}
 
@@ -129,7 +158,7 @@ class NodeBuilder {
 	getVectorType( type ) {
 
 		if ( type === 'color' ) return 'vec3';
-		else if ( type === 'texture' ) return 'vec4';
+		if ( type === 'texture' ) return 'vec4';
 
 		return type;
 
@@ -186,7 +215,7 @@ class NodeBuilder {
 			const uniforms = this.uniforms[ shaderStage ];
 			const index = uniforms.length;
 
-			nodeUniform = new NodeUniform( 'nodeU' + index, type, node );
+			nodeUniform = new NodeUniform( 'nodeUniform' + index, type, node );
 
 			uniforms.push( nodeUniform );
 
@@ -198,6 +227,29 @@ class NodeBuilder {
 
 	}
 
+	getVarFromNode( node, type, shaderStage = this.shaderStage ) {
+
+		const nodeData = this.getDataFromNode( node, shaderStage );
+
+		let nodeVar = nodeData.variable;
+
+		if ( nodeVar === undefined ) {
+
+			const vars = this.vars[ shaderStage ];
+			const index = vars.length;
+
+			nodeVar = new NodeVar( 'nodeVar' + index, type );
+
+			vars.push( nodeVar );
+
+			nodeData.variable = nodeVar;
+
+		}
+
+		return nodeVar;
+
+	}
+
 	getVaryFromNode( node, type ) {
 
 		const nodeData = this.getDataFromNode( node );
@@ -209,7 +261,7 @@ class NodeBuilder {
 			const varys = this.varys;
 			const index = varys.length;
 
-			nodeVary = new NodeVary( 'nodeV' + index, type );
+			nodeVary = new NodeVary( 'nodeVary' + index, type );
 
 			varys.push( nodeVary );
 
@@ -221,6 +273,29 @@ class NodeBuilder {
 
 	}
 
+	getCodeFromNode( node, type, shaderStage = this.shaderStage ) {
+
+		const nodeData = this.getDataFromNode( node );
+
+		let nodeCode = nodeData.code;
+
+		if ( nodeCode === undefined ) {
+
+			const nodeCodes = this.nodeCodes[ shaderStage ];
+			const index = nodeCodes.length;
+
+			nodeCode = new NodeCode( 'nodeCode' + index, type );
+
+			nodeCodes.push( nodeCode );
+
+			nodeData.code = nodeCode;
+
+		}
+
+		return nodeCode;
+
+	}
+
 	/*
 	analyzeNode( node ) {
 
@@ -228,12 +303,33 @@ class NodeBuilder {
 	}
 	*/
 
+	addFlowCode( code ) {
+
+		if ( ! /;\s*$/.test( code ) ) {
+
+			code += '; ';
+
+		}
+
+		this.flow.code += code;
+
+	}
+
 	flowNode( node, output ) {
 
-		const flowData = {};
-		flowData.result = node.build( this, output );
+		const previousFlow = this.flow;
 
-		return flowData;
+		const flow = {
+			code: previousFlow.code,
+		};
+
+		this.flow = flow;
+
+		flow.result = node.build( this, output );
+
+		this.flow = previousFlow;
+
+		return flow;
 
 	}
 
@@ -277,12 +373,40 @@ class NodeBuilder {
 
 	}
 
+	getVarsHeaderSnippet( /*shaderStage*/ ) {
+
+		console.warn( 'Abstract function.' );
+
+	}
+
+	getVarsBodySnippet( /*shaderStage*/ ) {
+
+		console.warn( 'Abstract function.' );
+
+	}
+
 	getUniformsHeaderSnippet( /*shaderStage*/ ) {
 
 		console.warn( 'Abstract function.' );
 
 	}
 
+	getUniformsHeaderCodes( shaderStage ) {
+
+		const nodeCodes = this.nodeCodes[ shaderStage ];
+
+		let code = '';
+
+		for ( const nodeCode of nodeCodes ) {
+
+			code += nodeCode.code + '\n';
+
+		}
+
+		return code;
+
+	}
+
 	getHash() {
 
 		return this.vertexShader + this.fragmentShader;
@@ -304,6 +428,7 @@ class NodeBuilder {
 
 				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 );
 
 			}
@@ -319,8 +444,14 @@ class NodeBuilder {
 			this.define( shaderStage, 'NODE_HEADER_VARYS', this.getVarysHeaderSnippet( shaderStage ) );
 
 			this.define( shaderStage, 'NODE_BODY_VARYS', this.getVarysBodySnippet( shaderStage ) );
+			this.define( shaderStage, 'NODE_BODY_VARS', this.getVarsBodySnippet( shaderStage ) );
+
+			let headerCode = '';
+
+			headerCode += this.getVarsHeaderSnippet( shaderStage ) + '\n';
+			headerCode += this.getUniformsHeaderCodes( shaderStage );
 
-			shaderData[ shaderStage ] = this._buildDefines( shaderStage );
+			shaderData[ shaderStage ] = this._buildDefines( shaderStage ) + '\n' + headerCode;
 
 		}
 

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

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

+ 16 - 0
examples/jsm/renderers/nodes/core/NodeFunctionInput.js

@@ -0,0 +1,16 @@
+class NodeFunctionInput {
+
+	constructor( type, name, qualifier = '', isConst = false ) {
+
+		this.type = type;
+		this.name = name;
+		this.qualifier = qualifier;
+		this.isConst = isConst;
+
+		Object.defineProperty( this, 'isNodeFunction', { value: true } );
+
+	}
+
+}
+
+export default NodeFunctionInput;

+ 229 - 0
examples/jsm/renderers/nodes/core/NodeKeywords.js

@@ -0,0 +1,229 @@
+import VarNode from './VarNode.js';
+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 { saturateMacro, whiteComplementMacro } from '../functions/MathFunctions.js';
+
+class NodeKeywords {
+
+	static PI = 'PI';
+	static RECIPROCAL_PI = 'RECIPROCAL_PI';
+
+	static Saturate = 'saturate';
+	static WhiteComplement = 'whiteComplement';
+
+	static PositionLocal = 'PositionLocal';
+	static PositionWorld = 'PositionWorld';
+	static PositionView = 'PositionView';
+	static PositionViewDirection = 'PositionViewDirection';
+
+	static NormalLocal = 'NormalLocal';
+	static NormalWorld = 'NormalWorld';
+	static NormalView = 'NormalView';
+
+	static Irradiance = 'Irradiance';
+	static ReflectedLightIndirectDiffuse = 'ReflectedLightIndirectDiffuse';
+	static ReflectedLightIndirectSpecular = 'ReflectedLightIndirectSpecular';
+	static ReflectedLightDirectDiffuse = 'ReflectedLightDirectDiffuse';
+	static ReflectedLightDirectSpecular = 'ReflectedLightDirectSpecular';
+
+	static MaterialDiffuseColor = 'MaterialDiffuseColor';
+
+	static MaterialSpecularShininess = 'MaterialSpecularShininess';
+	static MaterialSpecularColor = 'MaterialSpecularColor';
+
+	constructor() {
+
+		this.keywords = [
+			// consts
+			NodeKeywords.PI,
+			NodeKeywords.RECIPROCAL_PI,
+			// variadic macros
+			NodeKeywords.Saturate,
+			NodeKeywords.WhiteComplement,
+			// nodes
+			NodeKeywords.PositionLocal,
+			NodeKeywords.PositionWorld,
+			NodeKeywords.PositionView,
+			NodeKeywords.PositionViewDirection,
+			NodeKeywords.NormalLocal,
+			NodeKeywords.NormalWorld,
+			NodeKeywords.NormalView,
+			// vars -> float
+			NodeKeywords.MaterialSpecularShininess,
+			// vars -> vec3
+			NodeKeywords.Irradiance,
+			NodeKeywords.ReflectedLightIndirectDiffuse,
+			NodeKeywords.ReflectedLightIndirectSpecular,
+			NodeKeywords.ReflectedLightDirectDiffuse,
+			NodeKeywords.ReflectedLightDirectSpecular,
+			NodeKeywords.MaterialSpecularColor,
+			// vars -> vec4
+			NodeKeywords.MaterialDiffuseColor
+		];
+
+		this.nodes = [];
+
+	}
+
+	getNode( name ) {
+
+		let node = this.nodes[ name ];
+
+		if ( node === undefined ) {
+
+			switch ( name ) {
+
+				case NodeKeywords.PI:
+
+					node = PI;
+
+					break;
+
+				case NodeKeywords.RECIPROCAL_PI:
+
+					node = RECIPROCAL_PI;
+
+					break;
+
+				case NodeKeywords.Saturate:
+
+					node = saturateMacro;
+
+					break;
+
+				case NodeKeywords.WhiteComplement:
+
+					node = whiteComplementMacro;
+
+					break;
+
+				case NodeKeywords.PositionLocal:
+
+					node = new VarNode( new PositionNode( PositionNode.LOCAL ), name );
+
+					break;
+
+				case NodeKeywords.PositionWorld:
+
+					node = new VarNode( new PositionNode( PositionNode.WORLD ), name );
+
+					break;
+
+				case NodeKeywords.PositionView:
+
+					node = new VarNode( new PositionNode( PositionNode.VIEW ), name );
+
+					break;
+
+				case NodeKeywords.PositionViewDirection:
+
+					node = new VarNode( new PositionNode( PositionNode.VIEW_DIRECTION ), name );
+
+					break;
+
+				case NodeKeywords.NormalLocal:
+
+					node = new VarNode( new NormalNode( NormalNode.LOCAL ), name );
+
+					break;
+
+				case NodeKeywords.NormalWorld:
+
+					node = new VarNode( new NormalNode( NormalNode.WORLD ), name );
+
+					break;
+
+				case NodeKeywords.NormalView:
+
+					node = new VarNode( new NormalNode( NormalNode.VIEW ), name );
+
+					break;
+
+				// floats properties
+				case NodeKeywords.MaterialSpecularShininess:
+
+					node = new PropertyNode( name, 'float' );
+
+					break;
+
+				// vec3 properties
+				case NodeKeywords.Irradiance:
+				case NodeKeywords.ReflectedLightIndirectDiffuse:
+				case NodeKeywords.ReflectedLightIndirectSpecular:
+				case NodeKeywords.ReflectedLightDirectDiffuse:
+				case NodeKeywords.ReflectedLightDirectSpecular:
+				case NodeKeywords.MaterialSpecularColor:
+
+					node = new PropertyNode( name, 'vec3' );
+
+					break;
+
+				// vec4 properties
+				case NodeKeywords.MaterialDiffuseColor:
+
+					node = new PropertyNode( name, 'vec4' );
+
+					break;
+
+			}
+
+			if ( node !== undefined ) {
+
+				this.nodes[ name ] = node;
+
+			}
+
+		}
+
+		return node;
+
+	}
+
+	parse( code ) {
+
+		const keywordNames = this.keywords;
+
+		const regExp = new RegExp( `\\b${keywordNames.join( '\\b|\\b' )}\\b`, 'g' );
+
+		const codeKeywords = code.match( regExp );
+
+		const keywords = [];
+
+		if ( codeKeywords !== null ) {
+
+			for ( const keyword of codeKeywords ) {
+
+				const node = this.getNode( keyword );
+
+				if ( keywords.indexOf( node ) === - 1 ) {
+
+					keywords.push( node );
+
+				}
+
+			}
+
+		}
+
+		return keywords;
+
+	}
+
+	include( builder, code ) {
+
+		const keywords = this.parse( code );
+
+		for ( const keywordNode of keywords ) {
+
+			keywordNode.build( builder );
+
+		}
+
+	}
+
+}
+
+export default NodeKeywords;

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

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

+ 29 - 0
examples/jsm/renderers/nodes/core/PropertyNode.js

@@ -0,0 +1,29 @@
+import Node from './Node.js';
+
+class PropertyNode extends Node {
+
+	constructor( name, type ) {
+
+		super();
+
+		this.name = name;
+		this.type = type;
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		const nodeVary = builder.getVarFromNode( this, type );
+		nodeVary.name = this.name;
+
+		const propertyName = builder.getPropertyName( nodeVary );
+
+		return builder.format( propertyName, type, output );
+
+	}
+
+}
+
+export default PropertyNode;

+ 43 - 0
examples/jsm/renderers/nodes/core/VarNode.js

@@ -0,0 +1,43 @@
+import Node from './Node.js';
+
+class VarNode extends Node {
+
+	constructor( value, name = '' ) {
+
+		super();
+
+		this.value = value;
+		this.name = name;
+
+	}
+
+	getType( builder ) {
+
+		return this.value.getType( builder );
+
+	}
+
+	generate( builder, output ) {
+
+		const snippet = this.value.build( builder );
+
+		const type = this.getType( builder );
+
+		const nodeVary = builder.getVarFromNode( this, type );
+		nodeVary.snippet = snippet;
+
+		if ( this.name !== '' ) {
+
+			nodeVary.name = this.name;
+
+		}
+
+		const propertyName = builder.getPropertyName( nodeVary );
+
+		return builder.format( propertyName, type, output );
+
+	}
+
+}
+
+export default VarNode;

+ 118 - 0
examples/jsm/renderers/nodes/functions/BSDFs.js

@@ -0,0 +1,118 @@
+import FunctionNode from '../core/FunctionNode.js';
+
+export const F_Schlick = new FunctionNode( `
+vec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {
+
+	// Original approximation by Christophe Schlick '94
+	// float fresnel = pow( 1.0 - dotLH, 5.0 );
+
+	// Optimized variant (presented by Epic at SIGGRAPH '13)
+	// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
+	float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );
+
+	return ( 1.0 - specularColor ) * fresnel + specularColor;
+
+}` ); // validated
+
+export const G_BlinnPhong_Implicit = new FunctionNode( `
+float G_BlinnPhong_Implicit() {
+
+	// ( const in float dotNL, const in float dotNV )
+	// geometry term is (n dot l)(n dot v) / 4(n dot l)(n dot v)
+
+	return 0.25;
+
+}` ); // 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_Diffuse_Lambert = new FunctionNode( `
+vec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {
+
+	return RECIPROCAL_PI * diffuseColor;
+
+}` ); // validated
+
+export const BRDF_Specular_BlinnPhong = new FunctionNode( `
+vec3 BRDF_Specular_BlinnPhong( vec3 lightDirection, vec3 specularColor, float shininess ) {
+
+	vec3 halfDir = normalize( lightDirection + PositionViewDirection );
+
+	//float dotNL = saturate( dot( NormalView, lightDirection ) );
+	//float dotNV = saturate( dot( NormalView, PositionViewDirection ) );
+	float dotNH = saturate( dot( NormalView, halfDir ) );
+	float dotLH = saturate( dot( lightDirection, halfDir ) );
+
+	vec3 F = F_Schlick( specularColor, dotLH );
+
+	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 ) {
+
+#if defined ( PHYSICALLY_CORRECT_LIGHTS )
+
+	// based upon Frostbite 3 Moving to Physically-based Rendering
+	// page 32, equation 26: E[window1]
+	// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+	// this is intended to be used on spot and point lights who are represented as luminous intensity
+	// but who must be converted to luminous irradiance for surface lighting calculation
+	float distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );
+
+	if( cutoffDistance > 0.0 ) {
+
+		distanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );
+
+	}
+
+	return distanceFalloff;
+
+#else
+
+	if( cutoffDistance > 0.0 && decayExponent > 0.0 ) {
+
+		return pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );
+
+	}
+
+	return 1.0;
+
+#endif
+
+}` );
+
+export const RE_Direct_BlinnPhong = new FunctionNode( `
+void RE_Direct_BlinnPhong( vec3 lightDirection, vec3 lightColor ) {
+
+	float dotNL = saturate( dot( NormalView, lightDirection ) );
+	vec3 irradiance = dotNL * lightColor;
+
+#ifndef PHYSICALLY_CORRECT_LIGHTS
+
+		irradiance *= PI; // punctual light
+
+#endif
+
+	ReflectedLightDirectDiffuse += irradiance * BRDF_Diffuse_Lambert( MaterialDiffuseColor.rgb );
+
+	ReflectedLightDirectSpecular += irradiance * BRDF_Specular_BlinnPhong( lightDirection, MaterialSpecularColor, MaterialSpecularShininess );
+
+}` ).setIncludes( [ BRDF_Diffuse_Lambert, BRDF_Specular_BlinnPhong ] );
+
+export const RE_IndirectDiffuse_BlinnPhong = new FunctionNode( `
+void RE_IndirectDiffuse_BlinnPhong( ) {
+
+	ReflectedLightIndirectDiffuse += Irradiance * BRDF_Diffuse_Lambert( MaterialDiffuseColor.rgb );
+
+}` ).setIncludes( [ BRDF_Diffuse_Lambert ] );

+ 47 - 0
examples/jsm/renderers/nodes/functions/MathFunctions.js

@@ -0,0 +1,47 @@
+import { PI } from '../consts/MathConsts.js';
+import CodeNode from '../core/CodeNode.js';
+import FunctionNode from '../core/FunctionNode.js';
+
+// variadic macros
+export const saturateMacro = new CodeNode( '#define saturate(a) clamp( a, 0.0, 1.0 )' );
+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 ) {
+
+	return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
+
+}` );
+
+export const inverseTransformDirection = new FunctionNode( `
+vec3 inverseTransformDirection( 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
+
+	return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );
+
+}` );
+
+export const pow2 = new FunctionNode( `float pow2( const in float x ) { return x*x; }` );
+export const pow3 = new FunctionNode( `float pow3( const in float x ) { return x*x*x; }` );
+export const pow4 = new FunctionNode( `float pow4( const in float x ) { float x2 = x*x; return x2*x2; }` );
+
+export const average = new FunctionNode( `float average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }` );
+
+export const max3 = new FunctionNode( `float max3( vec3 v ) { return max( max( v.x, v.y ), v.z ); }` );
+
+// expects values in the range of [0,1]x[0,1], returns values in the [0,1] range.
+// do not collapse into a single function per: http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/
+export const rand = new FunctionNode( `
+highp float rand( const in vec2 uv ) {
+
+	const highp float a = 12.9898, b = 78.233, c = 43758.5453;
+
+	highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, 3.141592653589793 );
+
+	return fract(sin(sn) * c);
+
+}` ).setIncludes( [ PI ] );
+
+export const precisionSafeLength = new FunctionNode( `float precisionSafeLength( vec3 v ) { return length( v ); }` );

+ 1 - 1
examples/jsm/renderers/nodes/inputs/ColorNode.js

@@ -1,5 +1,5 @@
 import InputNode from '../core/InputNode.js';
-import { Color } from '../../../../../build/three.module.js';
+import { Color } from 'three';
 
 class ColorNode extends InputNode {
 

+ 1 - 1
examples/jsm/renderers/nodes/inputs/Matrix3Node.js

@@ -1,5 +1,5 @@
 import InputNode from '../core/InputNode.js';
-import { Matrix3 } from '../../../../../build/three.module.js';
+import { Matrix3 } from 'three';
 
 class Matrix3Node extends InputNode {
 

+ 1 - 1
examples/jsm/renderers/nodes/inputs/Matrix4Node.js

@@ -1,5 +1,5 @@
 import InputNode from '../core/InputNode.js';
-import { Matrix4 } from '../../../../../build/three.module.js';
+import { Matrix4 } from 'three';
 
 class Matrix4Node extends InputNode {
 

+ 1 - 1
examples/jsm/renderers/nodes/inputs/TextureNode.js

@@ -3,7 +3,7 @@ import UVNode from '../accessors/UVNode.js';
 
 class TextureNode extends InputNode {
 
-	constructor( value, uv = new UVNode() ) {
+	constructor( value = null, uv = new UVNode() ) {
 
 		super( 'texture' );
 

+ 1 - 1
examples/jsm/renderers/nodes/inputs/Vector2Node.js

@@ -1,5 +1,5 @@
 import InputNode from '../core/InputNode.js';
-import { Vector2 } from '../../../../../build/three.module.js';
+import { Vector2 } from 'three';
 
 class Vector2Node extends InputNode {
 

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

@@ -1,5 +1,5 @@
 import InputNode from '../core/InputNode.js';
-import { Vector3 } from '../../../../../build/three.module.js';
+import { Vector3 } from 'three';
 
 class Vector3Node extends InputNode {
 

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

@@ -1,5 +1,5 @@
 import InputNode from '../core/InputNode.js';
-import { Vector4 } from '../../../../../build/three.module.js';
+import { Vector4 } from 'three';
 
 class Vector4Node extends InputNode {
 

+ 60 - 0
examples/jsm/renderers/nodes/lights/LightContextNode.js

@@ -0,0 +1,60 @@
+import ContextNode from '../core/ContextNode.js';
+import { RE_Direct_BlinnPhong, RE_IndirectDiffuse_BlinnPhong } from '../functions/BSDFs.js';
+
+class LightContextNode extends ContextNode {
+
+	constructor( node ) {
+
+		super( node );
+
+	}
+
+	getType( /*builder*/ ) {
+
+		return 'vec3';
+
+	}
+
+	generate( builder, output ) {
+
+		const type = this.getType( builder );
+
+		const material = builder.material;
+
+		let RE_Direct = null;
+		let RE_IndirectDiffuse = null;
+
+		if ( material.isMeshPhongMaterial === true ) {
+
+			RE_Direct = RE_Direct_BlinnPhong;
+			RE_IndirectDiffuse = RE_IndirectDiffuse_BlinnPhong;
+
+		}
+
+		if ( RE_Direct !== null ) {
+
+			this.setParameter( 'RE_Direct', RE_Direct );
+			this.setParameter( 'RE_IndirectDiffuse', RE_IndirectDiffuse );
+
+		}
+
+		const resetTotalLight = `Irradiance = vec3( 0.0 ); ReflectedLightDirectDiffuse = vec3( 0.0 ); ReflectedLightDirectSpecular = vec3( 0.0 );`;
+		const resultTotalLight = `ReflectedLightDirectDiffuse + ReflectedLightDirectSpecular`;
+
+		// include keywords
+
+		builder.getContextParameter( 'keywords' ).include( builder, resetTotalLight );
+
+		// add code
+
+		builder.addFlowCode( resetTotalLight );
+
+		super.generate( builder, output );
+
+		return builder.format( resultTotalLight, type, output );
+
+	}
+
+}
+
+export default LightContextNode;

+ 82 - 0
examples/jsm/renderers/nodes/lights/LightNode.js

@@ -0,0 +1,82 @@
+import Node from '../core/Node.js';
+import Object3DNode from '../accessors/Object3DNode.js';
+import PositionNode from '../accessors/PositionNode.js';
+import ColorNode from '../inputs/ColorNode.js';
+import FloatNode from '../inputs/FloatNode.js';
+import OperatorNode from '../math/OperatorNode.js';
+import MathNode from '../math/MathNode.js';
+import { NodeUpdateType } from '../core/constants.js';
+import { punctualLightIntensityToIrradianceFactor } from '../functions/BSDFs.js';
+
+import { Color } from 'three';
+
+class LightNode extends Node {
+
+	constructor( light = null ) {
+
+		super( 'vec3' );
+
+		this.updateType = NodeUpdateType.Object;
+
+		this.light = light;
+
+		this.color = new ColorNode( new Color() );
+
+		this.lightCutoffDistance = new FloatNode( 0 );
+		this.lightDecayExponent = new FloatNode( 0 );
+
+		this.lightPositionView = new Object3DNode( Object3DNode.VIEW_POSITION );
+		this.positionView = new PositionNode( PositionNode.VIEW );
+
+		this.lVector = new OperatorNode( '-', this.lightPositionView, this.positionView );
+
+		this.lightDirection = new MathNode( MathNode.NORMALIZE, this.lVector );
+
+		this.lightDistance = new MathNode( MathNode.LENGTH, this.lVector );
+
+		this.lightIntensity = punctualLightIntensityToIrradianceFactor.call( {
+			lightDistance: this.lightDistance,
+			cutoffDistance: this.lightCutoffDistance,
+			decayExponent: this.lightDecayExponent
+		} );
+
+		this.lightColor = new OperatorNode( '*', this.color, this.lightIntensity );
+
+	}
+
+	update( frame ) {
+
+		this.color.value.copy( this.light.color ).multiplyScalar( this.light.intensity );
+		this.lightCutoffDistance.value = this.light.distance;
+		this.lightDecayExponent.value = this.light.decay;
+
+	}
+
+	generate( builder, output ) {
+
+		this.lightPositionView.object3d = this.light;
+
+		const directFunctionNode = builder.getContextParameter( 'RE_Direct' );
+		const indirectDiffuseFunctionNode = builder.getContextParameter( 'RE_IndirectDiffuse' );
+
+		const directFunctionCallNode = directFunctionNode.call( {
+			lightDirection: this.lightDirection,
+			lightColor: this.lightColor
+		} );
+
+		const indirectDiffuseFunctionCallNode = indirectDiffuseFunctionNode.call( {
+			lightDirection: this.lightDirection,
+			lightColor: this.lightColor
+		} );
+
+		builder.addFlowCode( directFunctionCallNode.build( builder ) );
+
+		builder.addFlowCode( indirectDiffuseFunctionCallNode.build( builder ) );
+
+		return this.color.build( builder, output );
+
+	}
+
+}
+
+export default LightNode;

+ 44 - 0
examples/jsm/renderers/nodes/lights/LightsNode.js

@@ -0,0 +1,44 @@
+import Node from '../core/Node.js';
+import LightNode from './LightNode.js';
+
+class LightsNode extends Node {
+
+	constructor( lightNodes = [] ) {
+
+		super( 'vec3' );
+
+		this.lightNodes = lightNodes;
+
+	}
+
+	generate( builder, output ) {
+
+		const lightNodes = this.lightNodes;
+
+		for ( const lightNode of lightNodes ) {
+
+			lightNode.build( builder );
+
+		}
+
+		return builder.format( 'vec3( 0.0 )',  this.getType( builder ), output );
+
+	}
+
+	static fromLights( lights ) {
+
+		const lightNodes = [];
+
+		for ( const light of lights ) {
+
+			lightNodes.push( new LightNode( light ) );
+
+		}
+
+		return new LightsNode( lightNodes );
+
+	}
+
+}
+
+export default LightsNode;

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

@@ -3,7 +3,8 @@ import Node from '../core/Node.js';
 class MathNode extends Node {
 
 	static NORMALIZE = 'normalize';
-	static INVERSE_TRANSFORM_DIRETION = 'inverseTransformDirection';
+	static NEGATE = 'negate';
+	static LENGTH = 'length';
 
 	constructor( method, a, b = null ) {
 
@@ -16,60 +17,62 @@ class MathNode extends Node {
 
 	}
 
-	getType( builder ) {
-
-		const method = this.method;
-
-		if ( method === MathNode.INVERSE_TRANSFORM_DIRETION ) {
-
-			return 'vec3';
-
-		} else {
+	getInputType( builder ) {
 
-			const typeA = this.a.getType( builder );
+		const typeA = this.a.getType( builder );
 
-			if ( this.b !== null ) {
+		if ( this.b !== null ) {
 
-				if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
+			const typeB = this.b.getType( builder );
 
-					// anytype x anytype: use the greater length vector
+			if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) {
 
-					return typeB;
+				// anytype x anytype: use the greater length vector
 
-				}
+				return typeB;
 
 			}
 
-			return typeA;
-
 		}
 
+		return typeA;
+
 	}
 
-	generate( builder, output ) {
+	getType( builder ) {
 
 		const method = this.method;
-		const type = this.getType( builder );
 
-		let a = null, b = null;
+		if ( method === MathNode.LENGTH ) {
 
-		if ( method === MathNode.INVERSE_TRANSFORM_DIRETION ) {
+			return 'float';
 
-			a = this.a.build( builder, 'vec3' );
-			b = this.b.build( builder, 'mat4' );
+		} else if (
+			method === MathNode.TRANSFORM_DIRETION ||
+			method === MathNode.INVERSE_TRANSFORM_DIRETION
+		) {
 
-			// add in FunctionNode later
-			return `normalize( ( vec4( ${a}, 0.0 ) * ${b} ).xyz )`;
+			return 'vec3';
 
 		} else {
 
-			a = this.a.build( builder, type );
+			return this.getInputType( builder );
 
-			if ( this.b !== null ) {
+		}
 
-				b = this.b.build( builder, type );
+	}
 
-			}
+	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 ) {
+
+			b = this.b.build( builder, type );
 
 		}
 
@@ -79,7 +82,15 @@ class MathNode extends Node {
 
 		} else {
 
-			return builder.format( `${method}( ${a} )`, type, output );
+			if ( method === MathNode.NEGATE ) {
+
+				return builder.format( `( -${a} )`, type, output );
+
+			} else {
+
+				return builder.format( `${method}( ${a} )`, type, output );
+
+			}
 
 		}
 

+ 2 - 2
examples/jsm/renderers/webgpu/WebGPUBindings.js

@@ -123,7 +123,7 @@ class WebGPUBindings {
 
 			} else if ( binding.isSampler ) {
 
-				const texture = binding.texture;
+				const texture = binding.getTexture();
 
 				textures.updateSampler( texture );
 
@@ -138,7 +138,7 @@ class WebGPUBindings {
 
 			} else if ( binding.isSampledTexture ) {
 
-				const texture = binding.texture;
+				const texture = binding.getTexture();
 
 				const forceUpdate = textures.updateTexture( texture );
 				const textureGPU = textures.getTextureGPU( texture );

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

@@ -18,6 +18,12 @@ class WebGPUSampledTexture extends WebGPUBinding {
 
 	}
 
+	getTexture() {
+
+		return this.texture;
+
+	}
+
 }
 
 WebGPUSampledTexture.prototype.isSampledTexture = true;

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

@@ -16,6 +16,12 @@ class WebGPUSampler extends WebGPUBinding {
 
 	}
 
+	getTexture() {
+
+		return this.texture; 
+
+	}
+
 }
 
 WebGPUSampler.prototype.isSampler = true;

+ 116 - 22
examples/jsm/renderers/webgpu/nodes/ShaderLib.js

@@ -1,41 +1,135 @@
 const ShaderLib = {
+
 	common: {
-		vertexShader: `#version 450
 
-NODE_HEADER_ATTRIBUTES
-NODE_HEADER_UNIFORMS
-NODE_HEADER_VARYS
+		vertexShader:
+			`#version 450
+
+			NODE_HEADER_ATTRIBUTES
+			NODE_HEADER_UNIFORMS
+			NODE_HEADER_VARYS
+
+			void main(){
+
+				NODE_BODY_VARYS
+				NODE_BODY_VARS
+
+				gl_Position = NODE_MVP;
+
+			}`,
+
+		fragmentShader:
+			`#version 450
+
+			NODE_HEADER_ATTRIBUTES
+			NODE_HEADER_UNIFORMS
+			NODE_HEADER_VARYS
+
+			layout(location = 0) out vec4 outColor;
+
+			void main() {
+
+				NODE_BODY_VARS
+
+				MaterialDiffuseColor = vec4( 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_LIGHT
 
-void main(){
-	NODE_BODY_VARYS
-	gl_Position = NODE_MVP;
-}`,
-		fragmentShader: `#version 450
+					NODE_CODE_LIGHT
 
-NODE_HEADER_ATTRIBUTES
-NODE_HEADER_UNIFORMS
-NODE_HEADER_VARYS
+					outColor.rgb = NODE_LIGHT;
+					outColor.a = MaterialDiffuseColor.a;
 
-layout(location = 0) out vec4 outColor;
+				#else
 
-void main() {
+					outColor = MaterialDiffuseColor;
 
-	outColor = vec4( 1.0, 1.0, 1.0, 1.0 );
+				#endif
 
-	#ifdef NODE_COLOR
+			}`
 
-		outColor = NODE_COLOR;
+	},
 
-	#endif
+	phong: {
 
-	#ifdef NODE_OPACITY
+		vertexShader:
+			`#version 450
 
-		outColor.a *= NODE_OPACITY;
+			NODE_HEADER_ATTRIBUTES
+			NODE_HEADER_UNIFORMS
+			NODE_HEADER_VARYS
 
-	#endif
+			void main(){
+
+				NODE_BODY_VARYS
+				NODE_BODY_VARS
+
+				gl_Position = NODE_MVP;
+
+			}`,
+
+		fragmentShader:
+			`#version 450
+
+			NODE_HEADER_ATTRIBUTES
+			NODE_HEADER_UNIFORMS
+			NODE_HEADER_VARYS
+
+			layout(location = 0) out vec4 outColor;
+
+			void main() {
+
+				NODE_BODY_VARS
+
+				MaterialDiffuseColor = vec4( 1.0 );
+				MaterialSpecularColor = vec3( 1.0 );
+				MaterialSpecularShininess = 30.0;
+
+				NODE_CODE_COLOR
+				MaterialDiffuseColor = NODE_COLOR;
+
+				NODE_CODE_OPACITY
+				MaterialDiffuseColor.a *= NODE_OPACITY;
+
+				NODE_CODE_SPECULAR
+				MaterialSpecularColor = NODE_SPECULAR;
+
+				NODE_CODE_SHININESS
+				MaterialSpecularShininess = NODE_SHININESS;
+
+				#ifdef NODE_LIGHT
+
+					NODE_CODE_LIGHT
+
+					outColor.rgb = NODE_LIGHT;
+					outColor.a = MaterialDiffuseColor.a;
+
+				#else
+
+					outColor = MaterialDiffuseColor;
+
+				#endif
+
+			}`
 
-}`
 	}
+
 };
 
 export default ShaderLib;

+ 117 - 15
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -3,13 +3,14 @@ import {
 	FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform,
 	ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform
 } from './WebGPUNodeUniform.js';
-import WebGPUSampler from '../WebGPUSampler.js';
-import { WebGPUSampledTexture } from '../WebGPUSampledTexture.js';
+import WebGPUNodeSampler from './WebGPUNodeSampler.js';
+import { WebGPUNodeSampledTexture } from './WebGPUNodeSampledTexture.js';
 
 import NodeSlot from '../../nodes/core/NodeSlot.js';
 import NodeBuilder from '../../nodes/core/NodeBuilder.js';
+import MaterialNode from '../../nodes/accessors/MaterialNode.js';
 import ModelViewProjectionNode from '../../nodes/accessors/ModelViewProjectionNode.js';
-
+import LightContextNode from '../../nodes/lights/LightContextNode.js';
 import ShaderLib from './ShaderLib.js';
 
 class WebGPUNodeBuilder extends NodeBuilder {
@@ -35,13 +36,26 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		// get shader
 
-		this.nativeShader = ShaderLib.common;
+		let shader = null;
+
+		if ( material.isMeshPhongMaterial ) {
+
+			shader = ShaderLib.phong;
+
+		} else {
+
+			shader = ShaderLib.common;
+
+		}
+
+		this.nativeShader = shader;
 
 		// parse inputs
 
-		if ( material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
+		if ( material.isMeshPhongMaterial || material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial ) {
 
 			const mvpNode = new ModelViewProjectionNode();
+			const lightNode = material.lightNode;
 
 			if ( material.positionNode !== undefined ) {
 
@@ -55,12 +69,52 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 				this.addSlot( 'fragment', new NodeSlot( material.colorNode, 'COLOR', 'vec4' ) );
 
+			} else {
+
+				this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.COLOR ), 'COLOR', 'vec4' ) );
+
 			}
 
 			if ( material.opacityNode !== undefined ) {
 
 				this.addSlot( 'fragment', new NodeSlot( material.opacityNode, 'OPACITY', 'float' ) );
 
+			} else {
+
+				this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.OPACITY ), 'OPACITY', 'float' ) );
+
+			}
+
+			if ( material.isMeshPhongMaterial ) {
+
+				if ( material.specularNode !== undefined ) {
+
+					this.addSlot( 'fragment', new NodeSlot( material.specularNode, 'SPECULAR', 'vec3' ) );
+
+				} else {
+
+					this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.SPECULAR ), 'SPECULAR', 'vec3' ) );
+
+				}
+
+				if ( material.shininessNode !== undefined ) {
+
+					this.addSlot( 'fragment', new NodeSlot( material.shininessNode, 'SHININESS', 'float' ) );
+
+				} else {
+
+					this.addSlot( 'fragment', new NodeSlot( new MaterialNode( MaterialNode.SHININESS ), 'SHININESS', 'float' ) );
+
+				}
+
+			}
+
+			if ( lightNode !== undefined ) {
+
+				const lightContextNode = new LightContextNode( lightNode );
+
+				this.addSlot( 'fragment', new NodeSlot( lightContextNode, 'LIGHT', 'vec3' ) );
+
 			}
 
 		}
@@ -75,7 +129,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	getPropertyName( node ) {
 
-		if ( node.isNodeUniform ) {
+		if ( node.isNodeUniform === true ) {
 
 			const name = node.name;
 			const type = node.type;
@@ -117,8 +171,8 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			if ( type === 'texture' ) {
 
-				const sampler = new WebGPUSampler( `${uniformNode.name}_sampler`, uniformNode.value );
-				const texture = new WebGPUSampledTexture( uniformNode.name, uniformNode.value );
+				const sampler = new WebGPUNodeSampler( `${uniformNode.name}_sampler`, uniformNode.node );
+				const texture = new WebGPUNodeSampledTexture( uniformNode.name, uniformNode.node );
 
 				// add first textures in sequence and group for last
 				const lastBinding = bindings[ bindings.length - 1 ];
@@ -206,7 +260,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 				const attribute = attributes[ index ];
 
-				snippet += `layout(location = ${index}) in ${attribute.type} ${attribute.name};`;
+				snippet += `layout(location = ${index}) in ${attribute.type} ${attribute.name}; `;
 
 			}
 
@@ -228,7 +282,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			const vary = varys[ index ];
 
-			snippet += `layout(location = ${index}) ${ioStage} ${vary.type} ${vary.name};`;
+			snippet += `layout(location = ${index}) ${ioStage} ${vary.type} ${vary.name}; `;
 
 		}
 
@@ -244,7 +298,45 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			for ( const vary of this.varys ) {
 
-				snippet += `${vary.name} = ${vary.snippet};`;
+				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}; `;
 
 			}
 
@@ -267,14 +359,14 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			if ( uniform.type === 'texture' ) {
 
-				snippet += `layout(set = 0, binding = ${index ++}) uniform sampler ${uniform.name}_sampler;`;
-				snippet += `layout(set = 0, binding = ${index ++}) uniform texture2D ${uniform.name};`;
+				snippet += `layout(set = 0, binding = ${index ++}) uniform sampler ${uniform.name}_sampler; `;
+				snippet += `layout(set = 0, binding = ${index ++}) uniform texture2D ${uniform.name}; `;
 
 			} else {
 
 				const vectorType = this.getVectorType( uniform.type );
 
-				groupSnippet += `uniform ${vectorType} ${uniform.name};`;
+				groupSnippet += `uniform ${vectorType} ${uniform.name}; `;
 
 			}
 
@@ -282,7 +374,7 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 		if ( groupSnippet ) {
 
-			snippet += `layout(set = 0, binding = ${index ++}) uniform NodeUniforms { ${groupSnippet} } nodeUniforms;`;
+			snippet += `layout(set = 0, binding = ${index ++}) uniform NodeUniforms { ${groupSnippet} } nodeUniforms; `;
 
 		}
 
@@ -307,6 +399,16 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	build() {
 
+		const keywords = this.getContextParameter( 'keywords' );
+
+		for ( const shaderStage of [ 'vertex', 'fragment' ] ) {
+
+			this.shaderStage = shaderStage;
+
+			keywords.include( this, this.nativeShader.fragmentShader );
+
+		}
+
 		super.build();
 
 		this.vertexShader = this.composeShaderCode( this.nativeShader.vertexShader, this.vertexShader );

+ 21 - 0
examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampledTexture.js

@@ -0,0 +1,21 @@
+import { WebGPUSampledTexture } from '../WebGPUSampledTexture.js';
+
+class WebGPUNodeSampledTexture extends WebGPUSampledTexture {
+
+	constructor( name, textureNode ) {
+
+		super( name, textureNode.value );
+
+		this.textureNode = textureNode;
+
+	}
+
+	getTexture() {
+
+		return this.textureNode.value;
+
+	}
+
+}
+
+export { WebGPUNodeSampledTexture };

+ 21 - 0
examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampler.js

@@ -0,0 +1,21 @@
+import WebGPUSampler from '../WebGPUSampler.js';
+
+class WebGPUNodeSampler extends WebGPUSampler {
+
+	constructor( name, textureNode ) {
+
+		super( name, textureNode.value );
+
+		this.textureNode = textureNode;
+
+	}
+
+	getTexture() {
+
+		return this.textureNode.value;
+
+	}
+
+}
+
+export default WebGPUNodeSampler;

BIN
examples/screenshots/webgpu_lights_selective.jpg


+ 181 - 0
examples/webgpu_lights_selective.html

@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - selective lights</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - selective lights WebGPU demo.<br />
+			<b style="color:red">Left: Red lights</b> - <b>Center: All lights</b> - <b style="color:blue">Right: blue light</b>
+		</div>
+
+		<script type="importmap">
+		{
+			"imports": {
+				"three": "../build/three.module.js"
+			}
+		}
+		</script>
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import Stats from './jsm/libs/stats.module.js';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { TeapotGeometry } from './jsm/geometries/TeapotGeometry.js';
+
+			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
+			import WebGPU from './jsm/renderers/webgpu/WebGPU.js';
+
+			import LightsNode from './jsm/renderers/nodes/lights/LightsNode.js';
+
+			let camera, scene, renderer,
+				light1, light2, light3, light4,
+				stats, controls;
+
+			init().then( animate ).catch( error );
+
+			async function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw 'No WebGPU support';
+
+				}
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.z = 70;
+
+				scene = new THREE.Scene();
+
+				const sphere = new THREE.SphereGeometry( 0.5, 16, 8 );
+
+				//lights
+
+				light1 = new THREE.PointLight( 0xff0040, 2, 100 );
+				light1.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0xff0040 } ) ) );
+				scene.add( light1 );
+
+				light2 = new THREE.PointLight( 0x0040ff, 2, 100 );
+				light2.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0x0040ff } ) ) );
+				scene.add( light2 );
+
+				light3 = new THREE.PointLight( 0x80ff80, 2, 100 );
+				light3.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0x80ff80 } ) ) );
+				scene.add( light3 );
+
+				light4 = new THREE.PointLight( 0xffaa00, 2, 100 );
+				light4.add( new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( { color: 0xffaa00 } ) ) );
+				scene.add( light4 );
+
+				//light nodes ( selective lights )
+
+				const allLightsNode = LightsNode.fromLights( [ light1, light2, light3, light4 ] );
+				const redLightNode = LightsNode.fromLights( [ light1 ] );
+				const blueLightNode = LightsNode.fromLights( [ light2 ] );
+
+				//models
+
+				const geometryTeapot = new TeapotGeometry( 8, 18 );
+
+				const leftObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) );
+				leftObject.material.lightNode = redLightNode;
+				leftObject.position.x = - 30;
+				scene.add( leftObject );
+
+				const centerObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) );
+				centerObject.material.lightNode = allLightsNode;
+				scene.add( centerObject );
+
+				const rightObject = new THREE.Mesh( geometryTeapot, new THREE.MeshPhongMaterial( { color: 0x555555 } ) );
+				rightObject.material.lightNode = blueLightNode;
+				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;
+
+				//renderer
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				//controls
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 30;
+				controls.maxDistance = 150;
+
+				//stats
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				return renderer.init();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				const time = Date.now() * 0.0005;
+
+				light1.position.x = Math.sin( time * 0.7 ) * 30;
+				light1.position.y = Math.cos( time * 0.5 ) * 40;
+				light1.position.z = Math.cos( time * 0.3 ) * 30;
+
+				light2.position.x = Math.cos( time * 0.3 ) * 30;
+				light2.position.y = Math.sin( time * 0.5 ) * 40;
+				light2.position.z = Math.sin( time * 0.7 ) * 30;
+
+				light3.position.x = Math.sin( time * 0.7 ) * 30;
+				light3.position.y = Math.cos( time * 0.3 ) * 40;
+				light3.position.z = Math.sin( time * 0.5 ) * 30;
+
+				light4.position.x = Math.sin( time * 0.3 ) * 30;
+				light4.position.y = Math.cos( time * 0.7 ) * 40;
+				light4.position.z = Math.sin( time * 0.5 ) * 30;
+
+				renderer.render( scene, camera );
+
+			}
+
+			function error( error ) {
+
+				console.error( error );
+
+			}
+
+		</script>
+	</body>
+</html>