Browse Source

Nodes: New features and revisions (#25041)

* Nodes: New cache system and revisions (WIP)

* ShaderNode: Added cache()

* cleanup

* Add StackNode

* TempNode.hasDependencies()

* Move .getMIPLevelAlgorithmNode() to context

* cleanup
sunag 2 years ago
parent
commit
f402578785

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

@@ -2,6 +2,7 @@
 import ArrayUniformNode from './core/ArrayUniformNode.js';
 import AttributeNode from './core/AttributeNode.js';
 import BypassNode from './core/BypassNode.js';
+import CacheNode from './core/CacheNode.js';
 import CodeNode from './core/CodeNode.js';
 import ConstNode from './core/ConstNode.js';
 import ContextNode from './core/ContextNode.js';
@@ -12,6 +13,7 @@ import InstanceIndexNode from './core/InstanceIndexNode.js';
 import Node from './core/Node.js';
 import NodeAttribute from './core/NodeAttribute.js';
 import NodeBuilder from './core/NodeBuilder.js';
+import NodeCache from './core/NodeCache.js';
 import NodeCode from './core/NodeCode.js';
 import NodeFrame from './core/NodeFrame.js';
 import NodeFunctionInput from './core/NodeFunctionInput.js';
@@ -20,6 +22,7 @@ import NodeUniform from './core/NodeUniform.js';
 import NodeVar from './core/NodeVar.js';
 import NodeVarying from './core/NodeVarying.js';
 import PropertyNode from './core/PropertyNode.js';
+import StackNode from './core/StackNode.js';
 import TempNode from './core/TempNode.js';
 import UniformNode from './core/UniformNode.js';
 import VarNode from './core/VarNode.js';
@@ -88,6 +91,7 @@ import MaxMipLevelNode from './utils/MaxMipLevelNode.js';
 import OscNode from './utils/OscNode.js';
 import RemapNode from './utils/RemapNode.js';
 import RotateUVNode from './utils/RotateUVNode.js';
+import SpecularMIPLevelNode from './utils/SpecularMIPLevelNode.js';
 import SplitNode from './utils/SplitNode.js';
 import SpriteSheetUVNode from './utils/SpriteSheetUVNode.js';
 import TimerNode from './utils/TimerNode.js';
@@ -129,6 +133,7 @@ const nodeLib = {
 	ArrayUniformNode,
 	AttributeNode,
 	BypassNode,
+	CacheNode,
 	CodeNode,
 	ContextNode,
 	ConstNode,
@@ -139,6 +144,7 @@ const nodeLib = {
 	Node,
 	NodeAttribute,
 	NodeBuilder,
+	NodeCache,
 	NodeCode,
 	NodeFrame,
 	NodeFunctionInput,
@@ -147,6 +153,7 @@ const nodeLib = {
 	NodeVar,
 	NodeVarying,
 	PropertyNode,
+	StackNode,
 	TempNode,
 	UniformNode,
 	VarNode,
@@ -215,6 +222,7 @@ const nodeLib = {
 	OscNode,
 	RemapNode,
 	RotateUVNode,
+	SpecularMIPLevelNode,
 	SplitNode,
 	SpriteSheetUVNode,
 	TimerNode,
@@ -249,6 +257,7 @@ export {
 	ArrayUniformNode,
 	AttributeNode,
 	BypassNode,
+	CacheNode,
 	CodeNode,
 	ContextNode,
 	ConstNode,
@@ -259,6 +268,7 @@ export {
 	Node,
 	NodeAttribute,
 	NodeBuilder,
+	NodeCache,
 	NodeCode,
 	NodeFrame,
 	NodeFunctionInput,
@@ -267,6 +277,7 @@ export {
 	NodeVar,
 	NodeVarying,
 	PropertyNode,
+	StackNode,
 	TempNode,
 	UniformNode,
 	VarNode,
@@ -335,6 +346,7 @@ export {
 	OscNode,
 	RemapNode,
 	RotateUVNode,
+	SpecularMIPLevelNode,
 	SplitNode,
 	SpriteSheetUVNode,
 	TimerNode,

+ 18 - 25
examples/jsm/nodes/accessors/CubeTextureNode.js

@@ -4,6 +4,8 @@ import ReflectVectorNode from './ReflectVectorNode.js';
 
 import { negate, vec3, nodeObject } from '../shadernode/ShaderNodeBaseElements.js';
 
+let defaultUV;
+
 class CubeTextureNode extends TextureNode {
 
 	constructor( value, uvNode = null, levelNode = null ) {
@@ -20,29 +22,11 @@ class CubeTextureNode extends TextureNode {
 
 	}
 
-	getConstructHash( builder ) {
-
-		return `${ this.uuid } / ${ builder.context.environmentContext?.uuid || '' }`;
-
-	}
-
-	construct( builder ) {
-
-		const properties = builder.getNodeProperties( this );
-
-		const uvNode = this.uvNode || builder.context.uvNode || new ReflectVectorNode();
-		let levelNode = this.levelNode || builder.context.levelNode;
-
-		if ( levelNode?.isNode === true ) {
+	getDefaultUV() {
 
-			const texture = this.value;
+		defaultUV ||= new ReflectVectorNode();
 
-			levelNode = builder.context.levelShaderNode ? builder.context.levelShaderNode.call( { texture, levelNode }, builder ) : levelNode;
-
-		}
-
-		properties.uvNode = uvNode;
-		properties.levelNode = levelNode;
+		return defaultUV;
 
 	}
 
@@ -72,15 +56,21 @@ class CubeTextureNode extends TextureNode {
 
 			const nodeData = builder.getDataFromNode( this );
 
-			let snippet = nodeData.snippet;
+			let propertyName = nodeData.propertyName;
 
-			if ( snippet === undefined || builder.context.tempRead === false ) {
+			if ( propertyName === undefined ) {
 
 				const uvNodeObject = nodeObject( uvNode );
 				const cubeUV = vec3( negate( uvNodeObject.x ), uvNodeObject.yz );
 				const uvSnippet = cubeUV.build( builder, 'vec3' );
 
-				if ( levelNode ) {
+				const nodeVar = builder.getVarFromNode( this, 'vec4' );
+
+				propertyName = builder.getPropertyName( nodeVar );
+
+				let snippet = null;
+
+				if ( levelNode?.isNode === true) {
 
 					const levelSnippet = levelNode.build( builder, 'float' );
 
@@ -92,11 +82,14 @@ class CubeTextureNode extends TextureNode {
 
 				}
 
+				builder.addFlowCode( `${propertyName} = ${snippet}` );
+
 				nodeData.snippet = snippet;
+				nodeData.propertyName = propertyName;
 
 			}
 
-			return builder.format( snippet, 'vec4', output );
+			return builder.format( propertyName, 'vec4', output );
 
 		}
 

+ 2 - 2
examples/jsm/nodes/accessors/MaterialNode.js

@@ -47,7 +47,7 @@ class MaterialNode extends Node {
 
 	}
 
-	generate( builder, output ) {
+	construct( builder ) {
 
 		const material = builder.context.material;
 		const scope = this.scope;
@@ -143,7 +143,7 @@ class MaterialNode extends Node {
 
 		}
 
-		return node.build( builder, output );
+		return node;
 
 	}
 

+ 6 - 0
examples/jsm/nodes/accessors/NormalNode.js

@@ -21,6 +21,12 @@ class NormalNode extends Node {
 
 	}
 
+	isGlobal() {
+
+		return true;
+
+	}
+
 	getHash( /*builder*/ ) {
 
 		return `normal-${this.scope}`;

+ 6 - 0
examples/jsm/nodes/accessors/PositionNode.js

@@ -22,6 +22,12 @@ class PositionNode extends Node {
 
 	}
 
+	isGlobal() {
+
+		return true;
+
+	}
+
 	getHash( /*builder*/ ) {
 
 		return `position-${this.scope}`;

+ 59 - 7
examples/jsm/nodes/accessors/TextureNode.js

@@ -1,9 +1,11 @@
 import UniformNode from '../core/UniformNode.js';
 import UVNode from './UVNode.js';
 
+let defaultUV;
+
 class TextureNode extends UniformNode {
 
-	constructor( value, uvNode = new UVNode(), levelNode = null ) {
+	constructor( value, uvNode = null, levelNode = null ) {
 
 		super( value, 'vec4' );
 
@@ -26,8 +28,51 @@ class TextureNode extends UniformNode {
 
 	}
 
+	getDefaultUV() {
+
+		defaultUV ||= new UVNode();
+
+		return defaultUV;
+
+	}
+
+	construct( builder ) {
+
+		const properties = builder.getNodeProperties( this );
+
+		//
+
+		let uvNode = this.uvNode;
+
+		if ( uvNode === null && builder.context.getUVNode ) {
+
+			uvNode = builder.context.getUVNode( this )
+
+		}
+
+		uvNode ||= this.getDefaultUV();
+
+		//
+
+		let levelNode = this.levelNode;
+
+		if ( levelNode === null && builder.context.getSamplerLevelNode ) {
+
+			levelNode = builder.context.getSamplerLevelNode( this );
+
+		}
+
+		//
+
+		properties.uvNode = uvNode;
+		properties.levelNode = levelNode ? builder.context.getMIPLevelAlgorithmNode( this, levelNode ) : null;
+
+	}
+
 	generate( builder, output ) {
 
+		const { uvNode, levelNode } = builder.getNodeProperties( this );
+
 		const texture = this.value;
 
 		if ( ! texture || texture.isTexture !== true ) {
@@ -50,14 +95,18 @@ class TextureNode extends UniformNode {
 
 			const nodeData = builder.getDataFromNode( this );
 
-			let snippet = nodeData.snippet;
+			let propertyName = nodeData.propertyName;
 
-			if ( snippet === undefined ) {
+			if ( propertyName === undefined ) {
 
-				const uvSnippet = this.uvNode.build( builder, 'vec2' );
-				const levelNode = this.levelNode;
+				const uvSnippet = uvNode.build( builder, 'vec2' );
+				const nodeVar = builder.getVarFromNode( this, 'vec4' );
 
-				if ( levelNode !== null ) {
+				propertyName = builder.getPropertyName( nodeVar );
+
+				let snippet = null;
+
+				if ( levelNode?.isNode === true) {
 
 					const levelSnippet = levelNode.build( builder, 'float' );
 
@@ -69,11 +118,14 @@ class TextureNode extends UniformNode {
 
 				}
 
+				builder.addFlowCode( `${propertyName} = ${snippet}` );
+
 				nodeData.snippet = snippet;
+				nodeData.propertyName = propertyName;
 
 			}
 
-			return builder.format( snippet, 'vec4', output );
+			return builder.format( propertyName, 'vec4', output );
 
 		}
 

+ 39 - 0
examples/jsm/nodes/core/CacheNode.js

@@ -0,0 +1,39 @@
+import Node from './Node.js';
+import NodeCache from './NodeCache.js';
+
+class CacheNode extends Node {
+
+	constructor( node, cache = new NodeCache() ) {
+
+		super();
+
+		this.isCacheNode = true;
+
+		this.node = node;
+		this.cache = cache;
+
+	}
+
+	getNodeType( builder ) {
+
+		return this.node.getNodeType( builder );
+
+	}
+
+	build( builder, ...params ) {
+
+		const previousCache = builder.getCache();
+
+		builder.setCache( this.cache );
+
+		const data = this.node.build( builder, ...params );
+
+		builder.setCache( previousCache );
+
+		return data;
+
+	}
+
+}
+
+export default CacheNode;

+ 20 - 6
examples/jsm/nodes/core/Node.js

@@ -26,6 +26,12 @@ class Node {
 
 	}
 
+	isGlobal( /*builder*/ ) {
+
+		return false;
+
+	}
+
 	getChildren() {
 
 		const children = [];
@@ -50,6 +56,20 @@ class Node {
 
 				children.push( object );
 
+			} else if ( typeof object === 'object' ) {
+
+				for ( const property in object ) {
+
+					const child = object[ property ];
+
+					if ( child?.isNode === true ) {
+
+						children.push( child );
+
+					}
+
+				}
+
 			}
 
 		}
@@ -82,12 +102,6 @@ class Node {
 
 	}
 
-	getConstructHash( /*builder*/ ) {
-
-		return this.uuid;
-
-	}
-
 	getReference( builder ) {
 
 		const hash = this.getHash( builder );

+ 46 - 53
examples/jsm/nodes/core/NodeBuilder.js

@@ -4,11 +4,15 @@ import NodeVarying from './NodeVarying.js';
 import NodeVar from './NodeVar.js';
 import NodeCode from './NodeCode.js';
 import NodeKeywords from './NodeKeywords.js';
+import NodeCache from './NodeCache.js';
 import { NodeUpdateType } from './constants.js';
 
 import { REVISION, LinearEncoding, Color, Vector2, Vector3, Vector4 } from 'three';
 
+import { mul, maxMipLevel } from '../shadernode/ShaderNodeElements.js';
+
 export const defaultShaderStages = [ 'fragment', 'vertex' ];
+export const defaultBuildStages = [ 'construct', 'analyze', 'generate' ]
 export const shaderStages = [ ...defaultShaderStages, 'compute' ];
 export const vector = [ 'x', 'y', 'z', 'w' ];
 
@@ -61,10 +65,13 @@ class NodeBuilder {
 
 		this.context = {
 			keywords: new NodeKeywords(),
-			material: object.material
+			material: object.material,
+			getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => mul( levelNode, maxMipLevel( textureNode ) )
 		};
 
-		this.nodesData = new WeakMap();
+		this.cache = new NodeCache();
+		this.globalCache = this.cache;
+
 		this.flowsData = new WeakMap();
 
 		this.shaderStage = null;
@@ -162,6 +169,18 @@ class NodeBuilder {
 
 	}
 
+	setCache( cache ) {
+
+		this.cache = cache;
+
+	}
+
+	getCache() {
+
+		return this.cache;
+
+	}
+
 	isAvailable( /*name*/ ) {
 
 		return false;
@@ -434,13 +453,15 @@ class NodeBuilder {
 
 	getDataFromNode( node, shaderStage = this.shaderStage ) {
 
-		let nodeData = this.nodesData.get( node );
+		const cache = node.isGlobal( this ) ? this.globalCache : this.cache;
+
+		let nodeData = cache.getNodeData( node );
 
 		if ( nodeData === undefined ) {
 
 			nodeData = { vertex: {}, fragment: {}, compute: {} };
 
-			this.nodesData.set( node, nodeData );
+			cache.setNodeData( node, nodeData );
 
 		}
 
@@ -450,13 +471,11 @@ class NodeBuilder {
 
 	getNodeProperties( node, shaderStage = this.shaderStage ) {
 
-		const nodeData = this.getDataFromNode( this, shaderStage );
-		const constructHash = node.getConstructHash( this );
+		const nodeData = this.getDataFromNode( node, shaderStage );
 
-		nodeData.properties = nodeData.properties || {};
-		nodeData.properties[ constructHash ] = nodeData.properties[ constructHash ] || { outputNode: null };
+		nodeData.properties ||= { outputNode: null };
 
-		return nodeData.properties[ constructHash ];
+		return nodeData.properties;
 
 	}
 
@@ -703,65 +722,39 @@ class NodeBuilder {
 
 	build() {
 
-		// stage 1: generate shader node
-
-		this.setBuildStage( 'construct' );
-
-		for ( const shaderStage of shaderStages ) {
-
-			this.setShaderStage( shaderStage );
+		// construct() -> stage 1: create possible new nodes and returns an output reference node
+		// analyze()   -> stage 2: analyze nodes to possible optimization and validation
+		// generate()  -> stage 3: generate shader
 
-			const flowNodes = this.flowNodes[ shaderStage ];
+		for ( const buildStage of defaultBuildStages ) {
 
-			for ( const node of flowNodes ) {
+			this.setBuildStage( buildStage );
 
-				node.build( this );
+			if ( this.context.vertex && this.context.vertex.isNode ) {
 
-			}
-
-		}
-
-		// stage 2: analyze nodes to possible optimization and validation
-
-		this.setBuildStage( 'analyze' );
-
-		for ( const shaderStage of shaderStages ) {
-
-			this.setShaderStage( shaderStage );
-
-			const flowNodes = this.flowNodes[ shaderStage ];
-
-			for ( const node of flowNodes ) {
-
-				node.build( this );
+				this.flowNodeFromShaderStage( 'vertex', this.context.vertex );
 
 			}
 
-		}
-
-		// stage 3: pre-build vertex code used in fragment shader
+			for ( const shaderStage of shaderStages ) {
 
-		this.setBuildStage( 'generate' );
+				this.setShaderStage( shaderStage );
 
-		if ( this.context.vertex && this.context.vertex.isNode ) {
-
-			this.flowNodeFromShaderStage( 'vertex', this.context.vertex );
-
-		}
+				const flowNodes = this.flowNodes[ shaderStage ];
 
-		// stage 4: generate shader
+				for ( const node of flowNodes ) {
 
-		this.setBuildStage( 'generate' );
+					if ( buildStage === 'generate' ) {
 
-		for ( const shaderStage of shaderStages ) {
+						this.flowNode( node );
 
-			this.setShaderStage( shaderStage );
+					} else {
 
-			const flowNodes = this.flowNodes[ shaderStage ];
+						node.build( this );
 
-			for ( const node of flowNodes ) {
+					}
 
-				this.flowNode( node );
+				}
 
 			}
 
@@ -770,7 +763,7 @@ class NodeBuilder {
 		this.setBuildStage( null );
 		this.setShaderStage( null );
 
-		// stage 5: build code for a specific output
+		// stage 4: build code for a specific output
 
 		this.buildCode();
 

+ 26 - 0
examples/jsm/nodes/core/NodeCache.js

@@ -0,0 +1,26 @@
+let id = 0;
+
+class NodeCache {
+
+	constructor() {
+
+		this.id = id ++;
+		this.nodesData = new WeakMap();
+
+	}
+
+	getNodeData( node ) {
+
+		return this.nodesData.get( node );
+
+	}
+
+	setNodeData( node, data ) {
+
+		this.nodesData.set( node, data );
+
+	}
+
+}
+
+export default NodeCache;

+ 45 - 0
examples/jsm/nodes/core/StackNode.js

@@ -0,0 +1,45 @@
+import Node from './Node.js';
+import OperatorNode from '../math/OperatorNode.js';
+
+class StackNode extends Node {
+
+	constructor() {
+
+		super();
+
+		this.nodes = [];
+		this.outputNode = null;
+
+		this.isStackNode = true;
+
+	}
+
+	getNodeType( builder ) {
+
+		return this.outputNode ? this.outputNode.getNodeType( builder ) : 'void';
+
+	}
+
+	assign( targetNode, sourceValue ) {
+
+		this.nodes.push( new OperatorNode( '=', targetNode, sourceValue ) );
+
+		return this;
+
+	}
+
+	build( builder, ...params ) {
+
+		for ( const node of this.nodes ) {
+
+			node.build( builder );
+
+		}
+
+		return this.outputNode ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params );
+
+	}
+
+}
+
+export default StackNode;

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

@@ -10,6 +10,12 @@ class TempNode extends Node {
 
 	}
 
+	hasDependencies( builder ) {
+
+		return builder.getDataFromNode( this ).dependenciesCount > 1;
+
+	}
+
 	build( builder, output ) {
 
 		const buildStage = builder.getBuildStage();
@@ -23,7 +29,7 @@ class TempNode extends Node {
 
 				return builder.format( nodeData.propertyName, type, output );
 
-			} else if ( builder.context.tempWrite !== false && type !== 'void ' && output !== 'void' && nodeData.dependenciesCount > 1 ) {
+			} else if ( builder.context.tempWrite !== false && type !== 'void ' && output !== 'void' && this.hasDependencies( builder ) ) {
 
 				const snippet = super.build( builder, type );
 

+ 6 - 0
examples/jsm/nodes/core/VarNode.js

@@ -50,6 +50,12 @@ class VarNode extends Node {
 
 	}
 
+	isGlobal() {
+
+		return true;
+
+	}
+
 	getHash( builder ) {
 
 		return this.name || super.getHash( builder );

+ 6 - 0
examples/jsm/nodes/core/VaryingNode.js

@@ -12,6 +12,12 @@ class VaryingNode extends Node {
 
 	}
 
+	isGlobal() {
+
+		return true;
+
+	}
+
 	getHash( builder ) {
 
 		return this.name || super.getHash( builder );

+ 93 - 30
examples/jsm/nodes/lighting/EnvironmentNode.js

@@ -1,19 +1,8 @@
 import LightingNode from './LightingNode.js';
 import ContextNode from '../core/ContextNode.js';
-import MaxMipLevelNode from '../utils/MaxMipLevelNode.js';
-import { ShaderNode, float, add, mul, div, log2, clamp, roughness, reflect, mix, positionViewDirection, negate, normalize, transformedNormalView, transformedNormalWorld, transformDirection, cameraViewMatrix } from '../shadernode/ShaderNodeElements.js';
-
-// taken from here: http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html
-const getSpecularMIPLevel = new ShaderNode( ( { texture, levelNode } ) => {
-
-	const maxMIPLevelScalar = new MaxMipLevelNode( texture );
-
-	const sigma = div( mul( Math.PI, mul( levelNode, levelNode ) ), add( 1.0, levelNode ) );
-	const desiredMIPLevel = add( maxMIPLevelScalar, log2( sigma ) );
-
-	return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
-
-} );
+import CacheNode from '../core/CacheNode.js';
+import SpecularMIPLevelNode from '../utils/SpecularMIPLevelNode.js';
+import { float, mul, roughness, reflect, mix, positionViewDirection, negate, normalize, transformedNormalView, transformedNormalWorld, transformDirection, cameraViewMatrix, equirectUV, vec2, invert } from '../shadernode/ShaderNodeElements.js';
 
 class EnvironmentNode extends LightingNode {
 
@@ -30,33 +19,107 @@ class EnvironmentNode extends LightingNode {
 		const envNode = this.envNode;
 		const properties = builder.getNodeProperties( this );
 
-		let reflectVec = reflect( negate( positionViewDirection ), transformedNormalView );
-		reflectVec = normalize( mix( reflectVec, transformedNormalView, mul( roughness, roughness ) ) );
-		reflectVec = transformDirection( reflectVec, cameraViewMatrix );
+		let reflectVec;
+		let radianceTextureUVNode;
+		let irradianceTextureUVNode;
 
 		const radianceContext = new ContextNode( envNode, {
-			tempRead: false,
-			uvNode: reflectVec,
-			levelNode: roughness,
-			levelShaderNode: getSpecularMIPLevel
+			getUVNode: ( textureNode ) => {
+
+				let node = null;
+
+				if ( reflectVec === undefined ) {
+
+					reflectVec = reflect( negate( positionViewDirection ), transformedNormalView );
+					reflectVec = normalize( mix( reflectVec, transformedNormalView, mul( roughness, roughness ) ) );
+					reflectVec = transformDirection( reflectVec, cameraViewMatrix );
+
+				}
+
+				if ( textureNode.isCubeTextureNode ) {
+
+					node = reflectVec;
+
+				} else if ( textureNode.isTextureNode ) {
+
+					if ( radianceTextureUVNode === undefined ) {
+
+						// @TODO: Needed PMREM
+
+						radianceTextureUVNode = equirectUV( reflectVec );
+						radianceTextureUVNode = vec2( radianceTextureUVNode.x, invert( radianceTextureUVNode.y ) );
+
+					}
+
+					node = radianceTextureUVNode;
+
+				}
+
+				return node;
+
+			},
+			getSamplerLevelNode: () => {
+
+				return roughness;
+
+			},
+			getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => {
+
+				return new SpecularMIPLevelNode( textureNode, levelNode );
+
+			}
 		} );
 
 		const irradianceContext = new ContextNode( envNode, {
-			tempRead: false,
-			uvNode: transformedNormalWorld,
-			levelNode: float( 1 ),
-			levelShaderNode: getSpecularMIPLevel
+			getUVNode: ( textureNode ) => {
+
+				let node = null;
+
+				if ( textureNode.isCubeTextureNode ) {
+
+					node = transformedNormalWorld;
+
+				} else if ( textureNode.isTextureNode ) {
+
+					if ( irradianceTextureUVNode === undefined ) {
+
+						// @TODO: Needed PMREM
+
+						irradianceTextureUVNode = equirectUV( transformedNormalWorld );
+						irradianceTextureUVNode = vec2( irradianceTextureUVNode.x, invert( irradianceTextureUVNode.y ) );
+
+					}
+
+					node = irradianceTextureUVNode;
+
+				}
+
+				return node;
+
+			},
+			getSamplerLevelNode: () => {
+
+				return float( 1 );
+
+			},
+			getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => {
+
+				return new SpecularMIPLevelNode( textureNode, levelNode );
+
+			}
 		} );
 
-		// it's used to cache the construct only if necessary: See `CubeTextureNode.getConstructReference()`
-		radianceContext.context.environmentContext = radianceContext;
-		irradianceContext.context.environmentContext = irradianceContext;
+		//
+
+		const isolateRadianceFlowContext = new CacheNode( radianceContext );
+
+		//
 
-		builder.context.radiance.add( radianceContext );
+		builder.context.radiance.add( isolateRadianceFlowContext );
 
 		builder.context.iblIrradiance.add( mul( Math.PI, irradianceContext ) );
 
-		properties.radianceContext = radianceContext;
+		properties.radianceContext = isolateRadianceFlowContext;
 		properties.irradianceContext = irradianceContext;
 
 	}

+ 6 - 0
examples/jsm/nodes/math/OperatorNode.js

@@ -27,6 +27,12 @@ class OperatorNode extends TempNode {
 
 	}
 
+	hasDependencies( builder ) {
+
+		return this.op !== '=' ? super.hasDependencies( builder ) : false;
+
+	}
+
 	getNodeType( builder, output ) {
 
 		const op = this.op;

+ 10 - 6
examples/jsm/nodes/shadernode/ShaderNode.js

@@ -4,6 +4,7 @@ import ConvertNode from '../utils/ConvertNode.js';
 import JoinNode from '../utils/JoinNode.js';
 import SplitNode from '../utils/SplitNode.js';
 import ConstNode from '../core/ConstNode.js';
+import StackNode from '../core/StackNode.js';
 import { getValueFromType } from '../core/NodeUtils.js';
 
 const shaderNodeHandler = {
@@ -166,17 +167,20 @@ class ShaderNodeInternal extends Node {
 
 	}
 
-	generate( builder, output ) {
+	getNodeType( builder ) {
 
-		const nodeCall = this.call( {}, builder );
+		const { outputNode } = builder.getNodeProperties( this );
 
-		if ( nodeCall === undefined ) {
+		return outputNode ? outputNode.getNodeType( builder ) : super.getNodeType( builder );
 
-			return '';
+	}
 
-		}
+	construct( builder ) {
+
+		const stackNode = new StackNode();
+		stackNode.outputNode = this.call( {}, stackNode, builder );
 
-		return builder.format( nodeCall.build( builder ), nodeCall.getNodeType( builder ), output );
+		return stackNode;
 
 	}
 

+ 4 - 0
examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js

@@ -2,6 +2,7 @@
 //import ArrayUniformNode from '../core/ArrayUniformNode.js';
 import AttributeNode from '../core/AttributeNode.js';
 import BypassNode from '../core/BypassNode.js';
+import CacheNode from '../core/CacheNode.js';
 import CodeNode from '../core/CodeNode.js';
 import ContextNode from '../core/ContextNode.js';
 import ExpressionNode from '../core/ExpressionNode.js';
@@ -45,6 +46,7 @@ import CondNode from '../math/CondNode.js';
 // utils
 import ArrayElementNode from '../utils/ArrayElementNode.js';
 import ConvertNode from '../utils/ConvertNode.js';
+import MaxMipLevelNode from '../utils/MaxMipLevelNode.js';
 
 // shader node utils
 import { ShaderNode, nodeObject, nodeObjects, nodeArray, nodeProxy, nodeImmutable, ConvertType, getConstNodeType, cacheMaps } from './ShaderNode.js';
@@ -117,8 +119,10 @@ export const attribute = ( name, nodeType ) => nodeObject( new AttributeNode( na
 export const property = ( name, nodeOrType ) => nodeObject( new PropertyNode( name, getConstNodeType( nodeOrType ) ) );
 
 export const convert = ( node, types ) => nodeObject( new ConvertNode( nodeObject( node ), types ) );
+export const maxMipLevel = nodeProxy( MaxMipLevelNode );
 
 export const bypass = nodeProxy( BypassNode );
+export const cache = nodeProxy( CacheNode );
 export const code = nodeProxy( CodeNode );
 export const context = nodeProxy( ContextNode );
 export const expression = nodeProxy( ExpressionNode );

+ 2 - 2
examples/jsm/nodes/shadernode/ShaderNodeElements.js

@@ -21,10 +21,10 @@ import LightingContextNode from '../lighting/LightingContextNode.js';
 // utils
 import EquirectUVNode from '../utils/EquirectUVNode.js';
 import MatcapUVNode from '../utils/MatcapUVNode.js';
-import MaxMipLevelNode from '../utils/MaxMipLevelNode.js';
 import OscNode from '../utils/OscNode.js';
 import RemapNode from '../utils/RemapNode.js';
 import RotateUVNode from '../utils/RotateUVNode.js';
+import SpecularMIPLevelNode from '../utils/SpecularMIPLevelNode.js';
 import SpriteSheetUVNode from '../utils/SpriteSheetUVNode.js';
 import TimerNode from '../utils/TimerNode.js';
 import TriplanarTexturesNode from '../utils/TriplanarTexturesNode.js';
@@ -112,7 +112,7 @@ export const lightingContext = nodeProxy( LightingContextNode );
 export const matcapUV = nodeImmutable( MatcapUVNode );
 export const equirectUV = nodeProxy( EquirectUVNode );
 
-export const maxMipLevel = nodeProxy( MaxMipLevelNode );
+export const specularMIPLevel = nodeProxy( SpecularMIPLevelNode );
 
 export const oscSine = nodeProxy( OscNode, OscNode.SINE );
 export const oscSquare = nodeProxy( OscNode, OscNode.SQUARE );

+ 11 - 4
examples/jsm/nodes/utils/MaxMipLevelNode.js

@@ -3,20 +3,27 @@ import { NodeUpdateType } from '../core/constants.js';
 
 class MaxMipLevelNode extends UniformNode {
 
-	constructor( texture ) {
+	constructor( textureNode ) {
 
 		super( 0 );
 
-		this.texture = texture;
+		this.textureNode = textureNode;
 
 		this.updateType = NodeUpdateType.FRAME;
 
 	}
 
+	get texture() {
+
+		return this.textureNode.value;
+
+	}
+
 	update() {
 
-		const images = this.texture.images;
-		const image = ( images && images.length > 0 ) ? ( images[ 0 ]?.image || images[ 0 ] ) : this.texture.image;
+		const texture = this.texture;
+		const images = texture.images;
+		const image = ( images && images.length > 0 ) ? ( images[ 0 ]?.image || images[ 0 ] ) : texture.image;
 
 		if ( image?.width !== undefined ) {
 

+ 32 - 0
examples/jsm/nodes/utils/SpecularMIPLevelNode.js

@@ -0,0 +1,32 @@
+import Node from '../core/Node.js';
+import { add, mul, div, log2, clamp, maxMipLevel } from '../shadernode/ShaderNodeBaseElements.js';
+
+class SpecularMIPLevelNode extends Node {
+
+	constructor( textureNode, roughnessNode = null ) {
+
+		super( 'float' );
+
+		this.textureNode = textureNode;
+		this.roughnessNode = roughnessNode;
+
+	}
+
+	construct() {
+
+		const { textureNode, roughnessNode } = this;
+
+		// taken from here: http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html
+
+		const maxMIPLevelScalar = maxMipLevel( textureNode );
+
+		const sigma = div( mul( Math.PI, mul( roughnessNode, roughnessNode ) ), add( 1.0, roughnessNode ) );
+		const desiredMIPLevel = add( maxMIPLevelScalar, log2( sigma ) );
+
+		return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
+
+	}
+
+}
+
+export default SpecularMIPLevelNode;

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

@@ -82,7 +82,8 @@ class WebGPUBackground {
 				} else /*if ( background.isNode === true )*/ {
 
 					node = context( background, {
-						uvNode: transformDirection( positionWorld, modelWorldMatrix )
+						// @TODO: Add Texture2D support using node context
+						getUVNode: () => transformDirection( positionWorld, modelWorldMatrix )
 					} );
 
 				}

+ 4 - 4
examples/webgpu_audio_processing.html

@@ -13,7 +13,7 @@
 
 		<div id="info">
 			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - Audio Processing
-			<br>Click on the screen to process the audio using WebGPU.
+			<br>Click on screen to process the audio using WebGPU.
 		</div>
 
 		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
@@ -35,7 +35,7 @@
 			import {
 				ShaderNode, compute,
 				uniform, element, storage, instanceIndex,
-				float, assign, add, sub, div, mul, texture, viewportTopLeft, color
+				float, add, sub, div, mul, texture, viewportTopLeft, color
 			} from 'three/nodes';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
@@ -141,7 +141,7 @@
 
 				// compute (shader-node)
 
-				const computeShaderNode = new ShaderNode( ( inputs, builder ) => {
+				const computeShaderNode = new ShaderNode( ( inputs, stack ) => {
 
 					const index = float( instanceIndex );
 
@@ -169,7 +169,7 @@
 
 					const waveStorageElementNode = element( waveStorageNode, instanceIndex );
 
-					assign( waveStorageElementNode, wave ).build( builder );
+					stack.assign( waveStorageElementNode, wave );
 
 				} );
 

+ 9 - 10
examples/webgpu_compute.html

@@ -31,7 +31,7 @@
 			import {
 				ShaderNode, compute,
 				uniform, element, storage, attribute, mul, sin, cos,
-				temp, assign, add, sub, cond, abs, negate, max, min, length, float, vec2, vec3, color,
+				add, sub, cond, abs, negate, max, min, length, float, vec2, vec3, color,
 				greaterThanEqual, lessThanEqual, instanceIndex
 			} from 'three/nodes';
 
@@ -81,7 +81,7 @@
 
 				// create function
 
-				const computeShaderNode = new ShaderNode( ( inputs, builder ) => {
+				const computeShaderNode = new ShaderNode( ( inputs, stack ) => {
 
 					const particle = element( particleBufferNode, instanceIndex );
 					const velocity = element( velocityBufferNode, instanceIndex );
@@ -89,18 +89,17 @@
 					const pointer = uniform( pointerVector );
 					const limit = uniform( scaleVector );
 
-					const position = temp( add( particle, velocity ), 'tempPos' ); // @TODO: this should work without 'tempPos' property name
-					position.build( builder );
+					const position = add( particle, velocity );
 
-					assign( velocity.x, cond( greaterThanEqual( abs( position.x ), limit.x ), negate( velocity.x ), velocity.x ) ).build( builder );
-					assign( velocity.y, cond( greaterThanEqual( abs( position.y ), limit.y ), negate( velocity.y ), velocity.y ) ).build( builder );
+					stack.assign( velocity.x, cond( greaterThanEqual( abs( position.x ), limit.x ), negate( velocity.x ), velocity.x ) );
+					stack.assign( velocity.y, cond( greaterThanEqual( abs( position.y ), limit.y ), negate( velocity.y ), velocity.y ) );
 
-					assign( position, max( negate( limit ), min( limit, position ) ) ).build( builder );
+					stack.assign( position, max( negate( limit ), min( limit, position ) ) );
 
 					const pointerSize = 0.1;
 					const distanceFromPointer = length( sub( pointer, position ) );
 
-					assign( particle, cond( lessThanEqual( distanceFromPointer, pointerSize ), vec3(), position ) ).build( builder );
+					stack.assign( particle, cond( lessThanEqual( distanceFromPointer, pointerSize ), vec3(), position ) );
 
 				} );
 
@@ -109,7 +108,7 @@
 				computeNode = compute( computeShaderNode, particleNum );
 				computeNode.onInit = ( { renderer } ) => {
 
-					const precomputeShaderNode = new ShaderNode( ( inputs, builder ) => {
+					const precomputeShaderNode = new ShaderNode( ( inputs, stack ) => {
 
 						const particleIndex = float( instanceIndex );
 
@@ -121,7 +120,7 @@
 
 						const velocity = element( velocityBufferNode, instanceIndex );
 
-						assign( velocity.xy, vec2( velX, velY ) ).build( builder );
+						stack.assign( velocity.xy, vec2( velX, velY ) );
 
 					} );
 

+ 2 - 2
examples/webgpu_cubemap_adjustments.html

@@ -126,7 +126,7 @@
 				scene.environmentNode = getEnvironmentNode( reflectVector );
 
 				scene.backgroundNode = context( getEnvironmentNode( transformDirection( positionWorld, modelWorldMatrix ) ), {
-					levelNode: blurNode // @TODO: currently it uses mipmaps value, I think it should be replaced for [0,1]
+					getSamplerLevelNode: () => blurNode
 				} );
 
 				// scene objects
@@ -169,7 +169,7 @@
 
 				const gui = new GUI();
 
-				gui.add( { blurBackground: blurNode.value }, 'blurBackground', 0, 10, 0.01 ).onChange( value => {
+				gui.add( { blurBackground: blurNode.value }, 'blurBackground', 0, 1, 0.01 ).onChange( value => {
 
 					blurNode.value = value;
 

+ 1 - 1
examples/webgpu_cubemap_mix.html

@@ -85,7 +85,7 @@
 				scene.environmentNode = mix( cubeTexture( cube2Texture ), cubeTexture( cube1Texture ), oscSine( timerLocal( .1 ) ) );
 
 				scene.backgroundNode = context( scene.environmentNode, {
-					levelNode: float( 9 ) // @TODO: currently it uses mipmaps value, I think it should be replaced for [0,1]
+					getSamplerLevelNode: () => float( 1 )
 				} );
 
 				const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );