فهرست منبع

Nodes: MeshPhysicalNodeMaterial (#24385)

* add MeshPhysicalNodeMaterial

* add example: webgl clearcoat using nodes

* cleanup

* add dafaultValues
sunag 3 سال پیش
والد
کامیت
ddaabf992f

+ 1 - 0
examples/files.json

@@ -229,6 +229,7 @@
 	],
 	"webgl / nodes": [
 		"webgl_materials_instance_uniform_nodes",
+		"webgl_materials_physical_clearcoat_nodes",
 		"webgl_materials_standard_nodes",
 		"webgl_nodes_playground",
 		"webgl_points_nodes"

+ 4 - 1
examples/jsm/nodes/materials/Materials.js

@@ -2,6 +2,7 @@ import NodeMaterial from './NodeMaterial.js';
 import LineBasicNodeMaterial from './LineBasicNodeMaterial.js';
 import MeshBasicNodeMaterial from './MeshBasicNodeMaterial.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
+import MeshPhysicalNodeMaterial from './MeshPhysicalNodeMaterial.js';
 import PointsNodeMaterial from './PointsNodeMaterial.js';
 import SpriteNodeMaterial from './SpriteNodeMaterial.js';
 
@@ -10,6 +11,7 @@ export {
 	LineBasicNodeMaterial,
 	MeshBasicNodeMaterial,
 	MeshStandardNodeMaterial,
+	MeshPhysicalNodeMaterial,
 	PointsNodeMaterial,
 	SpriteNodeMaterial
 };
@@ -21,8 +23,9 @@ NodeMaterial.fromMaterial = function ( material ) {
 		LineBasicNodeMaterial,
 		MeshBasicNodeMaterial,
 		MeshStandardNodeMaterial,
+		MeshPhysicalNodeMaterial,
 		PointsNodeMaterial,
-		SpriteNodeMaterial,
+		SpriteNodeMaterial
 	};
 
 	const type = material.type.replace( 'Material', 'NodeMaterial' );

+ 40 - 0
examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js

@@ -0,0 +1,40 @@
+import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
+
+import { MeshPhysicalMaterial } from 'three';
+
+const defaultValues = new MeshPhysicalMaterial();
+
+export default class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
+
+	constructor( parameters ) {
+
+		super();
+
+		this.isMeshPhysicalNodeMaterial = true;
+
+		this.clearcoatNode = null;
+		this.clearcoatRoughnessNode = null;
+		this.clearcoatNormalNode = null;
+
+		this.sheen = 0;
+		this.clearcoat = 0;
+		this.iridescence = 0;
+		this.transmission = 0;
+
+		this.setDefaultValues( defaultValues );
+
+		this.setValues( parameters );
+
+	}
+
+	copy( source ) {
+
+		this.clearcoatNode = source.clearcoatNode;
+		this.clearcoatRoughnessNode = source.clearcoatRoughnessNode;
+		this.clearcoatNormalNode = source.clearcoatNormalNode;
+
+		return super.copy( source );
+
+	}
+
+}

+ 0 - 6
examples/jsm/nodes/materials/MeshStandardNodeMaterial.js

@@ -35,9 +35,6 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 		this.metalnessNode = null;
 		this.roughnessNode = null;
 
-		this.clearcoatNode = null;
-		this.clearcoatRoughnessNode = null;
-
 		this.envNode = null;
 
 		this.lightsNode = null;
@@ -158,9 +155,6 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 		this.metalnessNode = source.metalnessNode;
 		this.roughnessNode = source.roughnessNode;
 
-		this.clearcoatNode = source.clearcoatNode;
-		this.clearcoatRoughnessNode = source.clearcoatRoughnessNode;
-
 		this.envNode = source.envNode;
 
 		this.lightsNode = source.lightsNode;

+ 44 - 18
examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js

@@ -14,7 +14,8 @@ const nodeShaderLib = {
 	LineBasicNodeMaterial: ShaderLib.basic,
 	MeshBasicNodeMaterial: ShaderLib.basic,
 	PointsNodeMaterial: ShaderLib.points,
-	MeshStandardNodeMaterial: ShaderLib.standard
+	MeshStandardNodeMaterial: ShaderLib.standard,
+	MeshPhysicalMaterial: ShaderLib.physical
 };
 
 function getIncludeSnippet( name ) {
@@ -70,7 +71,8 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 		// shader lib
 
-		if ( material.isMeshStandardNodeMaterial ) type = 'MeshStandardNodeMaterial';
+		if ( material.isMeshPhysicalNodeMaterial ) type = 'MeshPhysicalMaterial';
+		else if ( material.isMeshStandardNodeMaterial ) type = 'MeshStandardNodeMaterial';
 		else if ( material.isMeshBasicNodeMaterial ) type = 'MeshBasicNodeMaterial';
 		else if ( material.isPointsNodeMaterial ) type = 'PointsNodeMaterial';
 		else if ( material.isLineBasicNodeMaterial ) type = 'LineBasicNodeMaterial';
@@ -130,15 +132,31 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 		}
 
-		if ( material.clearcoatNode && material.clearcoatNode.isNode ) {
+		if ( material.isMeshPhysicalNodeMaterial ) {
 
-			this.addSlot( 'fragment', new SlotNode( material.clearcoatNode, 'CLEARCOAT', 'float' ) );
+			if ( material.clearcoatNode && material.clearcoatNode.isNode ) {
 
-		}
+				this.addSlot( 'fragment', new SlotNode( material.clearcoatNode, 'CLEARCOAT', 'float' ) );
+
+				if ( material.clearcoatRoughnessNode && material.clearcoatRoughnessNode.isNode ) {
+
+					this.addSlot( 'fragment', new SlotNode( material.clearcoatRoughnessNode, 'CLEARCOAT_ROUGHNESS', 'float' ) );
+
+				}
+
+				if ( material.clearcoatNormalNode && material.clearcoatNormalNode.isNode ) {
+
+					this.addSlot( 'fragment', new SlotNode( material.clearcoatNormalNode, 'CLEARCOAT_NORMAL', 'vec3' ) );
+
+				}
 
-		if ( material.clearcoatRoughnessNode && material.clearcoatRoughnessNode.isNode ) {
+				material.defines.USE_CLEARCOAT = '';
 
-			this.addSlot( 'fragment', new SlotNode( material.clearcoatRoughnessNode, 'CLEARCOAT_ROUGHNESS', 'float' ) );
+			} else {
+
+				delete material.defines.USE_CLEARCOAT;
+
+			}
 
 		}
 
@@ -250,9 +268,7 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 			const attributes = this.attributes;
 
-			for ( let index = 0; index < attributes.length; index ++ ) {
-
-				const attribute = attributes[ index ];
+			for ( const attribute of attributes ) {
 
 				// ignore common attributes to prevent redefinitions
 				if ( attribute.name === 'uv' || attribute.name === 'position' || attribute.name === 'normal' )
@@ -274,9 +290,7 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 		const varys = this.varys;
 
-		for ( let index = 0; index < varys.length; index ++ ) {
-
-			const vary = varys[ index ];
+		for ( const vary of varys ) {
 
 			snippet += `varying ${vary.type} ${vary.name}; `;
 
@@ -319,7 +333,7 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 		const shaderProperty = getShaderStageProperty( shaderStage );
 
-		this.shader[ shaderProperty ] = this.shader[ shaderProperty ].replaceAll( source, target );
+		this[ shaderProperty ] = this[ shaderProperty ].replaceAll( source, target );
 
 	}
 
@@ -433,6 +447,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 	_addSnippets() {
 
 		this.parseInclude( 'fragment', 'lights_physical_fragment' );
+		this.parseInclude( 'fragment', 'clearcoat_normal_fragment_begin' );
 
 		const colorSlot = this.getSlot( 'fragment', 'COLOR' );
 		const opacityNode = this.getSlot( 'fragment', 'OPACITY' );
@@ -442,6 +457,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 		const metalnessNode = this.getSlot( 'fragment', 'METALNESS' );
 		const clearcoatNode = this.getSlot( 'fragment', 'CLEARCOAT' );
 		const clearcoatRoughnessNode = this.getSlot( 'fragment', 'CLEARCOAT_ROUGHNESS' );
+		const clearcoatNormalNode = this.getSlot( 'fragment', 'CLEARCOAT_NORMAL' );
 		const iridescenceNode = this.getSlot( 'fragment', 'IRIDESCENCE' );
 		const iridescenceIORNode = this.getSlot( 'fragment', 'IRIDESCENCE_IOR' );
 		const iridescenceThicknessNode = this.getSlot( 'fragment', 'IRIDESCENCE_THICKNESS' );
@@ -513,7 +529,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 
 			this.addCodeAfterSnippet(
 				'fragment',
-				'material.clearcoatRoughness = clearcoatRoughness;',
+				'material.clearcoat = clearcoat;',
 				`${clearcoatNode.code}\n\tmaterial.clearcoat = ${clearcoatNode.result};`
 			);
 
@@ -529,9 +545,19 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 
 		}
 
-		if ( iridescenceNode !== undefined ) {
+		if ( clearcoatNormalNode !== undefined ) {
 
 			this.addCodeAfterSnippet(
+				'fragment',
+				'vec3 clearcoatNormal = geometryNormal;',
+				`${clearcoatNormalNode.code}\n\tclearcoatNormal = ${clearcoatNormalNode.result};`
+			);
+
+		}
+
+		if ( iridescenceNode !== undefined ) {
+
+			this.addCodeAfterInclude(
 				'fragment',
 				'iridescence_fragment',
 				`${iridescenceNode.code}\n\tmaterial.iridescence = ${iridescenceNode.result};`
@@ -541,7 +567,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 
 		if ( iridescenceIORNode !== undefined ) {
 
-			this.addCodeAfterSnippet(
+			this.addCodeAfterInclude(
 				'fragment',
 				'iridescence_fragment',
 				`${iridescenceIORNode.code}\n\tmaterial.iridescenceIOR = ${iridescenceIORNode.result};`
@@ -551,7 +577,7 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]}
 
 		if ( iridescenceThicknessNode !== undefined ) {
 
-			this.addCodeAfterSnippet(
+			this.addCodeAfterInclude(
 				'fragment',
 				'iridescence_fragment',
 				`${iridescenceThicknessNode.code}\n\tmaterial.iridescenceThickness = ${iridescenceThicknessNode.result};`

BIN
examples/screenshots/webgl_materials_physical_clearcoat_nodes.jpg


+ 256 - 0
examples/webgl_materials_physical_clearcoat_nodes.html

@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials - clearcoat 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 - materials - clearcoat nodes
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three-nodes/": "./jsm/nodes/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import * as Nodes from 'three-nodes/Nodes.js';
+
+			import { color, float, vec2, texture, normalMap, uv, mul } from 'three-nodes/Nodes.js';
+
+			import { nodeFrame } from './jsm/renderers/webgl/nodes/WebGLNodes.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { HDRCubeTextureLoader } from './jsm/loaders/HDRCubeTextureLoader.js';
+
+			import { FlakesTexture } from './jsm/textures/FlakesTexture.js';
+
+			let container, stats;
+
+			let camera, scene, renderer;
+
+			let particleLight;
+			let group;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 1, 10000 );
+				camera.position.z = 1000;
+
+				scene = new THREE.Scene();
+
+				group = new THREE.Group();
+				scene.add( group );
+
+				new HDRCubeTextureLoader()
+					.setPath( 'textures/cube/pisaHDR/' )
+					.load( [ 'px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr' ],
+						  function ( hdrTexture ) {
+
+							const geometry = new THREE.SphereGeometry( 80, 64, 32 );
+
+							const textureLoader = new THREE.TextureLoader();
+
+							const diffuse = textureLoader.load( 'textures/carbon/Carbon.png' );
+							diffuse.encoding = THREE.sRGBEncoding;
+							diffuse.wrapS = THREE.RepeatWrapping;
+							diffuse.wrapT = THREE.RepeatWrapping;
+
+							const normalMap1 = textureLoader.load( 'textures/carbon/Carbon_Normal.png' );
+							normalMap1.wrapS = THREE.RepeatWrapping;
+							normalMap1.wrapT = THREE.RepeatWrapping;
+
+							const normalMap2 = textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' );
+
+							const normalMap3 = new THREE.CanvasTexture( new FlakesTexture() );
+							normalMap3.wrapS = THREE.RepeatWrapping;
+							normalMap3.wrapT = THREE.RepeatWrapping;
+							normalMap3.anisotropy = 16;
+
+							const normalMap4 = textureLoader.load( 'textures/golfball.jpg' );
+
+							const clearcoatNormalMap = textureLoader.load( 'textures/pbr/Scratched_gold/Scratched_gold_01_1K_Normal.png' );
+
+							// car paint
+
+							const carPaintUV = mul( uv(), vec2( 10, 6 ) );
+							const carPaintNormalScale = vec2( 0.15 );
+
+							let material = new Nodes.MeshPhysicalNodeMaterial();
+							material.clearcoatNode = float( 1 );
+							material.clearcoatRoughnessNode = float( 0.1 );
+							material.metalnessNode = float( 0.9 );
+							material.roughnessNode = float( 0.5 );
+							material.colorNode = color( 0x0000ff );
+							material.normalNode = normalMap( texture( normalMap3, carPaintUV ), carPaintNormalScale );
+
+							let mesh = new THREE.Mesh( geometry, material );
+							mesh.position.x = - 100;
+							mesh.position.y = 100;
+							group.add( mesh );
+
+							// fibers
+
+							const fibersUV = mul( uv(), 10 );
+
+							material = new Nodes.MeshPhysicalNodeMaterial();
+							material.roughnessNode = float( 0.5 );
+							material.clearcoatNode = float( 1 );
+							material.clearcoatRoughnessNode = float( 0.1 );
+							material.colorNode = texture( diffuse, fibersUV );
+							material.normalNode = normalMap( texture( normalMap1, fibersUV ) );
+
+							mesh = new THREE.Mesh( geometry, material );
+							mesh.position.x = 100;
+							mesh.position.y = 100;
+							group.add( mesh );
+
+							// golf
+
+							material = new Nodes.MeshPhysicalNodeMaterial();
+							material.clearcoatNode = float( 1 );
+							material.roughnessNode = float( 0.1 );
+							material.metalnessNode = float( 0 );
+							material.colorNode = color( 0xffffff );
+							material.normalNode = normalMap( texture( normalMap4 ) );
+							// y scale is negated to compensate for normal map handedness.
+							material.clearcoatNormalNode = normalMap( texture( clearcoatNormalMap ), vec2( 2.0, - 2.0 ) );
+
+							mesh = new THREE.Mesh( geometry, material );
+							mesh.position.x = - 100;
+							mesh.position.y = - 100;
+							group.add( mesh );
+
+							// clearcoat + normalmap
+
+							material = new Nodes.MeshPhysicalNodeMaterial();
+							material.clearcoatNode = float( 1 );
+							material.roughnessNode = float( 1 );
+							material.metalnessNode = float( 1 );
+							material.colorNode = color( 0xff0000 );
+							material.normalNode = normalMap( texture( normalMap2 ), vec2( 0.15, 0.15 ) );
+							// y scale is negated to compensate for normal map handedness.
+							material.clearcoatNormalNode = normalMap( texture( clearcoatNormalMap ), vec2( 2.0, - 2.0 ) );
+
+							mesh = new THREE.Mesh( geometry, material );
+							mesh.position.x = 100;
+							mesh.position.y = - 100;
+							group.add( mesh );
+
+							//
+
+							scene.background = hdrTexture;
+							scene.environment = hdrTexture;
+
+						}
+
+						 );
+
+				// LIGHTS
+
+				particleLight = new THREE.Mesh(
+					new THREE.SphereGeometry( 4, 8, 8 ),
+					new THREE.MeshBasicMaterial( { color: 0xffffff } )
+				);
+				scene.add( particleLight );
+
+				particleLight.add( new THREE.PointLight( 0xffffff, 1 ) );
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				//
+
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 1.25;
+
+				//
+
+				renderer.outputEncoding = THREE.sRGBEncoding;
+
+				//
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				// EVENTS
+
+				new OrbitControls( camera, renderer.domElement );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			//
+
+			function onWindowResize() {
+
+				const width = window.innerWidth;
+				const height = window.innerHeight;
+
+				camera.aspect = width / height;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( width, height );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				nodeFrame.update();
+
+				render();
+
+				stats.update();
+
+			}
+
+			function render() {
+
+				const timer = Date.now() * 0.00025;
+
+				particleLight.position.x = Math.sin( timer * 7 ) * 300;
+				particleLight.position.y = Math.cos( timer * 5 ) * 400;
+				particleLight.position.z = Math.cos( timer * 3 ) * 300;
+
+				for ( let i = 0; i < group.children.length; i ++ ) {
+
+					const child = group.children[ i ];
+					child.rotation.y += 0.005;
+
+				}
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>