Bladeren bron

WebGLRenderer: NodeMaterial (#21117)

* draft of WebGLNodeBuilder

* Revert "draft of WebGLNodeBuilder"

This reverts commit d37f10fe861823d76d4f2b0b6887579b12d29815.

* add UVNode.isUVNode

* WebGL: NodeMaterial

* new example (colorNode)

* add Material.onNodeBuild()

* .onNodeBuild for NodeMaterial

* cleanup

* add example in files.json

* add screenshot

* rename Material.onNodeBuild() -> Material.onBuild()

* fix tabs
sunag 4 jaren geleden
bovenliggende
commit
f2e4e0fe94

+ 1 - 0
examples/files.json

@@ -232,6 +232,7 @@
 		"webgl_materials_envmaps_hdr_nodes",
 		"webgl_materials_envmaps_hdr_nodes",
 		"webgl_materials_envmaps_pmrem_nodes",
 		"webgl_materials_envmaps_pmrem_nodes",
 		"webgl_materials_nodes",
 		"webgl_materials_nodes",
+		"webgl_materials_standard_nodes",
 		"webgl_mirror_nodes",
 		"webgl_mirror_nodes",
 		"webgl_performance_nodes",
 		"webgl_performance_nodes",
 		"webgl_postprocessing_nodes",
 		"webgl_postprocessing_nodes",

+ 2 - 0
examples/jsm/renderers/nodes/accessors/UVNode.js

@@ -8,6 +8,8 @@ class UVNode extends AttributeNode {
 
 
 		this.index = index;
 		this.index = index;
 
 
+		Object.defineProperty( this, 'isUVNode', { value: true } );
+
 	}
 	}
 
 
 	getAttributeName( /*builder*/ ) {
 	getAttributeName( /*builder*/ ) {

+ 115 - 0
examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js

@@ -0,0 +1,115 @@
+import NodeBuilder from '../../nodes/core/NodeBuilder.js';
+import NodeSlot from '../../nodes/core/NodeSlot.js';
+
+class WebGLNodeBuilder extends NodeBuilder {
+
+	constructor( material, renderer, properties ) {
+
+		super( material, renderer );
+
+		this.properties = properties;
+
+		this._parseMaterial();
+
+	}
+
+	_parseMaterial() {
+
+		const material = this.material;
+
+		// parse inputs
+
+		if ( material.colorNode !== undefined ) {
+
+			this.addSlot( 'fragment', new NodeSlot( material.colorNode, 'COLOR', 'vec4' ) );
+
+		}
+
+	}
+
+	getVaryFromNode( node, type ) {
+
+		const vary = super.getVaryFromNode( node, type );
+
+		if ( node.isUVNode ) {
+
+			vary.name = 'vUv';
+
+		}
+
+		return vary;
+
+	}
+
+	getTexture( textureProperty, uvSnippet ) {
+
+		return `sRGBToLinear( texture2D( ${textureProperty}, ${uvSnippet} ) )`;
+
+	}
+
+	getUniformsHeaderSnippet( shaderStage ) {
+
+		const uniforms = this.uniforms[ shaderStage ];
+
+		let snippet = '';
+
+		for ( let uniform of uniforms ) {
+
+			if ( uniform.type === 'texture' ) {
+
+				snippet += `uniform sampler2D ${uniform.name};`;
+
+			} else {
+
+				let vectorType = this.getVectorType( uniform.type );
+
+				snippet += `uniform ${vectorType} ${uniform.name};`;
+
+			}
+
+		}
+
+		return snippet;
+
+	}
+
+	getAttributesHeaderSnippet( /*shaderStage*/ ) {
+
+	}
+
+	getVarysHeaderSnippet( /*shaderStage*/ ) {
+
+	}
+
+	getVarysBodySnippet( /*shaderStage*/ ) {
+
+	}
+
+	composeUniforms() {
+
+		const uniforms = this.uniforms[ 'fragment' ];
+
+		for ( let uniform of uniforms ) {
+
+			this.properties.uniforms[ uniform.name ] = uniform;
+
+		}
+
+	}
+
+	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();
+
+		return this;
+
+	}
+
+}
+
+export { WebGLNodeBuilder };

+ 44 - 0
examples/jsm/renderers/webgl/nodes/WebGLNodes.js

@@ -0,0 +1,44 @@
+import { WebGLNodeBuilder } from './WebGLNodeBuilder.js';
+
+import { Material } from '../../../../../build/three.module.js';
+
+function addCodeAfterSnippet( source, snippet, code ) {
+
+	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 ) {
+
+	const nodeBuilder = new WebGLNodeBuilder( this, renderer, parameters ).build();
+
+	let fragmentShader = parameters.fragmentShader;
+
+	fragmentShader = addCodeAfterSnippet( fragmentShader, '#include <color_pars_fragment>',
+		`#ifdef NODE_HEADER_UNIFORMS
+
+			NODE_HEADER_UNIFORMS
+
+		#endif` );
+
+	fragmentShader = addCodeAfterSnippet( fragmentShader, '#include <color_fragment>',
+		`#ifdef NODE_COLOR
+
+			diffuseColor *= NODE_COLOR;
+
+		#endif` );
+
+	parameters.fragmentShader = fragmentShader;
+
+};

BIN
examples/screenshots/webgl_materials_standard_nodes.jpg


+ 206 - 0
examples/webgl_materials_standard_nodes.html

@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials - standard (nodes)</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> - webgl physically based material<br/>
+			<a href="http://www.polycount.com/forum/showthread.php?t=130641" target="_blank" rel="noopener">Cerberus(FFVII Gun) model</a> by Andrew Maximov.
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+
+			import { GUI } from './jsm/libs/dat.gui.module.js';
+			import { TrackballControls } from './jsm/controls/TrackballControls.js';
+			import { OBJLoader } from './jsm/loaders/OBJLoader.js';
+			import { RGBELoader } from './jsm/loaders/RGBELoader.js';
+
+			import './jsm/renderers/webgl/nodes/WebGLNodes.js';
+
+			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';
+
+			const statsEnabled = true;
+
+			let container, stats;
+
+			let camera, scene, renderer, controls;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				renderer.outputEncoding = THREE.sRGBEncoding;
+				renderer.toneMapping = THREE.ReinhardToneMapping;
+				renderer.toneMappingExposure = 3;
+
+				//
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 1000 );
+				camera.position.z = 2;
+
+				controls = new TrackballControls( camera, renderer.domElement );
+
+				//
+
+				scene.add( new THREE.HemisphereLight( 0x443333, 0x222233, 4 ) );
+
+				//
+
+				const material = new THREE.MeshStandardMaterial();
+
+				new OBJLoader()
+					.setPath( 'models/obj/cerberus/' )
+					.load( 'Cerberus.obj', function ( group ) {
+
+						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;
+						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.roughnessMap.wrapS = THREE.RepeatWrapping;
+						material.metalnessMap.wrapS = THREE.RepeatWrapping;
+						material.normalMap.wrapS = THREE.RepeatWrapping;
+
+						group.traverse( function ( child ) {
+
+							if ( child.isMesh ) {
+
+								child.material = material;
+
+							}
+
+						} );
+
+						group.position.x = - 0.45;
+						group.rotation.y = - Math.PI / 2;
+						scene.add( group );
+
+					} );
+
+				const environments = {
+
+					'Venice Sunset': { filename: 'venice_sunset_1k.hdr' },
+					'Overpass': { filename: 'pedestrian_overpass_1k.hdr' }
+
+				};
+
+				function loadEnvironment(name) {
+
+					if ( environments[ name ].texture !== undefined ) {
+
+						scene.background = environments[ name ].texture;
+						scene.environment = environments[ name ].texture;
+						return;
+
+					}
+
+					const filename = environments[ name ].filename;
+					new RGBELoader()
+					.setDataType( THREE.UnsignedByteType )
+					.setPath( 'textures/equirectangular/' )
+					.load( filename, function ( hdrEquirect ) {
+
+						const hdrCubeRenderTarget = pmremGenerator.fromEquirectangular( hdrEquirect );
+						hdrEquirect.dispose();
+
+						scene.background = hdrCubeRenderTarget.texture;
+						scene.environment = hdrCubeRenderTarget.texture;
+						environments[ name ].texture = hdrCubeRenderTarget.texture;
+
+					} );
+
+				}
+
+				const params = {
+
+					environment: Object.keys( environments )[ 0 ]
+
+				};
+				loadEnvironment( params.environment );
+
+				const gui = new GUI();
+				gui.add( params, 'environment', Object.keys( environments ) ).onChange( function( value ) {
+
+					loadEnvironment(value);
+
+				} );
+				gui.open();
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				pmremGenerator.compileEquirectangularShader();
+
+				//
+
+				if ( statsEnabled ) {
+
+					stats = new Stats();
+					container.appendChild( stats.dom );
+
+				}
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			//
+
+			function onWindowResize() {
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				controls.update();
+				renderer.render( scene, camera );
+
+				if ( statsEnabled ) stats.update();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 2 - 0
src/materials/Material.js

@@ -78,6 +78,8 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
 
 	isMaterial: true,
 	isMaterial: true,
 
 
+	onBuild: function ( /* shaderobject, renderer */ ) {},
+
 	onBeforeCompile: function ( /* shaderobject, renderer */ ) {},
 	onBeforeCompile: function ( /* shaderobject, renderer */ ) {},
 
 
 	customProgramCacheKey: function () {
 	customProgramCacheKey: function () {

+ 2 - 0
src/renderers/WebGLRenderer.js

@@ -1383,6 +1383,8 @@ function WebGLRenderer( parameters ) {
 
 
 			parameters.uniforms = programCache.getUniforms( material );
 			parameters.uniforms = programCache.getUniforms( material );
 
 
+			material.onBuild( parameters, _this );
+
 			material.onBeforeCompile( parameters, _this );
 			material.onBeforeCompile( parameters, _this );
 
 
 			program = programCache.acquireProgram( parameters, programCacheKey );
 			program = programCache.acquireProgram( parameters, programCacheKey );