You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							270 lines
						
					
					
						
							6.6 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							270 lines
						
					
					
						
							6.6 KiB
						
					
					
				| <!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> | |
| 
 | |
| 		<!-- Import maps polyfill --> | |
| 		<!-- Remove this when import maps will be widely supported --> | |
| 		<script async src="https://unpkg.com/es-module-shims@1.3.6/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 { GUI } from 'three/addons/libs/lil-gui.module.min.js'; | |
| 			import { TrackballControls } from 'three/addons/controls/TrackballControls.js'; | |
| 			import { OBJLoader } from 'three/addons/loaders/OBJLoader.js'; | |
| 			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; | |
| 
 | |
| 			import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js'; | |
| 
 | |
| 			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 ) ); | |
| 
 | |
| 				// Test Extended Material | |
|  | |
| 				class MeshCustomNodeMaterial extends Nodes.MeshStandardNodeMaterial { | |
| 
 | |
| 					constructor() { | |
| 
 | |
| 						super(); | |
| 
 | |
| 					} | |
| 
 | |
| 				} | |
| 
 | |
| 				// Extends Serialization Material | |
|  | |
| 				const superCreateMaterialFromType = THREE.MaterialLoader.createMaterialFromType; | |
| 
 | |
| 				THREE.MaterialLoader.createMaterialFromType = function ( type ) { | |
| 
 | |
| 					const materialLib = { | |
| 						MeshCustomNodeMaterial | |
| 					}; | |
| 
 | |
| 					if ( materialLib[ type ] !== undefined ) { | |
| 
 | |
| 						return new materialLib[ type ](); | |
| 
 | |
| 					} | |
| 
 | |
| 					return superCreateMaterialFromType.call( this, type ); | |
| 
 | |
| 				}; | |
| 
 | |
| 				// | |
|  | |
| 				const material = new MeshCustomNodeMaterial(); | |
| 
 | |
| 				new OBJLoader() | |
| 					.setPath( 'models/obj/cerberus/' ) | |
| 					.load( 'Cerberus.obj', function ( group ) { | |
| 
 | |
| 						const loaderManager = new THREE.LoadingManager(); | |
| 
 | |
| 						const loader = new THREE.TextureLoader( loaderManager ) | |
| 							.setPath( 'models/obj/cerberus/' ); | |
| 
 | |
| 						const diffuseMap = loader.load( 'Cerberus_A.jpg' ); | |
| 						diffuseMap.wrapS = THREE.RepeatWrapping; | |
| 						diffuseMap.encoding = THREE.sRGBEncoding; | |
| 
 | |
| 						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 Nodes.TextureNode( rmMap ); | |
| 
 | |
| 						material.colorNode = new Nodes.OperatorNode( '*', new Nodes.TextureNode( diffuseMap ), new Nodes.UniformNode( material.color ) ); | |
| 
 | |
| 						// roughness is in G channel, metalness is in B channel | |
| 						material.roughnessNode = new Nodes.SplitNode( mpMapNode, 'g' ); | |
| 						material.metalnessNode = new Nodes.SplitNode( mpMapNode, 'b' ); | |
| 
 | |
| 						material.normalNode = new Nodes.NormalMapNode( new Nodes.TextureNode( normalMap ) ); | |
| 
 | |
| 						group.traverse( function ( child ) { | |
| 
 | |
| 							if ( child.isMesh ) { | |
| 
 | |
| 								child.material = material; | |
| 
 | |
| 							} | |
| 
 | |
| 						} ); | |
| 
 | |
| 						group.position.x = - 0.45; | |
| 						group.rotation.y = - Math.PI / 2; | |
| 						//scene.add( group ); | |
|  | |
| 						// TODO: Serialization test | |
|  | |
| 						loaderManager.onLoad = () => { | |
| 
 | |
| 							const groupJSON = JSON.stringify( group.toJSON() ); | |
| 
 | |
| 							const objectLoader = new Nodes.NodeObjectLoader(); | |
| 							objectLoader.parse( JSON.parse( groupJSON ), ( newGroup ) => { | |
| 
 | |
| 								//scene.remove( group ); | |
|  | |
| 								newGroup.position.copy( group.position ); | |
| 								newGroup.rotation.copy( group.rotation ); | |
| 
 | |
| 								scene.add( newGroup ); | |
| 
 | |
| 								console.log( 'Serialized!' ); | |
| 
 | |
| 							} ); | |
| 
 | |
| 						}; | |
| 
 | |
| 					} ); | |
| 
 | |
| 				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() | |
| 						.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(); | |
| 
 | |
| 				// | |
|  | |
| 				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 ); | |
| 
 | |
| 				nodeFrame.update(); | |
| 
 | |
| 				controls.update(); | |
| 				renderer.render( scene, camera ); | |
| 
 | |
| 				stats.update(); | |
| 
 | |
| 			} | |
| 
 | |
| 		</script> | |
| 
 | |
| 	</body> | |
| </html>
 | |
| 
 |