Explorar o código

NodeMaterial & WebGPU: Revision of Lighting Model System (#25170)

* CubeTextureNode: cleanup

* Added ExtendedMaterialNode

* MaterialNode: Add UV, SHININESS, SPECULAR_COLOR, REFLECTIVITY

* CondNode: Support to use of "if" only and "void"

* Add LightingModel class

* ExpressionNode: Fix if used with "void" type

* ReflectVectorNode: udpate syntax

* Object3DNode: Added DIRECTION

* ShaderNode: Add materialUV, materialShininess, materialSpecularColor, materialReflectivity

* StackNode: new add() method

* NodeBuilder: Non-breakline supports

* Add DiscardNode

* Rename exp2Fog to densityFog

* Revised lighting system and added Phong Model Lighting

* Added webgpu phong example

* update example

* cleanup

* cleanup(2)

* cleanup

* cleanup

* Remove JS syntax > ECMA 2018

* cleanup by @LeviPesin

* Downgrade Logical OR assignments
sunag %!s(int64=2) %!d(string=hai) anos
pai
achega
0bec83fee6
Modificáronse 33 ficheiros con 788 adicións e 214 borrados
  1. 1 0
      examples/files.json
  2. 6 0
      examples/jsm/nodes/Nodes.js
  3. 3 5
      examples/jsm/nodes/accessors/CubeTextureNode.js
  4. 51 0
      examples/jsm/nodes/accessors/ExtendedMaterialNode.js
  5. 86 4
      examples/jsm/nodes/accessors/MaterialNode.js
  6. 9 2
      examples/jsm/nodes/accessors/Object3DNode.js
  7. 3 4
      examples/jsm/nodes/accessors/ReflectVectorNode.js
  8. 1 3
      examples/jsm/nodes/accessors/TextureNode.js
  9. 4 4
      examples/jsm/nodes/core/ExpressionNode.js
  10. 14 0
      examples/jsm/nodes/core/LightingModel.js
  11. 8 4
      examples/jsm/nodes/core/NodeBuilder.js
  12. 7 1
      examples/jsm/nodes/core/PropertyNode.js
  13. 10 2
      examples/jsm/nodes/core/StackNode.js
  14. 1 3
      examples/jsm/nodes/display/ViewportNode.js
  15. 27 0
      examples/jsm/nodes/functions/BSDF/BRDF_BlinnPhong.js
  16. 29 0
      examples/jsm/nodes/functions/PhongLightingModel.js
  17. 3 8
      examples/jsm/nodes/functions/PhysicalLightingModel.js
  18. 3 0
      examples/jsm/nodes/materials/LineBasicNodeMaterial.js
  19. 3 0
      examples/jsm/nodes/materials/Materials.js
  20. 83 0
      examples/jsm/nodes/materials/MeshPhongNodeMaterial.js
  21. 12 80
      examples/jsm/nodes/materials/MeshStandardNodeMaterial.js
  22. 134 41
      examples/jsm/nodes/materials/NodeMaterial.js
  23. 3 0
      examples/jsm/nodes/materials/PointsNodeMaterial.js
  24. 16 15
      examples/jsm/nodes/materials/SpriteNodeMaterial.js
  25. 26 14
      examples/jsm/nodes/math/CondNode.js
  26. 17 8
      examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js
  27. 10 3
      examples/jsm/nodes/shadernode/ShaderNodeElements.js
  28. 18 0
      examples/jsm/nodes/utils/DiscardNode.js
  29. 0 12
      examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js
  30. BIN=BIN
      examples/screenshots/webgpu_lights_phong.jpg
  31. 198 0
      examples/webgpu_lights_phong.html
  32. 1 1
      examples/webgpu_lights_selective.html
  33. 1 0
      test/e2e/puppeteer.js

+ 1 - 0
examples/files.json

@@ -333,6 +333,7 @@
 		"webgpu_instance_mesh",
 		"webgpu_instance_uniform",
 		"webgpu_lights_custom",
+		"webgpu_lights_phong",
 		"webgpu_lights_selective",
 		"webgpu_loader_gltf",
 		"webgpu_materials",

+ 6 - 0
examples/jsm/nodes/Nodes.js

@@ -10,6 +10,7 @@ import ExpressionNode from './core/ExpressionNode.js';
 import FunctionCallNode from './core/FunctionCallNode.js';
 import FunctionNode from './core/FunctionNode.js';
 import InstanceIndexNode from './core/InstanceIndexNode.js';
+import LightingModel from './core/LightingModel.js';
 import Node from './core/Node.js';
 import NodeAttribute from './core/NodeAttribute.js';
 import NodeBuilder from './core/NodeBuilder.js';
@@ -87,6 +88,7 @@ import AnalyticLightNode from './lighting/AnalyticLightNode.js';
 // utils
 import ArrayElementNode from './utils/ArrayElementNode.js';
 import ConvertNode from './utils/ConvertNode.js';
+import DiscardNode from './utils/DiscardNode.js';
 import EquirectUVNode from './utils/EquirectUVNode.js';
 import JoinNode from './utils/JoinNode.js';
 import MatcapUVNode from './utils/MatcapUVNode.js';
@@ -145,6 +147,7 @@ const nodeLib = {
 	FunctionCallNode,
 	FunctionNode,
 	InstanceIndexNode,
+	LightingModel,
 	Node,
 	NodeAttribute,
 	NodeBuilder,
@@ -222,6 +225,7 @@ const nodeLib = {
 	// utils
 	ArrayElementNode,
 	ConvertNode,
+	DiscardNode,
 	EquirectUVNode,
 	JoinNode,
 	MatcapUVNode,
@@ -273,6 +277,7 @@ export {
 	FunctionCallNode,
 	FunctionNode,
 	InstanceIndexNode,
+	LightingModel,
 	Node,
 	NodeAttribute,
 	NodeBuilder,
@@ -350,6 +355,7 @@ export {
 	// utils
 	ArrayElementNode,
 	ConvertNode,
+	DiscardNode,
 	EquirectUVNode,
 	JoinNode,
 	MatcapUVNode,

+ 3 - 5
examples/jsm/nodes/accessors/CubeTextureNode.js

@@ -2,7 +2,7 @@ import TextureNode from './TextureNode.js';
 import UniformNode from '../core/UniformNode.js';
 import ReflectVectorNode from './ReflectVectorNode.js';
 
-import { negate, vec3, nodeObject } from '../shadernode/ShaderNodeBaseElements.js';
+import { vec3, nodeObject } from '../shadernode/ShaderNodeBaseElements.js';
 
 let defaultUV;
 
@@ -24,9 +24,7 @@ class CubeTextureNode extends TextureNode {
 
 	getDefaultUV() {
 
-		defaultUV || ( defaultUV = new ReflectVectorNode() );
-
-		return defaultUV;
+		return defaultUV || ( defaultUV = new ReflectVectorNode() );;
 
 	}
 
@@ -61,7 +59,7 @@ class CubeTextureNode extends TextureNode {
 			if ( propertyName === undefined ) {
 
 				const uvNodeObject = nodeObject( uvNode );
-				const cubeUV = vec3( negate( uvNodeObject.x ), uvNodeObject.yz );
+				const cubeUV = vec3( uvNodeObject.x.negate(), uvNodeObject.yz );
 				const uvSnippet = cubeUV.build( builder, 'vec3' );
 
 				const nodeVar = builder.getVarFromNode( this, 'vec4' );

+ 51 - 0
examples/jsm/nodes/accessors/ExtendedMaterialNode.js

@@ -0,0 +1,51 @@
+import MaterialNode from './MaterialNode.js';
+import NormalMapNode from '../display/NormalMapNode.js';
+
+import {
+	texture, normalView, materialReference
+} from '../shadernode/ShaderNodeElements.js';
+
+class ExtendedMaterialNode extends MaterialNode {
+
+	constructor( scope ) {
+
+		super( scope );
+
+	}
+
+	getNodeType( builder ) {
+
+		const scope = this.scope;
+		let type = null;
+
+		if ( scope === ExtendedMaterialNode.NORMAL ) {
+
+			type = 'vec3';
+
+		}
+
+		return type || super.getNodeType( builder );
+	}
+
+	construct( builder ) {
+
+		const material = builder.material;
+		const scope = this.scope;
+
+		let node = null;
+
+		if ( scope === ExtendedMaterialNode.NORMAL ) {
+
+			node = material.normalMap ? new NormalMapNode( texture( material.normalMap ), materialReference( 'normalScale', 'vec2' ) ) : normalView;
+
+		}
+
+		return node || super.construct( builder );
+
+	}
+
+}
+
+ExtendedMaterialNode.NORMAL = 'normal';
+
+export default ExtendedMaterialNode;

+ 86 - 4
examples/jsm/nodes/accessors/MaterialNode.js

@@ -1,12 +1,16 @@
 import Node from '../core/Node.js';
+import UniformNode from '../core/UniformNode.js';
+import UVNode from '../accessors/UVNode.js';
+import ConstNode from '../core/ConstNode.js';
 import OperatorNode from '../math/OperatorNode.js';
+import JoinNode from '../utils/JoinNode.js';
 import MaterialReferenceNode from './MaterialReferenceNode.js';
 import TextureNode from './TextureNode.js';
 import SplitNode from '../utils/SplitNode.js';
 
 class MaterialNode extends Node {
 
-	constructor( scope = MaterialNode.COLOR ) {
+	constructor( scope ) {
 
 		super();
 
@@ -27,11 +31,15 @@ class MaterialNode extends Node {
 
 			return 'float';
 
+		} else if ( scope === MaterialNode.UV ) {
+
+			return 'vec2';
+
 		} else if ( scope === MaterialNode.EMISSIVE ) {
 
 			return 'vec3';
 
-		} else if ( scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS ) {
+		} else if ( scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS || scope === MaterialNode.SPECULAR || scope === MaterialNode.SHININESS ) {
 
 			return 'float';
 
@@ -56,8 +64,8 @@ class MaterialNode extends Node {
 
 			if ( material.map && material.map.isTexture === true ) {
 
-				//new MaterialReferenceNode( 'map', 'texture' )
-				const map = new TextureNode( material.map );
+				//const map = new MaterialReferenceNode( 'map', 'texture' );
+				const map = new TextureNode( material.map, new MaterialNode( MaterialNode.UV ) );
 
 				node = new OperatorNode( '*', colorNode, map );
 
@@ -81,6 +89,28 @@ class MaterialNode extends Node {
 
 			}
 
+		} else if ( scope === MaterialNode.SHININESS ) {
+
+			return new MaterialReferenceNode( 'shininess', 'float' );
+
+		} else if ( scope === MaterialNode.SPECULAR_COLOR ) {
+
+			node = new MaterialReferenceNode( 'specular', 'color' );
+
+		} else if ( scope === MaterialNode.REFLECTIVITY ) {
+
+			const reflectivityNode = new MaterialReferenceNode( 'reflectivity', 'float' );
+
+			if ( material.specularMap && material.specularMap.isTexture === true ) {
+
+				node = new OperatorNode( '*', reflectivityNode, new SplitNode( new TextureNode( material.specularMap ), 'r' ) );
+
+			} else {
+
+				node = reflectivityNode;
+
+			}
+
 		} else if ( scope === MaterialNode.ROUGHNESS ) {
 
 			const roughnessNode = new MaterialReferenceNode( 'roughness', 'float' );
@@ -127,6 +157,54 @@ class MaterialNode extends Node {
 
 			node = new MaterialReferenceNode( 'rotation', 'float' );
 
+		} else if ( scope === MaterialNode.UV ) {
+
+			// uv repeat and offset setting priorities
+
+			let uvNode;
+			let uvScaleMap = 
+				material.map ||
+				material.specularMap ||
+				material.displacementMap ||
+				material.normalMap ||
+				material.bumpMap ||
+				material.roughnessMap ||
+				material.metalnessMap ||
+				material.alphaMap ||
+				material.emissiveMap ||
+				material.clearcoatMap ||
+				material.clearcoatNormalMap ||
+				material.clearcoatRoughnessMap ||
+				material.iridescenceMap ||
+				material.iridescenceThicknessMap ||
+				material.specularIntensityMap ||
+				material.specularColorMap ||
+				material.transmissionMap ||
+				material.thicknessMap ||
+				material.sheenColorMap ||
+				material.sheenRoughnessMap;
+
+			if ( uvScaleMap ) {
+
+				// backwards compatibility
+				if ( uvScaleMap.isWebGLRenderTarget ) {
+
+					uvScaleMap = uvScaleMap.texture;
+
+				}
+
+				if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+					uvScaleMap.updateMatrix();
+
+				}
+
+				uvNode = new OperatorNode( '*', new UniformNode( uvScaleMap.matrix ), new JoinNode( [ new UVNode(), new ConstNode( 1 ) ] ) );
+
+			}
+
+			return uvNode || new UVNode();
+
 		} else {
 
 			const outputType = this.getNodeType( builder );
@@ -144,9 +222,13 @@ class MaterialNode extends Node {
 MaterialNode.ALPHA_TEST = 'alphaTest';
 MaterialNode.COLOR = 'color';
 MaterialNode.OPACITY = 'opacity';
+MaterialNode.SHININESS = 'shininess';
+MaterialNode.SPECULAR_COLOR = 'specularColor';
+MaterialNode.REFLECTIVITY = 'reflectivity';
 MaterialNode.ROUGHNESS = 'roughness';
 MaterialNode.METALNESS = 'metalness';
 MaterialNode.EMISSIVE = 'emissive';
 MaterialNode.ROTATION = 'rotation';
+MaterialNode.UV = 'uv';
 
 export default MaterialNode;

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

@@ -30,7 +30,7 @@ class Object3DNode extends Node {
 
 			return 'mat3';
 
-		} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) {
+		} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION ) {
 
 			return 'vec3';
 
@@ -60,6 +60,12 @@ class Object3DNode extends Node {
 
 			uniformNode.value.setFromMatrixPosition( object.matrixWorld );
 
+		} else if ( scope === Object3DNode.DIRECTION ) {
+
+			uniformNode.value = uniformNode.value || new Vector3();
+
+			object.getWorldDirection( uniformNode.value );
+
 		} else if ( scope === Object3DNode.VIEW_POSITION ) {
 
 			const camera = frame.camera;
@@ -85,7 +91,7 @@ class Object3DNode extends Node {
 
 			this._uniformNode.nodeType = 'mat3';
 
-		} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION ) {
+		} else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION ) {
 
 			this._uniformNode.nodeType = 'vec3';
 
@@ -118,5 +124,6 @@ Object3DNode.NORMAL_MATRIX = 'normalMatrix';
 Object3DNode.WORLD_MATRIX = 'worldMatrix';
 Object3DNode.POSITION = 'position';
 Object3DNode.VIEW_POSITION = 'viewPosition';
+Object3DNode.DIRECTION = 'direction';
 
 export default Object3DNode;

+ 3 - 4
examples/jsm/nodes/accessors/ReflectVectorNode.js

@@ -1,7 +1,6 @@
 import Node from '../core/Node.js';
 import {
-	transformedNormalView, positionViewDirection,
-	transformDirection, negate, reflect, cameraViewMatrix
+	transformedNormalView, positionViewDirection, cameraViewMatrix
 } from '../shadernode/ShaderNodeBaseElements.js';
 
 class ReflectVectorNode extends Node {
@@ -20,9 +19,9 @@ class ReflectVectorNode extends Node {
 
 	construct() {
 
-		const reflectView = reflect( negate( positionViewDirection ), transformedNormalView );
+		const reflectView = positionViewDirection.negate().reflect( transformedNormalView );
 
-		return transformDirection( reflectView, cameraViewMatrix );
+		return reflectView.transformDirection( cameraViewMatrix );
 
 	}
 

+ 1 - 3
examples/jsm/nodes/accessors/TextureNode.js

@@ -30,9 +30,7 @@ class TextureNode extends UniformNode {
 
 	getDefaultUV() {
 
-		defaultUV || ( defaultUV = new UVNode() );
-
-		return defaultUV;
+		return defaultUV || ( defaultUV = new UVNode() );
 
 	}
 

+ 4 - 4
examples/jsm/nodes/core/ExpressionNode.js

@@ -1,6 +1,6 @@
-import TempNode from './TempNode.js';
+import Node from './Node.js';
 
-class ExpressionNode extends TempNode {
+class ExpressionNode extends Node {
 
 	constructor( snipped = '', nodeType = 'void' ) {
 
@@ -10,7 +10,7 @@ class ExpressionNode extends TempNode {
 
 	}
 
-	generate( builder ) {
+	generate( builder, output ) {
 
 		const type = this.getNodeType( builder );
 		const snipped = this.snipped;
@@ -21,7 +21,7 @@ class ExpressionNode extends TempNode {
 
 		} else {
 
-			return `( ${ snipped } )`;
+			return builder.format( `( ${ snipped } )`, type, output );
 
 		}
 

+ 14 - 0
examples/jsm/nodes/core/LightingModel.js

@@ -0,0 +1,14 @@
+class LightingModel {
+
+	constructor( direct = null, indirectDiffuse = null, indirectSpecular = null, ambientOcclusion = null ) {
+
+		this.direct = direct;
+		this.indirectDiffuse = indirectDiffuse;
+		this.indirectSpecular = indirectSpecular;
+		this.ambientOcclusion = ambientOcclusion;
+
+	}
+
+}
+
+export default LightingModel;

+ 8 - 4
examples/jsm/nodes/core/NodeBuilder.js

@@ -473,9 +473,7 @@ class NodeBuilder {
 
 		const nodeData = this.getDataFromNode( node, shaderStage );
 
-		nodeData.properties || ( nodeData.properties = { outputNode: null } );
-
-		return nodeData.properties;
+		return nodeData.properties || ( nodeData.properties = { outputNode: null } );
 
 	}
 
@@ -570,7 +568,13 @@ class NodeBuilder {
 
 	}
 
-	addFlowCode( code ) {
+	addFlowCode( code, breakline = true ) {
+
+		if ( breakline && ! /;\s*$/.test( code ) ) {
+
+			code += ';\n\t';
+
+		}
 
 		this.flow.code += code;
 

+ 7 - 1
examples/jsm/nodes/core/PropertyNode.js

@@ -2,7 +2,7 @@ import Node from './Node.js';
 
 class PropertyNode extends Node {
 
-	constructor( name = null, nodeType = 'vec4' ) {
+	constructor( nodeType, name = null ) {
 
 		super( nodeType );
 
@@ -16,6 +16,12 @@ class PropertyNode extends Node {
 
 	}
 
+	isGlobal( /*builder*/ ) {
+
+		return true;
+
+	}
+
 	generate( builder ) {
 
 		const nodeVary = builder.getVarFromNode( this, this.getNodeType( builder ) );

+ 10 - 2
examples/jsm/nodes/core/StackNode.js

@@ -1,5 +1,7 @@
 import Node from './Node.js';
 import OperatorNode from '../math/OperatorNode.js';
+import BypassNode from '../core/BypassNode.js';
+import ExpressionNode from '../core/ExpressionNode.js';
 
 class StackNode extends Node {
 
@@ -20,14 +22,20 @@ class StackNode extends Node {
 
 	}
 
-	assign( targetNode, sourceValue ) {
+	add( node ) {
 
-		this.nodes.push( new OperatorNode( '=', targetNode, sourceValue ) );
+		this.nodes.push( new BypassNode( new ExpressionNode(), node ) );
 
 		return this;
 
 	}
 
+	assign( targetNode, sourceValue ) {
+
+		return this.add( new OperatorNode( '=', targetNode, sourceValue ) );
+
+	}
+
 	build( builder, ...params ) {
 
 		for ( const node of this.nodes ) {

+ 1 - 3
examples/jsm/nodes/display/ViewportNode.js

@@ -55,9 +55,7 @@ class ViewportNode extends Node {
 
 		if ( scope === ViewportNode.RESOLUTION ) {
 
-			resolution || ( resolution = new Vector2() );
-
-			output = uniform( resolution );
+			output = uniform( resolution || ( resolution = new Vector2() ) );
 
 		} else {
 

+ 27 - 0
examples/jsm/nodes/functions/BSDF/BRDF_BlinnPhong.js

@@ -0,0 +1,27 @@
+import F_Schlick from './F_Schlick.js';
+import { ShaderNode, shininess, specularColor, float, add, clamp, dot, mul, normalize, positionViewDirection, transformedNormalView } from '../../shadernode/ShaderNodeBaseElements.js';
+
+const G_BlinnPhong_Implicit = () => float( 0.25 );
+
+const D_BlinnPhong = new ShaderNode( ( { dotNH } ) => {
+
+	return shininess.mul( 1 / Math.PI ).mul( 0.5 ).add( 1.0 ).mul( dotNH.pow( shininess ) );
+
+} );
+
+const BRDF_BlinnPhong = new ShaderNode( ( { lightDirection } ) => {
+
+	const halfDir = normalize( add( lightDirection, positionViewDirection ) );
+
+	const dotNH = clamp( dot( transformedNormalView, halfDir ) );
+	const dotVH = clamp( dot( positionViewDirection, halfDir ) );
+
+	const F = F_Schlick.call( { f0: specularColor, f90: 1.0, dotVH } );
+	const G = G_BlinnPhong_Implicit();
+	const D = D_BlinnPhong.call( { dotNH } );
+
+	return mul( F, G, D );
+
+} );
+
+export default BRDF_BlinnPhong;

+ 29 - 0
examples/jsm/nodes/functions/PhongLightingModel.js

@@ -0,0 +1,29 @@
+import BRDF_Lambert from './BSDF/BRDF_Lambert.js';
+import BRDF_BlinnPhong from './BSDF/BRDF_BlinnPhong.js';
+
+import {
+	ShaderNode,
+	mul, clamp, dot, transformedNormalView,
+	diffuseColor, materialReflectivity, lightingModel
+} from '../shadernode/ShaderNodeElements.js';
+
+const RE_Direct_BlinnPhong = new ShaderNode( ( { lightDirection, lightColor, reflectedLight } ) => {
+
+	const dotNL = clamp( dot( transformedNormalView, lightDirection ) );
+	const irradiance = mul( dotNL, lightColor );
+
+	reflectedLight.directDiffuse.add( mul( irradiance, BRDF_Lambert.call( { diffuseColor: diffuseColor.rgb } ) ) );
+
+	reflectedLight.directSpecular.add( irradiance.mul( BRDF_BlinnPhong.call( { lightDirection } ) ).mul( materialReflectivity ) );
+
+} );
+
+const RE_IndirectDiffuse_BlinnPhong = new ShaderNode( ( { irradiance, reflectedLight } ) => {
+
+	reflectedLight.indirectDiffuse.add( irradiance.mul( BRDF_Lambert.call( { diffuseColor } ) ) );
+
+} );
+
+const phongLightingModel = lightingModel( RE_Direct_BlinnPhong, RE_IndirectDiffuse_BlinnPhong );
+
+export default phongLightingModel;

+ 3 - 8
examples/jsm/nodes/functions/PhysicalLightingModel.js

@@ -5,7 +5,7 @@ import {
 	ShaderNode,
 	vec3, mul, clamp, add, sub, dot, div, transformedNormalView,
 	pow, exp2, dotNV,
-	diffuseColor, specularColor, roughness, temp
+	diffuseColor, specularColor, roughness, temp, lightingModel
 } from '../shadernode/ShaderNodeElements.js';
 
 // Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
@@ -84,11 +84,6 @@ const RE_AmbientOcclusion_Physical = new ShaderNode( ( { ambientOcclusion, refle
 
 } );
 
-const PhysicalLightingModel = {
-	direct: RE_Direct_Physical,
-	indirectDiffuse: RE_IndirectDiffuse_Physical,
-	indirectSpecular: RE_IndirectSpecular_Physical,
-	ambientOcclusion: RE_AmbientOcclusion_Physical
-};
+const physicalLightingModel = lightingModel( RE_Direct_Physical, RE_IndirectDiffuse_Physical, RE_IndirectSpecular_Physical, RE_AmbientOcclusion_Physical );
 
-export default PhysicalLightingModel;
+export default physicalLightingModel;

+ 3 - 0
examples/jsm/nodes/materials/LineBasicNodeMaterial.js

@@ -11,6 +11,9 @@ class LineBasicNodeMaterial extends NodeMaterial {
 
 		this.isLineBasicNodeMaterial = true;
 
+		this.lights = false;
+		this.normals = false;
+
 		this.colorNode = null;
 		this.opacityNode = null;
 

+ 3 - 0
examples/jsm/nodes/materials/Materials.js

@@ -1,6 +1,7 @@
 import NodeMaterial from './NodeMaterial.js';
 import LineBasicNodeMaterial from './LineBasicNodeMaterial.js';
 import MeshBasicNodeMaterial from './MeshBasicNodeMaterial.js';
+import MeshPhongNodeMaterial from './MeshPhongNodeMaterial.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
 import MeshPhysicalNodeMaterial from './MeshPhysicalNodeMaterial.js';
 import PointsNodeMaterial from './PointsNodeMaterial.js';
@@ -10,6 +11,7 @@ export {
 	NodeMaterial,
 	LineBasicNodeMaterial,
 	MeshBasicNodeMaterial,
+	MeshPhongNodeMaterial,
 	MeshStandardNodeMaterial,
 	MeshPhysicalNodeMaterial,
 	PointsNodeMaterial,
@@ -22,6 +24,7 @@ NodeMaterial.fromMaterial = function ( material ) {
 		NodeMaterial,
 		LineBasicNodeMaterial,
 		MeshBasicNodeMaterial,
+		MeshPhongNodeMaterial,
 		MeshStandardNodeMaterial,
 		MeshPhysicalNodeMaterial,
 		PointsNodeMaterial,

+ 83 - 0
examples/jsm/nodes/materials/MeshPhongNodeMaterial.js

@@ -0,0 +1,83 @@
+import NodeMaterial from './NodeMaterial.js';
+import {
+	float, vec3,
+	materialShininess, materialSpecularColor,
+	shininess, specularColor
+} from '../shadernode/ShaderNodeElements.js';
+import phongLightingModel from '../functions/PhongLightingModel.js';
+
+import { MeshPhongMaterial } from 'three';
+
+const defaultValues = new MeshPhongMaterial();
+
+class MeshPhongNodeMaterial extends NodeMaterial {
+
+	constructor( parameters ) {
+
+		super();
+
+		this.isMeshPhongNodeMaterial = true;
+
+		this.lights = true;
+
+		this.colorNode = null;
+		this.opacityNode = null;
+
+		this.shininessNode = null;
+		this.specularNode = null;
+
+		this.alphaTestNode = null;
+
+		this.lightNode = null;
+
+		this.positionNode = null;
+
+		this.setDefaultValues( defaultValues );
+
+		this.setValues( parameters );
+
+	}
+
+	constructLightingModel( /*builder*/ ) {
+
+		return phongLightingModel;
+
+	}
+
+	constructVariants( builder, stack ) {
+
+		// SHININESS
+
+		const shininessNode = float( this.shininessNode || materialShininess ).max( 1e-4 ); // to prevent pow( 0.0, 0.0 )
+
+		stack.assign( shininess, shininessNode );
+
+		// SPECULAR COLOR
+
+		const specularNode = vec3( this.specularNode || materialSpecularColor );
+
+		stack.assign( specularColor, specularNode );
+
+	}
+
+	copy( source ) {
+
+		this.colorNode = source.colorNode;
+		this.opacityNode = source.opacityNode;
+
+		this.alphaTestNode = source.alphaTestNode;
+
+		this.shininessNode = source.shininessNode;
+		this.specularNode = source.specularNode;
+
+		this.lightNode = source.lightNode;
+
+		this.positionNode = source.positionNode;
+
+		return super.copy( source );
+
+	}
+
+}
+
+export default MeshPhongNodeMaterial;

+ 12 - 80
examples/jsm/nodes/materials/MeshStandardNodeMaterial.js

@@ -1,15 +1,11 @@
 import NodeMaterial from './NodeMaterial.js';
 import {
-	float, vec3, vec4, normalView, add, context,
-	assign, label, mul, invert, mix, texture, uniform,
-	materialRoughness, materialMetalness, materialEmissive
+	float, vec3, vec4, mix,
+	materialRoughness, materialMetalness, materialColor, diffuseColor,
+	metalness, roughness, specularColor
 } from '../shadernode/ShaderNodeElements.js';
-import LightsNode from '../lighting/LightsNode.js';
-import EnvironmentNode from '../lighting/EnvironmentNode.js';
-import AONode from '../lighting/AONode.js';
 import getRoughness from '../functions/material/getRoughness.js';
-import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
-import NormalMapNode from '../display/NormalMapNode.js';
+import physicalLightingModel from '../functions/PhysicalLightingModel.js';
 
 import { MeshStandardMaterial } from 'three';
 
@@ -47,97 +43,33 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 	}
 
-	build( builder ) {
+	constructLightingModel( /*builder*/ ) {
 
-		this.generatePosition( builder );
-
-		const colorNodes = this.generateDiffuseColor( builder );
-		const { colorNode } = colorNodes;
-		let { diffuseColorNode } = colorNodes;
-
-		const envNode = this.envNode || builder.scene.environmentNode;
-
-		diffuseColorNode = this.generateStandardMaterial( builder, { colorNode, diffuseColorNode } );
-
-		if ( this.lightsNode ) builder.lightsNode = this.lightsNode;
-
-		const materialLightsNode = [];
-
-		if ( envNode ) {
-
-			materialLightsNode.push( new EnvironmentNode( envNode ) );
-
-		}
-
-		if ( builder.material.aoMap ) {
-
-			materialLightsNode.push( new AONode( texture( builder.material.aoMap ) ) );
-
-		}
-
-		if ( materialLightsNode.length > 0 ) {
-
-			builder.lightsNode = new LightsNode( [ ...builder.lightsNode.lightNodes, ...materialLightsNode ] );
-
-		}
-
-		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightingModelNode: PhysicalLightingModel } );
-
-		this.generateOutput( builder, { diffuseColorNode, outgoingLightNode } );
+		return physicalLightingModel;
 
 	}
 
-	generateStandardMaterial( builder, { colorNode, diffuseColorNode } ) {
-
-		const { material } = builder;
+	constructVariants( builder, stack ) {
 
 		// METALNESS
 
 		let metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness;
 
-		metalnessNode = builder.addFlow( 'fragment', label( metalnessNode, 'Metalness' ) );
-		builder.addFlow( 'fragment', assign( diffuseColorNode, vec4( mul( diffuseColorNode.rgb, invert( metalnessNode ) ), diffuseColorNode.a ) ) );
+		stack.assign( metalness, metalnessNode );
+		stack.assign( diffuseColor, vec4( diffuseColor.rgb.mul( metalnessNode.invert() ), diffuseColor.a ) );
 
 		// ROUGHNESS
 
 		let roughnessNode = this.roughnessNode ? float( this.roughnessNode ) : materialRoughness;
 		roughnessNode = getRoughness.call( { roughness: roughnessNode } );
 
-		builder.addFlow( 'fragment', label( roughnessNode, 'Roughness' ) );
+		stack.assign( roughness, roughnessNode );
 
 		// SPECULAR COLOR
 
-		const specularColorNode = mix( vec3( 0.04 ), colorNode.rgb, metalnessNode );
-
-		builder.addFlow( 'fragment', label( specularColorNode, 'SpecularColor' ) );
-
-		// NORMAL VIEW
-
-		const normalNode = this.normalNode ? vec3( this.normalNode ) : ( material.normalMap ? new NormalMapNode( texture( material.normalMap ), uniform( material.normalScale ) ) : normalView );
-
-		builder.addFlow( 'fragment', label( normalNode, 'TransformedNormalView' ) );
-
-		return diffuseColorNode;
-
-	}
-
-	generateLight( builder, { diffuseColorNode, lightingModelNode, lightsNode = builder.lightsNode } ) {
-
-		const renderer = builder.renderer;
-
-		// OUTGOING LIGHT
-
-		let outgoingLightNode = super.generateLight( builder, { diffuseColorNode, lightingModelNode, lightsNode } );
-
-		// EMISSIVE
-
-		outgoingLightNode = add( vec3( this.emissiveNode || materialEmissive ), outgoingLightNode );
-
-		// TONE MAPPING
-
-		if ( renderer.toneMappingNode ) outgoingLightNode = context( renderer.toneMappingNode, { color: outgoingLightNode } );
+		const specularColorNode = mix( vec3( 0.04 ), materialColor.rgb, metalnessNode );
 
-		return outgoingLightNode;
+		stack.assign( specularColor, specularColorNode );
 
 	}
 

+ 134 - 41
examples/jsm/nodes/materials/NodeMaterial.js

@@ -1,11 +1,15 @@
 import { Material, ShaderMaterial } from 'three';
 import { getNodesKeys, getCacheKey } from '../core/NodeUtils.js';
-import ExpressionNode from '../core/ExpressionNode.js';
+import StackNode from '../core/StackNode.js';
+import LightsNode from '../lighting/LightsNode.js';
+import EnvironmentNode from '../lighting/EnvironmentNode.js';
+import AONode from '../lighting/AONode.js';
 import {
 	float, vec3, vec4,
-	assign, label, mul, bypass, attribute,
-	positionLocal, skinning, instance, modelViewProjection, lightingContext, colorSpace,
-	materialAlphaTest, materialColor, materialOpacity, reference, rangeFog, exp2Fog
+	assign, mul, bypass, attribute, context, texture, lessThanEqual, discard,
+	positionLocal, diffuseColor, skinning, instance, modelViewProjection, lightingContext, colorSpace,
+	materialAlphaTest, materialColor, materialOpacity, materialEmissive, materialNormal, transformedNormalView,
+	reference, rangeFog, densityFog
 } from '../shadernode/ShaderNodeElements.js';
 
 class NodeMaterial extends ShaderMaterial {
@@ -19,34 +23,57 @@ class NodeMaterial extends ShaderMaterial {
 		this.type = this.constructor.name;
 
 		this.lights = true;
+		this.normals = true;
+
+		this.lightsNode = null;
 
 	}
 
-	build( builder ) {
+	customProgramCacheKey() {
 
-		this.generatePosition( builder );
+		return getCacheKey( this );
 
-		const { lightsNode } = this;
-		const { diffuseColorNode } = this.generateDiffuseColor( builder );
+	}
 
-		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightsNode } );
+	build( builder ) {
 
-		this.generateOutput( builder, { diffuseColorNode, outgoingLightNode } );
+		this.construct( builder );
 
 	}
 
-	customProgramCacheKey() {
+	construct( builder ) {
 
-		return getCacheKey( this );
+		// < STACKS >
+
+		const vertexStack = new StackNode();
+		const fragmentStack = new StackNode();
+
+		// < VERTEX STAGE >
+
+		vertexStack.outputNode = this.constructPosition( builder, vertexStack );
+
+		// < FRAGMENT STAGE >
+
+		if ( this.normals === true ) this.constructNormal( builder, fragmentStack );
+
+		this.constructDiffuseColor( builder, fragmentStack );
+		this.constructVariants( builder, fragmentStack );
+
+		const outgoingLightNode = this.constructLighting( builder, fragmentStack );
+
+		fragmentStack.outputNode = this.constructOutput( builder, fragmentStack, outgoingLightNode, diffuseColor.a );
+
+		// < FLOW >
+
+		builder.addFlow( 'vertex', vertexStack );
+		builder.addFlow( 'fragment', fragmentStack );
 
 	}
 
-	generatePosition( builder ) {
+	constructPosition( builder ) {
 
 		const object = builder.object;
 
-		// < VERTEX STAGE >
-
 		let vertex = positionLocal;
 
 		if ( this.positionNode !== null ) {
@@ -69,16 +96,14 @@ class NodeMaterial extends ShaderMaterial {
 
 		builder.context.vertex = vertex;
 
-		builder.addFlow( 'vertex', modelViewProjection() );
+		return modelViewProjection();
 
 	}
 
-	generateDiffuseColor( builder ) {
-
-		// < FRAGMENT STAGE >
+	constructDiffuseColor( builder, stack ) {
 
 		let colorNode = vec4( this.colorNode || materialColor );
-		let opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
+		const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
 
 		// VERTEX COLORS
 
@@ -90,13 +115,11 @@ class NodeMaterial extends ShaderMaterial {
 
 		// COLOR
 
-		colorNode = builder.addFlow( 'fragment', label( colorNode, 'Color' ) );
-		const diffuseColorNode = builder.addFlow( 'fragment', label( colorNode, 'DiffuseColor' ) );
+		stack.assign( diffuseColor, colorNode );
 
 		// OPACITY
 
-		opacityNode = builder.addFlow( 'fragment', label( opacityNode, 'OPACITY' ) );
-		builder.addFlow( 'fragment', assign( diffuseColorNode.a, mul( diffuseColorNode.a, opacityNode ) ) );
+		stack.assign( diffuseColor.a, diffuseColor.a.mul( opacityNode ) );
 
 		// ALPHA TEST
 
@@ -104,39 +127,113 @@ class NodeMaterial extends ShaderMaterial {
 
 			const alphaTestNode = this.alphaTestNode ? float( this.alphaTestNode ) : materialAlphaTest;
 
-			builder.addFlow( 'fragment', label( alphaTestNode, 'AlphaTest' ) );
+			stack.add( discard( lessThanEqual( diffuseColor.a, alphaTestNode ) ) );
+
+		}
+
+	}
+
+	constructVariants( /*builder*/ ) {
+
+		// Interface function.
+
+	}
+
+	constructNormal( builder, stack ) {
 
-			// @TODO: remove ExpressionNode here and then possibly remove it completely
-			builder.addFlow( 'fragment', new ExpressionNode( 'if ( DiffuseColor.a <= AlphaTest ) { discard; }' ) );
+		// NORMAL VIEW
+
+		const normalNode = this.normalNode ? vec3( this.normalNode ) : materialNormal;
+
+		stack.assign( transformedNormalView, normalNode );
+
+		return normalNode;
+
+	}
+
+	constructLights( builder ) {
+
+		let lightsNode = this.lightsNode || builder.lightsNode;
+
+		const envNode = this.envNode || builder.scene.environmentNode;
+		const materialLightsNode = [];
+
+		if ( envNode ) {
+
+			materialLightsNode.push( new EnvironmentNode( envNode ) );
+
+		}
+
+		if ( builder.material.aoMap ) {
+
+			materialLightsNode.push( new AONode( texture( builder.material.aoMap ) ) );
+
+		}
+
+		if ( materialLightsNode.length > 0 ) {
+
+			lightsNode = new LightsNode( [ ...lightsNode.lightNodes, ...materialLightsNode ] );
 
 		}
 
-		return { colorNode, diffuseColorNode };
+		return lightsNode;
+
+	}
+
+	constructLightingModel( /*builder*/ ) {
+
+		// Interface function.
 
 	}
 
-	generateLight( builder, { diffuseColorNode, lightingModelNode, lightsNode = builder.lightsNode } ) {
+	constructLighting( builder ) {
 
-		// < ANALYTIC LIGHTS >
+		const { material } = builder;
 
 		// OUTGOING LIGHT
 
-		let outgoingLightNode = diffuseColorNode.xyz;
-		if ( lightsNode && lightsNode.hasLight !== false ) outgoingLightNode = builder.addFlow( 'fragment', label( lightingContext( lightsNode, lightingModelNode ), 'Light' ) );
+		const lights = ( this.lights === true ) || this.lightsNode !== null;
+
+		const lightsNode = lights ? this.constructLights( builder ) : null;
+		const lightingModelNode = lightsNode ? this.constructLightingModel( builder ) : null;
+
+		let outgoingLightNode = diffuseColor.xyz;
+
+		if ( lightsNode && lightsNode.hasLight !== false ) {
+
+			outgoingLightNode = lightingContext( lightsNode, lightingModelNode );
+
+		}
+
+		// EMISSIVE
+
+		if ( ( this.emissiveNode && this.emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) {
+
+			outgoingLightNode = outgoingLightNode.add( vec3( this.emissiveNode || materialEmissive ) );
+
+		}
 
 		return outgoingLightNode;
 
 	}
 
-	generateOutput( builder, { diffuseColorNode, outgoingLightNode } ) {
+	constructOutput( builder, stack, outgoingLight, opacity ) {
+
+		const renderer = builder.renderer;
 
-		// OUTPUT
+		// TONE MAPPING
 
-		let outputNode = vec4( outgoingLightNode, diffuseColorNode.a );
+		if ( renderer.toneMappingNode && renderer.toneMappingNode.isNode === true ) {
+
+			outgoingLight = context( renderer.toneMappingNode, { color: outgoingLight } );
+
+		}
+
+		let outputNode = vec4( outgoingLight, opacity );
 
 		// ENCODING
 
-		outputNode = colorSpace( outputNode, builder.renderer.outputEncoding );
+		outputNode = colorSpace( outputNode, renderer.outputEncoding );
 
 		// FOG
 
@@ -148,7 +245,7 @@ class NodeMaterial extends ShaderMaterial {
 
 			if ( fog.isFogExp2 ) {
 
-				fogNode = exp2Fog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) );
+				fogNode = densityFog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) );
 
 			} else if ( fog.isFog ) {
 
@@ -164,10 +261,6 @@ class NodeMaterial extends ShaderMaterial {
 
 		if ( fogNode ) outputNode = vec4( vec3( fogNode.mix( outputNode ) ), outputNode.w );
 
-		// RESULT
-
-		builder.addFlow( 'fragment', label( outputNode, 'Output' ) );
-
 		return outputNode;
 
 	}

+ 3 - 0
examples/jsm/nodes/materials/PointsNodeMaterial.js

@@ -11,6 +11,9 @@ class PointsNodeMaterial extends NodeMaterial {
 
 		this.isPointsNodeMaterial = true;
 
+		this.lights = false;
+		this.normals = false;
+
 		this.transparent = true;
 
 		this.colorNode = null;

+ 16 - 15
examples/jsm/nodes/materials/SpriteNodeMaterial.js

@@ -2,8 +2,8 @@ import NodeMaterial from './NodeMaterial.js';
 import { SpriteMaterial } from 'three';
 import {
 	vec2, vec3, vec4,
-	uniform, add, mul, sub,
-	positionLocal, length, cos, sin,
+	uniform, mul,
+	positionLocal, cos, sin,
 	modelViewMatrix, cameraProjectionMatrix, modelWorldMatrix, materialRotation
 } from '../shadernode/ShaderNodeElements.js';
 
@@ -17,7 +17,8 @@ class SpriteNodeMaterial extends NodeMaterial {
 
 		this.isSpriteNodeMaterial = true;
 
-		this.lights = true;
+		this.lights = false;
+		this.normals = false;
 
 		this.colorNode = null;
 		this.opacityNode = null;
@@ -36,7 +37,7 @@ class SpriteNodeMaterial extends NodeMaterial {
 
 	}
 
-	generatePosition( builder ) {
+	constructPosition( { object, context } ) {
 
 		// < VERTEX STAGE >
 
@@ -47,21 +48,21 @@ class SpriteNodeMaterial extends NodeMaterial {
 		let mvPosition = mul( modelViewMatrix, positionNode ? vec4( positionNode.xyz, 1 ) : vec4( 0, 0, 0, 1 ) );
 
 		let scale = vec2(
-			length( vec3( modelWorldMatrix[ 0 ].x, modelWorldMatrix[ 0 ].y, modelWorldMatrix[ 0 ].z ) ),
-			length( vec3( modelWorldMatrix[ 1 ].x, modelWorldMatrix[ 1 ].y, modelWorldMatrix[ 1 ].z ) )
+			vec3( modelWorldMatrix[ 0 ].x, modelWorldMatrix[ 0 ].y, modelWorldMatrix[ 0 ].z ).length(),
+			vec3( modelWorldMatrix[ 1 ].x, modelWorldMatrix[ 1 ].y, modelWorldMatrix[ 1 ].z ).length()
 		);
 
 		if ( scaleNode !== null ) {
 
-			scale = mul( scale, scaleNode );
+			scale = scale.mul( scaleNode );
 
 		}
 
 		let alignedPosition = vertex.xy;
 
-		if ( builder.object.center && builder.object.center.isVector2 === true ) {
+		if ( object.center && object.center.isVector2 === true ) {
 
-			alignedPosition = sub( alignedPosition, sub( uniform( builder.object.center ), vec2( 0.5 ) ) );
+			alignedPosition = alignedPosition.sub( uniform( object.center ).sub( vec2( 0.5 ) ) );
 
 		}
 
@@ -70,17 +71,17 @@ class SpriteNodeMaterial extends NodeMaterial {
 		const rotation = rotationNode || materialRotation;
 
 		const rotatedPosition = vec2(
-			sub( mul( cos( rotation ), alignedPosition.x ), mul( sin( rotation ), alignedPosition.y ) ),
-			add( mul( sin( rotation ), alignedPosition.x ), mul( cos( rotation ), alignedPosition.y ) )
+			cos( rotation ).mul( alignedPosition.x ).sub( sin( rotation ).mul( alignedPosition.y ) ),
+			sin( rotation ).mul( alignedPosition.x ).add( cos( rotation ).mul( alignedPosition.y ) )
 		);
 
-		mvPosition = vec4( add( mvPosition.xy, rotatedPosition.xy ), mvPosition.z, mvPosition.w );
+		mvPosition = vec4( mvPosition.xy.add( rotatedPosition.xy ), mvPosition.z, mvPosition.w );
 
-		const modelViewProjection = mul( cameraProjectionMatrix, mvPosition );
+		const modelViewProjection = cameraProjectionMatrix.mul( mvPosition );
 
-		builder.context.vertex = vertex;
+		context.vertex = vertex;
 
-		builder.addFlow( 'vertex', modelViewProjection );
+		return modelViewProjection;
 
 	}
 

+ 26 - 14
examples/jsm/nodes/math/CondNode.js

@@ -4,7 +4,7 @@ import ContextNode from '../core/ContextNode.js';
 
 class CondNode extends Node {
 
-	constructor( condNode, ifNode, elseNode ) {
+	constructor( condNode, ifNode, elseNode = null ) {
 
 		super();
 
@@ -18,11 +18,16 @@ class CondNode extends Node {
 	getNodeType( builder ) {
 
 		const ifType = this.ifNode.getNodeType( builder );
-		const elseType = this.elseNode.getNodeType( builder );
 
-		if ( builder.getTypeLength( elseType ) > builder.getTypeLength( ifType ) ) {
+		if ( this.elseNode !== null ) {
 
-			return elseType;
+			const elseType = this.elseNode.getNodeType( builder );
+
+			if ( builder.getTypeLength( elseType ) > builder.getTypeLength( ifType ) ) {
+
+				return elseType;
+
+			}
 
 		}
 
@@ -33,23 +38,30 @@ class CondNode extends Node {
 	generate( builder ) {
 
 		const type = this.getNodeType( builder );
-
 		const context = { tempWrite: false };
-		const nodeProperty = new PropertyNode( null, type ).build( builder );
 
-		const nodeSnippet = new ContextNode( this.condNode/*, context*/ ).build( builder, 'bool' ),
-			ifSnippet = new ContextNode( this.ifNode, context ).build( builder, type ),
-			elseSnippet = new ContextNode( this.elseNode, context ).build( builder, type );
+		const needsProperty = this.ifNode.getNodeType( builder ) !== 'void' || ( this.elseNode && this.elseNode.getNodeType( builder ) !== 'void' );
+		const nodeProperty = needsProperty ? new PropertyNode( type ).build( builder ) : '';
+
+		const nodeSnippet = new ContextNode( this.condNode/*, context*/ ).build( builder, 'bool' );
 
-		builder.addFlowCode( `if ( ${nodeSnippet} ) {
+		builder.addFlowCode( `if ( ${nodeSnippet} ) {\n\n\t\t`, false );
 
-\t\t${nodeProperty} = ${ifSnippet};
+		let ifSnippet = new ContextNode( this.ifNode, context ).build( builder, type );
 
-\t} else {
+		ifSnippet = needsProperty ? nodeProperty + ' = ' + ifSnippet + ';' : ifSnippet;
 
-\t\t${nodeProperty} = ${elseSnippet};
+		builder.addFlowCode( ifSnippet + '\n\n\t}', false );
 
-\t}` );
+		let elseSnippet = this.elseNode ? new ContextNode( this.elseNode, context ).build( builder, type ) : null;
+
+		if ( elseSnippet ) {
+
+			elseSnippet = nodeProperty ? nodeProperty + ' = ' + elseSnippet + ';' : elseSnippet;
+
+			builder.addFlowCode( 'else {\n\n\t\t' + elseSnippet + '\n\n\t}', false );
+
+		}
 
 		return nodeProperty;
 

+ 17 - 8
examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js

@@ -9,6 +9,7 @@ import ExpressionNode from '../core/ExpressionNode.js';
 import FunctionCallNode from '../core/FunctionCallNode.js';
 import FunctionNode from '../core/FunctionNode.js';
 import InstanceIndexNode from '../core/InstanceIndexNode.js';
+import LightingModel from '../core/LightingModel.js';
 import PropertyNode from '../core/PropertyNode.js';
 import UniformNode from '../core/UniformNode.js';
 import VarNode from '../core/VarNode.js';
@@ -47,6 +48,7 @@ import CondNode from '../math/CondNode.js';
 // utils
 import ArrayElementNode from '../utils/ArrayElementNode.js';
 import ConvertNode from '../utils/ConvertNode.js';
+import DiscardNode from '../utils/DiscardNode.js';
 import MaxMipLevelNode from '../utils/MaxMipLevelNode.js';
 
 // shader node utils
@@ -220,20 +222,23 @@ export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_M
 export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX );
 export const cameraPosition = nodeImmutable( CameraNode, CameraNode.POSITION );
 
+export const materialUV = nodeImmutable( MaterialNode, MaterialNode.UV );
 export const materialAlphaTest = nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );
 export const materialColor = nodeImmutable( MaterialNode, MaterialNode.COLOR );
+export const materialShininess = nodeImmutable( MaterialNode, MaterialNode.SHININESS );
 export const materialEmissive = nodeImmutable( MaterialNode, MaterialNode.EMISSIVE );
 export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY );
-//export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
+export const materialSpecularColor = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR );
+export const materialReflectivity = nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY );
 export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
 export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS );
 export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
 
-export const diffuseColor = nodeImmutable( PropertyNode, 'DiffuseColor', 'vec4' );
-export const roughness = nodeImmutable( PropertyNode, 'Roughness', 'float' );
-export const metalness = nodeImmutable( PropertyNode, 'Metalness', 'float' );
-export const alphaTest = nodeImmutable( PropertyNode, 'AlphaTest', 'float' );
-export const specularColor = nodeImmutable( PropertyNode, 'SpecularColor', 'color' );
+export const diffuseColor = nodeImmutable( PropertyNode, 'vec4', 'DiffuseColor' );
+export const roughness = nodeImmutable( PropertyNode, 'float', 'Roughness' );
+export const metalness = nodeImmutable( PropertyNode, 'float', 'Metalness' );
+export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
+export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' );
 
 export const reference = ( name, nodeOrType, object ) => nodeObject( new ReferenceNode( name, getConstNodeType( nodeOrType ), object ) );
 export const materialReference = ( name, nodeOrType, material ) => nodeObject( new MaterialReferenceNode( name, getConstNodeType( nodeOrType ), material ) );
@@ -244,8 +249,8 @@ export const modelViewProjection = nodeProxy( ModelViewProjectionNode );
 export const normalGeometry = nodeImmutable( NormalNode, NormalNode.GEOMETRY );
 export const normalLocal = nodeImmutable( NormalNode, NormalNode.LOCAL );
 export const normalView = nodeImmutable( NormalNode, NormalNode.VIEW );
-export const normalWorld = nodeImmutable( NormalNode, NormalNode.WORLD );
-export const transformedNormalView = nodeImmutable( VarNode, normalView, 'TransformedNormalView' );
+export const normalWorld = nodeImmutable( NormalNode, NormalNode.WORLD );;
+export const transformedNormalView = nodeImmutable( PropertyNode, 'vec3', 'TransformedNormalView' );
 export const transformedNormalWorld = normalize( transformDirection( transformedNormalView, cameraViewMatrix ) );
 
 export const tangentGeometry = nodeImmutable( TangentNode, TangentNode.GEOMETRY );
@@ -262,12 +267,14 @@ export const bitangentWorld = nodeImmutable( BitangentNode, BitangentNode.WORLD
 export const transformedBitangentView = normalize( mul( cross( transformedNormalView, transformedTangentView ), tangentGeometry.w ) );
 export const transformedBitangentWorld = normalize( transformDirection( transformedBitangentView, cameraViewMatrix ) );
 
+export const modelDirection = nodeImmutable( ModelNode, ModelNode.DIRECTION );
 export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX );
 export const modelNormalMatrix = nodeImmutable( ModelNode, ModelNode.NORMAL_MATRIX );
 export const modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX );
 export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION );
 export const modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITION );
 
+export const objectDirection = nodeProxy( Object3DNode, Object3DNode.DIRECTION );
 export const objectViewMatrix = nodeProxy( Object3DNode, Object3DNode.VIEW_MATRIX );
 export const objectNormalMatrix = nodeProxy( Object3DNode, Object3DNode.NORMAL_MATRIX );
 export const objectWorldMatrix = nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX );
@@ -297,10 +304,12 @@ export const faceDirection = sub( mul( float( frontFacing ), 2 ), 1 );
 
 // lighting
 
+export const lightingModel = ( ...params ) => new LightingModel( ...params );
 
 // utils
 
 export const element = nodeProxy( ArrayElementNode );
+export const discard = nodeProxy( DiscardNode );
 
 // miscellaneous
 

+ 10 - 3
examples/jsm/nodes/shadernode/ShaderNodeElements.js

@@ -3,6 +3,7 @@ import CubeTextureNode from '../accessors/CubeTextureNode.js';
 import InstanceNode from '../accessors/InstanceNode.js';
 import ReflectVectorNode from '../accessors/ReflectVectorNode.js';
 import SkinningNode from '../accessors/SkinningNode.js';
+import ExtendedMaterialNode from '../accessors/ExtendedMaterialNode.js';
 
 // display
 import BlendModeNode from '../display/BlendModeNode.js';
@@ -53,7 +54,8 @@ export * from './ShaderNodeBaseElements.js';
 
 // functions
 
-export { default as BRDF_GGX } from '../functions/BSDF/BRDF_GGX.js'; // see https://github.com/tc39/proposal-export-default-from
+export { default as BRDF_BlinnPhong } from '../functions/BSDF/BRDF_BlinnPhong.js';
+export { default as BRDF_GGX } from '../functions/BSDF/BRDF_GGX.js';
 export { default as BRDF_Lambert } from '../functions/BSDF/BRDF_Lambert.js';
 export { default as D_GGX } from '../functions/BSDF/D_GGX.js';
 export { default as DFGApprox } from '../functions/BSDF/DFGApprox.js';
@@ -65,7 +67,8 @@ export { default as getDistanceAttenuation } from '../functions/light/getDistanc
 export { default as getGeometryRoughness } from '../functions/material/getGeometryRoughness.js';
 export { default as getRoughness } from '../functions/material/getRoughness.js';
 
-export { default as PhysicalLightingModel } from '../functions/PhysicalLightingModel.js';
+export { default as phongLightingModel } from '../functions/PhongLightingModel.js';
+export { default as physicalLightingModel } from '../functions/PhysicalLightingModel.js';
 
 // accessors
 
@@ -77,6 +80,10 @@ export const reflectVector = nodeImmutable( ReflectVectorNode );
 
 export const skinning = nodeProxy( SkinningNode );
 
+// material
+
+export const materialNormal = nodeImmutable( ExtendedMaterialNode, ExtendedMaterialNode.NORMAL );
+
 // display
 
 export const burn = nodeProxy( BlendModeNode, BlendModeNode.BURN );
@@ -148,4 +155,4 @@ export const checker = nodeProxy( CheckerNode );
 
 export const fog = nodeProxy( FogNode );
 export const rangeFog = nodeProxy( FogRangeNode );
-export const exp2Fog = nodeProxy( FogExp2Node );
+export const densityFog = nodeProxy( FogExp2Node );

+ 18 - 0
examples/jsm/nodes/utils/DiscardNode.js

@@ -0,0 +1,18 @@
+import CondNode from '../math/CondNode.js';
+import ExpressionNode from '../core/ExpressionNode.js';
+
+let discardExpression;
+
+class DiscardNode extends CondNode {
+
+	constructor( condNode ) {
+
+		discardExpression = discardExpression || new ExpressionNode( 'discard' );
+
+		super( condNode, discardExpression );
+
+	}
+
+}
+
+export default DiscardNode;

+ 0 - 12
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -127,18 +127,6 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 	}
 
-	addFlowCode( code ) {
-
-		if ( ! /;\s*$/.test( code ) ) {
-
-			code += ';';
-
-		}
-
-		super.addFlowCode( code + '\n\t' );
-
-	}
-
 	getSampler( textureProperty, uvSnippet, shaderStage = this.shaderStage ) {
 
 		if ( shaderStage === 'fragment' ) {

BIN=BIN
examples/screenshots/webgpu_lights_phong.jpg


+ 198 - 0
examples/webgpu_lights_phong.html

@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js - WebGPU - Lights Phong</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> - WebGPU - Phong Model Lighting<br />
+			<b style="color:red">Left: Red lights</b> - <b>Center: All lights</b> - <b style="color:blue">Right: blue light</b>
+		</div>
+
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import * as Nodes from 'three/nodes';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import { float, color, rangeFog, checker, uv, mix, texture } from 'three/nodes';
+
+			let camera, scene, renderer,
+				light1, light2, light3, light4,
+				stats, controls;
+
+			init();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 100 );
+				camera.position.z = 7;
+
+				scene = new THREE.Scene();
+				scene.fogNode = rangeFog( color( 0xFF00FF ), 3, 30 );
+
+				const sphereGeometry = new THREE.SphereGeometry( 0.1, 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
+
+				const addLight = ( hexColor, power = 1700, distance = 100 ) => {
+
+					const material = new Nodes.MeshPhongNodeMaterial();
+					material.colorNode = color( hexColor );
+					material.lights = false;
+
+					const mesh = new THREE.Mesh( sphereGeometry, material );
+
+					const light = new THREE.PointLight( hexColor, 1, distance );
+					light.power = power;
+					light.add( mesh );
+
+					scene.add( light );
+
+					return light;
+
+				};
+
+				light1 = addLight( 0x0040ff );
+				light2 = addLight( 0xffffff );
+				light3 = addLight( 0x80ff80 );
+				light4 = addLight( 0xffaa00 );
+
+				// light nodes ( selective lights )
+
+				const blueLightsNode = new Nodes.LightsNode().fromLights( [ light1 ] );
+				const whiteLightsNode = new Nodes.LightsNode().fromLights( [ light2 ] );
+
+				// models
+
+				const geometryTeapot = new TeapotGeometry( .8, 18 );
+
+				const leftObject = new THREE.Mesh( geometryTeapot, new Nodes.MeshPhongNodeMaterial( { color: 0x555555 } ) );
+				leftObject.material.lightsNode = blueLightsNode;
+				leftObject.material.specularNode = texture( alphaTexture );
+				leftObject.position.x = - 3;
+				scene.add( leftObject );
+
+				const centerObject = new THREE.Mesh( geometryTeapot, new Nodes.MeshPhongNodeMaterial( { color: 0x555555 } ) );
+				centerObject.material.normalNode = new Nodes.NormalMapNode( texture( normalMapTexture ) );
+				centerObject.material.shininessNode = float( 80 );
+				scene.add( centerObject );
+
+				const rightObject = new THREE.Mesh( geometryTeapot, new Nodes.MeshPhongNodeMaterial( { color: 0x555555 } ) );
+				rightObject.material.lightsNode = whiteLightsNode;
+				//rightObject.material.specular.setHex( 0xFF00FF );
+				rightObject.material.specularNode = mix( color( 0x0000FF ), color( 0xFF0000 ), checker( uv().mul( 5 ) ) );
+				rightObject.material.shininess = 90;
+				rightObject.position.x = 3;
+				scene.add( rightObject );
+
+				leftObject.rotation.y = centerObject.rotation.y = rightObject.rotation.y = Math.PI * - 0.5;
+				leftObject.position.y = centerObject.position.y = rightObject.position.y = - 1;
+
+				// renderer
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+				renderer.outputEncoding = THREE.sRGBEncoding;
+				renderer.toneMappingNode = new Nodes.ToneMappingNode( THREE.LinearToneMapping, .2 );
+
+				// controls
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 3;
+				controls.maxDistance = 25;
+
+				// stats
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				const time = performance.now() / 1000;
+				const lightTime = time * 0.5;
+
+				light1.position.x = Math.sin( lightTime * 0.7 ) * 3;
+				light1.position.y = Math.cos( lightTime * 0.5 ) * 4;
+				light1.position.z = Math.cos( lightTime * 0.3 ) * 3;
+
+				light2.position.x = Math.cos( lightTime * 0.3 ) * 3;
+				light2.position.y = Math.sin( lightTime * 0.5 ) * 4;
+				light2.position.z = Math.sin( lightTime * 0.7 ) * 3;
+
+				light3.position.x = Math.sin( lightTime * 0.7 ) * 3;
+				light3.position.y = Math.cos( lightTime * 0.3 ) * 4;
+				light3.position.z = Math.sin( lightTime * 0.5 ) * 3;
+
+				light4.position.x = Math.sin( lightTime * 0.3 ) * 3;
+				light4.position.y = Math.cos( lightTime * 0.7 ) * 4;
+				light4.position.z = Math.sin( lightTime * 0.5 ) * 3;
+
+				renderer.render( scene, camera );
+
+				stats.update();
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 1
examples/webgpu_lights_selective.html

@@ -84,7 +84,7 @@
 
 					const material = new Nodes.MeshStandardNodeMaterial();
 					material.colorNode = color( hexColor );
-					material.lightsNode = new Nodes.LightsNode(); // ignore scene lights
+					material.lights = false;
 
 					const mesh = new THREE.Mesh( sphereGeometry, material );
 

+ 1 - 0
test/e2e/puppeteer.js

@@ -53,6 +53,7 @@ const exceptionList = [
 	'webgpu_instance_mesh',
 	'webgpu_instance_uniform',
 	'webgpu_lights_custom',
+	'webgpu_lights_phong',
 	'webgpu_lights_selective',
 	'webgpu_loader_gltf',
 	'webgpu_materials',