Prechádzať zdrojové kódy

WebGLNodeBuilder: New features (#22474)

* move to webgl/nodes

* WebGLNodes: normal, opacity, emissive, roughness, metalness, clearcoat, clearcoatRoughness

* ignore normalNode for now
sunag 3 rokov pred
rodič
commit
f17e42db7c

+ 292 - 27
examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js

@@ -1,13 +1,30 @@
 import NodeBuilder from '../../nodes/core/NodeBuilder.js';
 import NodeSlot from '../../nodes/core/NodeSlot.js';
+import WebGLPhysicalContextNode from './WebGLPhysicalContextNode.js';
+
+import { ShaderChunk } from 'three';
+
+const shaderStages = [ 'vertex', 'fragment' ];
+
+function getIncludeSnippet( name ) {
+
+	return `#include <${name}>`;
+
+}
+
+function getShaderStageProperty( shaderStage ) {
+
+	return `${shaderStage}Shader`;
+
+}
 
 class WebGLNodeBuilder extends NodeBuilder {
 
-	constructor( material, renderer, properties ) {
+	constructor( material, renderer, shader ) {
 
 		super( material, renderer );
 
-		this.properties = properties;
+		this.shader = shader;
 
 		this._parseMaterial();
 
@@ -19,35 +36,97 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 		// parse inputs
 
-		if ( material.colorNode !== undefined ) {
+		if ( material.colorNode && material.colorNode.isNode ) {
 
 			this.addSlot( 'fragment', new NodeSlot( material.colorNode, 'COLOR', 'vec4' ) );
 
 		}
 
-	}
+		if ( material.opacityNode && material.opacityNode.isNode ) {
+
+			this.addSlot( 'fragment', new NodeSlot( material.opacityNode, 'OPACITY', 'float' ) );
+
+		}
+
+		if ( material.normalNode && material.normalNode.isNode ) {
+
+			this.addSlot( 'fragment', new NodeSlot( material.normalNode, 'NORMAL', 'vec3' ) );
+
+		}
+
+		if ( material.emissiveNode && material.emissiveNode.isNode ) {
 
-	getVaryFromNode( node, type ) {
+			this.addSlot( 'fragment', new NodeSlot( material.emissiveNode, 'EMISSIVE', 'vec3' ) );
+
+		}
+
+		if ( material.metalnessNode && material.metalnessNode.isNode ) {
+
+			this.addSlot( 'fragment', new NodeSlot( material.metalnessNode, 'METALNESS', 'float' ) );
+
+		}
+
+		if ( material.roughnessNode && material.roughnessNode.isNode ) {
+
+			this.addSlot( 'fragment', new NodeSlot( material.roughnessNode, 'ROUGHNESS', 'float' ) );
+
+		}
+
+		if ( material.clearcoatNode && material.clearcoatNode.isNode ) {
+
+			this.addSlot( 'fragment', new NodeSlot( material.clearcoatNode, 'CLEARCOAT', 'float' ) );
+
+		}
+
+		if ( material.clearcoatRoughnessNode && material.clearcoatRoughnessNode.isNode ) {
+
+			this.addSlot( 'fragment', new NodeSlot( material.clearcoatRoughnessNode, 'CLEARCOAT_ROUGHNESS', 'float' ) );
+
+		}
 
-		const vary = super.getVaryFromNode( node, type );
+		if ( material.envNode && material.envNode.isNode ) {
 
-		if ( node.isUVNode ) {
+			const envRadianceNode = new WebGLPhysicalContextNode( WebGLPhysicalContextNode.RADIANCE, material.envNode );
+			const envIrradianceNode = new WebGLPhysicalContextNode( WebGLPhysicalContextNode.IRRADIANCE, material.envNode );
 
-			vary.name = 'vUv';
+			this.addSlot( 'fragment', new NodeSlot( envRadianceNode, 'RADIANCE', 'vec3' ) );
+			this.addSlot( 'fragment', new NodeSlot( envIrradianceNode, 'IRRADIANCE', 'vec3' ) );
 
 		}
 
-		return vary;
+	}
+
+	getTexture( textureProperty, uvSnippet, biasSnippet = null ) {
+
+		if ( biasSnippet !== null ) {
+
+			return `texture2D( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
+
+		} else {
+
+			return `texture2D( ${textureProperty}, ${uvSnippet} )`;
+
+		}
 
 	}
 
-	getTexture( textureProperty, uvSnippet ) {
+	getCubeTexture( textureProperty, uvSnippet, biasSnippet = null ) {
+
+		const textureCube = 'textureCubeLodEXT'; // textureCubeLodEXT textureLod
+
+		if ( biasSnippet !== null ) {
 
-		return `sRGBToLinear( texture2D( ${textureProperty}, ${uvSnippet} ) )`;
+			return `${textureCube}( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
+
+		} else {
+
+			return `${textureCube}( ${textureProperty}, ${uvSnippet} )`;
+
+		}
 
 	}
 
-	getUniformsHeaderSnippet( shaderStage ) {
+	getUniforms( shaderStage ) {
 
 		const uniforms = this.uniforms[ shaderStage ];
 
@@ -57,13 +136,17 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 			if ( uniform.type === 'texture' ) {
 
-				snippet += `uniform sampler2D ${uniform.name};`;
+				snippet += `uniform sampler2D ${uniform.name}; `;
+
+			} else if ( uniform.type === 'cubeTexture' ) {
+
+				snippet += `uniform samplerCube ${uniform.name}; `;
 
 			} else {
 
 				const vectorType = this.getVectorType( uniform.type );
 
-				snippet += `uniform ${vectorType} ${uniform.name};`;
+				snippet += `uniform ${vectorType} ${uniform.name}; `;
 
 			}
 
@@ -73,51 +156,233 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 	}
 
-	getAttributesHeaderSnippet( /*shaderStage*/ ) {
+	getAttributes( shaderStage ) {
+
+		let snippet = '';
+
+		if ( shaderStage === 'vertex' ) {
+
+			const attributes = this.attributes;
+
+			for ( let index = 0; index < attributes.length; index ++ ) {
+
+				const attribute = attributes[ index ];
+
+				// ignore common attributes to prevent redefinitions
+				if ( attribute.name === 'uv' || attribute.name === 'position' || attribute.name === 'normal' )
+					continue;
+
+				snippet += `attribute ${attribute.type} ${attribute.name}; `;
+
+			}
+
+		}
+
+		return snippet;
 
 	}
 
-	getVarsHeaderSnippet( /*shaderStage*/ ) {
+	getVarys( shaderStage ) {
+
+		let snippet = '';
+
+		const varys = this.varys;
+
+		for ( let index = 0; index < varys.length; index ++ ) {
+
+			const vary = varys[ index ];
+
+			snippet += `varying ${vary.type} ${vary.name}; `;
+
+		}
+
+		return snippet;
 
 	}
 
-	getVarsBodySnippet( /*shaderStage*/ ) {
+	addCodeAfterSnippet( shaderStage, snippet, code ) {
+
+		const shaderProperty = getShaderStageProperty( shaderStage );
+
+		let source = this.shader[ shaderProperty ];
+
+		const index = source.indexOf( snippet );
+
+		if ( index !== - 1 ) {
+
+			const start = source.substring( 0, index + snippet.length );
+			const end = source.substring( index + snippet.length );
+
+			source = `${start}\n${code}\n${end}`;
+
+		}
+
+		this.shader[ shaderProperty ] = source;
 
 	}
 
-	getVarysHeaderSnippet( /*shaderStage*/ ) {
+	addCodeAfterInclude( shaderStage, includeName, code ) {
+
+		const includeSnippet = getIncludeSnippet( includeName );
+
+		this.addCodeAfterSnippet( shaderStage, includeSnippet, code );
 
 	}
 
-	getVarysBodySnippet( /*shaderStage*/ ) {
+	replaceCode( shaderStage, source, target ) {
+
+		const shaderProperty = getShaderStageProperty( shaderStage );
+
+		this.shader[ shaderProperty ] = this.shader[ shaderProperty ].replaceAll( source, target );
 
 	}
 
-	composeUniforms() {
+	parseInclude( shaderStage, ...includes ) {
 
-		const uniforms = this.uniforms[ 'fragment' ];
+		for ( const name of includes ) {
 
-		for ( const uniform of uniforms ) {
+			const includeSnippet = getIncludeSnippet( name );
+			const code = ShaderChunk[ name ];
 
-			this.properties.uniforms[ uniform.name ] = uniform;
+			this.replaceCode( shaderStage, includeSnippet, code );
 
 		}
 
 	}
 
+	/*prependCode( code ) {
+
+		this.shader.vertexShader = code + this.shader.vertexShader;
+		this.shader.fragmentShader = code + this.shader.fragmentShader;
+
+	}*/
+
 	build() {
 
 		super.build();
 
-		this.properties.defines[ 'NODE_HEADER_UNIFORMS' ] = this.defines[ 'fragment' ][ 'NODE_HEADER_UNIFORMS' ];
-		this.properties.defines[ 'NODE_COLOR' ] = this.defines[ 'fragment' ][ 'NODE_COLOR' ];
-
-		this.composeUniforms();
+		this._addSnippets();
+		this._buildShader();
 
 		return this;
 
 	}
 
+	_addSnippets() {
+
+		this.parseInclude( 'fragment', 'lights_physical_fragment' );
+
+		this.addCodeAfterInclude( 'fragment', 'normal_fragment_begin',
+			`#ifdef NODE_NORMAL
+
+				NODE_CODE_NORMAL
+				normal = NODE_NORMAL;
+
+			#endif` );
+
+		this.addCodeAfterInclude( 'fragment', 'color_fragment',
+			`#ifdef NODE_COLOR
+
+				NODE_CODE_COLOR
+				diffuseColor = NODE_COLOR;
+
+			#endif` );
+
+		this.addCodeAfterInclude( 'fragment', 'alphamap_fragment',
+			`#ifdef NODE_OPACITY
+
+				NODE_CODE_OPACITY
+				diffuseColor.a *= NODE_OPACITY;
+
+			#endif` );
+
+		this.addCodeAfterInclude( 'fragment', 'emissivemap_fragment',
+			`#ifdef NODE_EMISSIVE
+
+				NODE_CODE_EMISSIVE
+				totalEmissiveRadiance = NODE_EMISSIVE;
+
+			#endif` );
+
+		this.addCodeAfterInclude( 'fragment', 'roughnessmap_fragment',
+			`#ifdef NODE_ROUGHNESS
+
+				NODE_CODE_ROUGHNESS
+				roughnessFactor = NODE_ROUGHNESS;
+
+			#endif` );
+
+		this.addCodeAfterInclude( 'fragment', 'metalnessmap_fragment',
+			`#ifdef NODE_METALNESS
+
+				NODE_CODE_METALNESS
+				metalnessFactor = NODE_METALNESS;
+
+			#endif` );
+
+		this.addCodeAfterSnippet( 'fragment', 'material.clearcoatRoughness = clearcoatRoughness;',
+			`#ifdef NODE_CLEARCOAT
+
+				NODE_CODE_CLEARCOAT
+				material.clearcoat = NODE_CLEARCOAT;
+
+			#endif
+
+			#ifdef NODE_CLEARCOAT_ROUGHNESS
+
+				NODE_CODE_CLEARCOAT_ROUGHNESS
+				material.clearcoatRoughness = NODE_CLEARCOAT_ROUGHNESS;
+
+			#endif` );
+
+		this.addCodeAfterInclude( 'fragment', 'lights_fragment_begin',
+			`#ifdef NODE_RADIANCE
+
+				NODE_CODE_RADIANCE
+				radiance += NODE_RADIANCE;
+
+				NODE_CODE_IRRADIANCE
+				iblIrradiance += PI * NODE_IRRADIANCE;
+
+			#endif` );
+
+		for ( const shaderStage of shaderStages ) {
+
+			this.addCodeAfterSnippet( shaderStage, 'main() {',
+				`#ifdef NODE_CODE
+
+					NODE_CODE
+
+				#endif` );
+
+		}
+
+	}
+
+	_buildShader() {
+
+		for ( const shaderStage of shaderStages ) {
+
+			// uniforms
+
+			for ( const uniform of this.uniforms[ shaderStage ] ) {
+
+				this.shader.uniforms[ uniform.name ] = uniform;
+
+			}
+
+			// code
+
+			const shaderProperty = getShaderStageProperty( shaderStage );
+
+			const nodeCode = this[ shaderProperty ];
+
+			this.shader[ shaderProperty ] = nodeCode + this.shader[ shaderProperty ];
+
+		}
+
+	}
+
 }
 
 export { WebGLNodeBuilder };

+ 18 - 27
examples/jsm/renderers/webgl/nodes/WebGLNodes.js

@@ -1,44 +1,35 @@
 import { WebGLNodeBuilder } from './WebGLNodeBuilder.js';
+import NodeFrame from '../../nodes/core/NodeFrame.js';
 
-import { Material } from '../../../../../build/three.module.js';
+import { Material } from 'three';
 
-function addCodeAfterSnippet( source, snippet, code ) {
+const builders = new WeakMap();
+export const nodeFrame = new NodeFrame();
 
-	const index = source.indexOf( snippet );
-
-	if ( index !== - 1 ) {
-
-		const start = source.substring( 0, index + snippet.length );
-		const end = source.substring( index + snippet.length );
-
-		return `${start}\n${code}\n${end}`;
-
-	}
-
-	return source;
+Material.prototype.onBuild = function ( parameters, renderer ) {
 
-}
+	builders.set( this, new WebGLNodeBuilder( this, renderer, parameters ).build() );
 
-Material.prototype.onBuild = function ( parameters, renderer ) {
+};
 
-	new WebGLNodeBuilder( this, renderer, parameters ).build();
+Material.prototype.onUpdate = function ( renderer, scene, camera, geometry, object ) {
 
-	let fragmentShader = parameters.fragmentShader;
+	const nodeBuilder = builders.get( this );
 
-	fragmentShader = addCodeAfterSnippet( fragmentShader, '#include <color_pars_fragment>',
-		`#ifdef NODE_HEADER_UNIFORMS
+	if ( nodeBuilder !== undefined ) {
 
-			NODE_HEADER_UNIFORMS
+		nodeFrame.update();
 
-		#endif` );
+		nodeFrame.material = this;
+		nodeFrame.camera = camera;
+		nodeFrame.object = object;
 
-	fragmentShader = addCodeAfterSnippet( fragmentShader, '#include <color_fragment>',
-		`#ifdef NODE_COLOR
+		for ( const node of nodeBuilder.updateNodes ) {
 
-			diffuseColor = NODE_COLOR;
+			nodeFrame.updateNode( node );
 
-		#endif` );
+		}
 
-	parameters.fragmentShader = fragmentShader;
+	}
 
 };

+ 8 - 8
examples/jsm/renderers/nodes/lights/PhysicalMaterialContextNode.js → examples/jsm/renderers/webgl/nodes/WebGLPhysicalContextNode.js

@@ -1,9 +1,9 @@
-import ContextNode from '../core/ContextNode.js';
-import NormalNode from '../accessors/NormalNode.js';
-import ExpressionNode from '../core/ExpressionNode.js';
-import FloatNode from '../inputs/FloatNode.js';
+import ContextNode from '../../nodes/core/ContextNode.js';
+import NormalNode from '../../nodes/accessors/NormalNode.js';
+import ExpressionNode from '../../nodes/core/ExpressionNode.js';
+import FloatNode from '../../nodes/inputs/FloatNode.js';
 
-class PhysicalMaterialContextNode extends ContextNode {
+class WebGLPhysicalContextNode extends ContextNode {
 
 	static RADIANCE = 'radiance';
 	static IRRADIANCE = 'irradiance';
@@ -22,11 +22,11 @@ class PhysicalMaterialContextNode extends ContextNode {
 
 		let roughness = null;
 
-		if ( scope === PhysicalMaterialContextNode.RADIANCE ) {
+		if ( scope === WebGLPhysicalContextNode.RADIANCE ) {
 
 			roughness = new ExpressionNode( 'roughnessFactor', 'float' );
 
-		} else if ( scope === PhysicalMaterialContextNode.IRRADIANCE ) {
+		} else if ( scope === WebGLPhysicalContextNode.IRRADIANCE ) {
 
 			roughness = new FloatNode( 1.0 ).setConst( true );
 
@@ -42,4 +42,4 @@ class PhysicalMaterialContextNode extends ContextNode {
 
 }
 
-export default PhysicalMaterialContextNode;
+export default WebGLPhysicalContextNode;

+ 12 - 9
examples/webgl_materials_standard_nodes.html

@@ -36,6 +36,7 @@
 			import TextureNode from './jsm/renderers/nodes/inputs/TextureNode.js';
 			import Vector3Node from './jsm/renderers/nodes/inputs/Vector3Node.js';
 			import OperatorNode from './jsm/renderers/nodes/math/OperatorNode.js';
+			import SwitchNode from './jsm/renderers/nodes/utils/SwitchNode.js';
 
 			let container, stats;
 
@@ -82,23 +83,25 @@
 						const loader = new THREE.TextureLoader()
 							.setPath( 'models/obj/cerberus/' );
 
-						material.roughness = 1; // attenuates roughnessMap
-						material.metalness = 1; // attenuates metalnessMap
-
 						const diffuseMap = loader.load( 'Cerberus_A.jpg' );
 						diffuseMap.wrapS = THREE.RepeatWrapping;
 						diffuseMap.encoding = THREE.sRGBEncoding;
 
-						//material.map = diffuseMap;
+						const rmMap = loader.load( 'Cerberus_RM.jpg' );
+						rmMap.wrapS = THREE.RepeatWrapping;
+
+						const normalMap = loader.load( 'Cerberus_N.jpg' );
+						normalMap.wrapS = THREE.RepeatWrapping;
+
+						const mpMapNode = new TextureNode( rmMap );
+
 						material.colorNode = new OperatorNode( '*', new TextureNode( diffuseMap ), new Vector3Node( material.color ) );
 
 						// roughness is in G channel, metalness is in B channel
-						material.metalnessMap = material.roughnessMap = loader.load( 'Cerberus_RM.jpg' );
-						material.normalMap = loader.load( 'Cerberus_N.jpg' );
+						material.roughnessNode = new SwitchNode( mpMapNode, 'g' );
+						material.metalnessNode = new SwitchNode( mpMapNode, 'b' );
 
-						material.roughnessMap.wrapS = THREE.RepeatWrapping;
-						material.metalnessMap.wrapS = THREE.RepeatWrapping;
-						material.normalMap.wrapS = THREE.RepeatWrapping;
+						material.normalMap = normalMap;
 
 						group.traverse( function ( child ) {