فهرست منبع

Merge pull request #16971 from DanielSturk/sheen

MeshPhysicalMaterial: Added sheen
Mr.doob 6 سال پیش
والد
کامیت
1855c1536d

+ 1 - 0
examples/files.js

@@ -185,6 +185,7 @@ var files = {
 		"webgl_materials_video",
 		"webgl_materials_video_webcam",
 		"webgl_materials_wireframe",
+		"webgl_materials_sheen",
 		"webgl_math_orientation_transform",
 		"webgl_mirror",
 		"webgl_modifier_simplifier",

+ 2 - 1
examples/jsm/nodes/materials/StandardNodeMaterial.js

@@ -36,7 +36,8 @@ NodeUtils.addShortcuts( StandardNodeMaterial.prototype, 'fragment', [
 	'ao',
 	'environment',
 	'mask',
-	'position'
+	'position',
+	'sheenColor'
 ] );
 
 export { StandardNodeMaterial };

+ 16 - 2
examples/jsm/nodes/materials/nodes/StandardNode.js

@@ -169,16 +169,18 @@ StandardNode.prototype.build = function ( builder ) {
 			// isolate environment from others inputs ( see TextureNode, CubeTextureNode )
 			// environment.analyze will detect if there is a need of calculate irradiance
 
-			this.environment.analyze( builder, { cache: 'radiance', context: contextEnvironment, slot: 'radiance' } ); 
+			this.environment.analyze( builder, { cache: 'radiance', context: contextEnvironment, slot: 'radiance' } );
 
 			if ( builder.requires.irradiance ) {
 
-				this.environment.analyze( builder, { cache: 'irradiance', context: contextEnvironment, slot: 'irradiance' } ); 
+				this.environment.analyze( builder, { cache: 'irradiance', context: contextEnvironment, slot: 'irradiance' } );
 
 			}
 
 		}
 
+		if ( this.sheenColor ) this.sheenColor.analyze( builder );
+
 		// build code
 
 		var mask = this.mask ? this.mask.flow( builder, 'b' ) : undefined;
@@ -222,6 +224,8 @@ StandardNode.prototype.build = function ( builder ) {
 
 		var clearCoatEnv = useClearCoat && environment ? this.environment.flow( builder, 'c', { cache: 'clearCoat', context: contextClearCoatEnvironment, slot: 'environment' } ) : undefined;
 
+		var sheenColor = this.sheenColor ? this.sheenColor.flow( builder, 'c' ) : undefined;
+
 		builder.requires.transparent = alpha !== undefined;
 
 		builder.addParsCode( [
@@ -342,6 +346,12 @@ StandardNode.prototype.build = function ( builder ) {
 
 		}
 
+		if ( sheenColor ) {
+
+			output.push( 'material.sheenColor = ' + sheenColor.result + ';' );
+
+		}
+
 		if ( reflectivity ) {
 
 			output.push(
@@ -515,6 +525,8 @@ StandardNode.prototype.copy = function ( source ) {
 
 	if ( source.environment ) this.environment = source.environment;
 
+	if ( source.sheenColor ) this.sheenColor = source.sheenColor;
+
 	return this;
 
 };
@@ -559,6 +571,8 @@ StandardNode.prototype.toJSON = function ( meta ) {
 
 		if ( this.environment ) data.environment = this.environment.toJSON( meta ).uuid;
 
+		if ( this.sheenColor ) data.sheenColor = this.sheenColor.toJSON( meta ).uuid;
+
 	}
 
 	return data;

BIN
examples/models/fbx/cloth.fbx


+ 190 - 0
examples/webgl_materials_sheen.html

@@ -0,0 +1,190 @@
+<html lang="en">
+	<head>
+		<title>Sheen demo (material property)</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">
+		<style>
+			body {
+				color: #333;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="info">Sheen demo by <a href="https://github.com/DanielSturk">DanielSturk</a></div>
+		<div id="container"></div>
+
+		<script src="js/libs/ammo.js"></script>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import * as Nodes from './jsm/nodes/Nodes.js';
+
+			import Stats from './jsm/libs/stats.module.js';
+			import { GUI } from './jsm/libs/dat.gui.module.js';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+
+			import { FBXLoader } from './jsm/loaders/FBXLoader.js';
+
+			// Graphics variables
+			var camera, controls, scene, renderer, stats;
+			var directionalLight;
+			var mesh, sphere, material, nodeMaterial;
+
+			var params = {
+				nodeMaterial: true,
+				color: new THREE.Color( 255, 0, 127 ),
+				sheenBRDF: true,
+				sheenColor: new THREE.Color( 10, 10, 10 ), // corresponds to .04 reflectance
+				roughness: .9,
+				exposure: 2,
+			};
+
+			// model
+			new FBXLoader().load( 'models/fbx/cloth.fbx', function ( loadedModel ) {
+
+				mesh = loadedModel.children[0];
+
+				init();
+
+			} );
+
+			function init( ) {
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xbfd1e5 );
+
+				mesh.scale.multiplyScalar( .5 );
+				scene.add( mesh );
+
+				//
+
+				material = new THREE.MeshPhysicalMaterial();
+				material.side = THREE.DoubleSide;
+				material.metalness = 0;
+
+				//
+
+				nodeMaterial = new Nodes.StandardNodeMaterial();
+				nodeMaterial.side = THREE.DoubleSide;
+				nodeMaterial.metalness = new Nodes.FloatNode( 0 );
+				nodeMaterial.roughness = new Nodes.FloatNode();
+				nodeMaterial.color = new Nodes.ColorNode( params.color.clone() );
+
+				//
+
+				sphere = new THREE.Mesh(
+					new THREE.SphereBufferGeometry( 1, 100, 100 ),
+					material
+				);
+				scene.add(sphere);
+
+				camera.position.set( - 12, 7, 4 );
+
+				var container = document.getElementById( 'container' );
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.shadowMap.enabled = true;
+				container.appendChild( renderer.domElement );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 2, 0 );
+				controls.update();
+
+				directionalLight = new THREE.DirectionalLight( 0xffffff, .5 );
+				directionalLight.position.set( 0, 10, 0 );
+				directionalLight.castShadow = true;
+				directionalLight.add(
+					new THREE.Mesh(
+						new THREE.SphereBufferGeometry( .5 ),
+						new THREE.MeshBasicMaterial( { color: 0xffffff } )
+					)
+				);
+
+				scene.add( directionalLight );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				var gui = new GUI();
+
+				gui.add( params, 'nodeMaterial' );
+				gui.addColor( params, 'color' );
+				gui.add( params, 'sheenBRDF' );
+				gui.addColor( params, 'sheenColor' );
+				gui.add( params, 'roughness', 0, 1 );
+				gui.add( params, 'exposure', 0, 3 );
+				gui.open();
+
+				animate();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				mesh.material = sphere.material = params.nodeMaterial
+					? nodeMaterial
+					: material;
+
+				//
+
+				material.sheenColor = params.sheenBRDF
+					? new THREE.Color().copy( params.sheenColor ).multiplyScalar( 1 / 255 )
+					: null;
+
+				material.color.copy( params.color ).multiplyScalar( 1 / 255 );
+				material.roughness = params.roughness;
+
+				material.needsUpdate = true;
+
+				//
+
+				nodeMaterial.sheenColor = params.sheenBRDF
+					? new Nodes.ColorNode( material.sheenColor )
+					: undefined;
+
+				nodeMaterial.color.value.copy( material.color );
+
+				nodeMaterial.roughness.value = params.roughness;
+
+				nodeMaterial.needsCompile = true;
+
+				//
+
+				renderer.toneMappingExposure = params.exposure;
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 5 - 2
src/materials/MeshPhysicalMaterial.d.ts

@@ -4,17 +4,18 @@ import {
 	MeshStandardMaterialParameters,
 	MeshStandardMaterial,
 } from './MeshStandardMaterial';
+import { Color } from './../math/Color';
 
 export interface MeshPhysicalMaterialParameters
 	extends MeshStandardMaterialParameters {
-
 	reflectivity?: number;
 	clearCoat?: number;
 	clearCoatRoughness?: number;
 
+	sheenColor?: Color;
+
 	clearCoatNormalScale?: Vector2;
 	clearCoatNormalMap?: Texture;
-
 }
 
 export class MeshPhysicalMaterial extends MeshStandardMaterial {
@@ -26,6 +27,8 @@ export class MeshPhysicalMaterial extends MeshStandardMaterial {
 	clearCoat: number;
 	clearCoatRoughness: number;
 
+	sheenColor: Color | null;
+
 	clearCoatNormalScale: Vector2;
 	clearCoatNormalMap: Texture | null;
 

+ 8 - 0
src/materials/MeshPhysicalMaterial.js

@@ -1,5 +1,6 @@
 import { Vector2 } from '../math/Vector2.js';
 import { MeshStandardMaterial } from './MeshStandardMaterial.js';
+import { Color } from '../math/Color.js';
 
 /**
  * @author WestLangley / http://github.com/WestLangley
@@ -9,6 +10,8 @@ import { MeshStandardMaterial } from './MeshStandardMaterial.js';
  *  clearCoat: <float>
  *  clearCoatRoughness: <float>
  *
+ *  sheen: <Color>
+ *
  *  clearCoatNormalScale: <Vector2>,
  *  clearCoatNormalMap: new THREE.Texture( <Image> ),
  * }
@@ -27,6 +30,8 @@ function MeshPhysicalMaterial( parameters ) {
 	this.clearCoat = 0.0;
 	this.clearCoatRoughness = 0.0;
 
+	this.sheenColor = null; // null will disable sheen bsdf
+
 	this.clearCoatNormalScale = new Vector2( 1, 1 );
 	this.clearCoatNormalMap = null;
 
@@ -50,6 +55,9 @@ MeshPhysicalMaterial.prototype.copy = function ( source ) {
 	this.clearCoat = source.clearCoat;
 	this.clearCoatRoughness = source.clearCoatRoughness;
 
+	if ( source.sheenColor ) this.sheenColor = ( this.sheenColor || new Color() ).copy( source.sheenColor );
+	else this.sheenColor = null;
+
 	this.clearCoatNormalMap = source.clearCoatNormalMap;
 	this.clearCoatNormalScale.copy( source.clearCoatNormalScale );
 

+ 1 - 0
src/renderers/WebGLRenderer.js

@@ -2271,6 +2271,7 @@ function WebGLRenderer( parameters ) {
 
 		uniforms.clearCoat.value = material.clearCoat;
 		uniforms.clearCoatRoughness.value = material.clearCoatRoughness;
+		if ( material.sheenColor ) uniforms.sheenColor.value.copy( material.sheenColor );
 
 		if ( material.clearCoatNormalMap ) {
 

+ 31 - 0
src/renderers/shaders/ShaderChunk/bsdfs.glsl.js

@@ -336,4 +336,35 @@ float GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {
 float BlinnExponentToGGXRoughness( const in float blinnExponent ) {
 	return sqrt( 2.0 / ( blinnExponent + 2.0 ) );
 }
+
+#if defined( USE_SHEEN )
+
+// https://github.com/google/filament/blob/master/shaders/src/brdf.fs#L94
+float D_Charlie(float roughness, float NoH) {
+	// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"
+	float invAlpha  = 1.0 / roughness;
+	float cos2h = NoH * NoH;
+	float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
+	return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);
+}
+
+// https://github.com/google/filament/blob/master/shaders/src/brdf.fs#L136
+float V_Neubelt(float NoV, float NoL) {
+	// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
+	return saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));
+}
+
+vec3 BRDF_Specular_Sheen( const in float roughness, const in vec3 L, const in GeometricContext geometry, vec3 specularColor ) {
+
+	vec3 N = geometry.normal;
+	vec3 V = geometry.viewDir;
+
+	vec3 H = normalize( V + L );
+	float dotNH = saturate( dot( N, H ) );
+
+	return specularColor * D_Charlie( roughness, dotNH ) * V_Neubelt( dot(N, V), dot(N, L) );
+
+}
+
+#endif
 `;

+ 3 - 0
src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js

@@ -9,4 +9,7 @@ material.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );
 	material.clearCoat = saturate( clearCoat ); // Burley clearcoat model
 	material.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );
 #endif
+#ifdef USE_SHEEN
+	material.sheenColor = sheenColor;
+#endif
 `;

+ 14 - 2
src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js

@@ -10,6 +10,10 @@ struct PhysicalMaterial {
 		float clearCoatRoughness;
 	#endif
 
+	#ifdef USE_SHEEN
+		vec3 sheenColor;
+	#endif
+
 };
 
 #define MAXIMUM_SPECULAR_COEFFICIENT 0.16
@@ -98,10 +102,18 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC
 
 	#endif
 
-	reflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness );
+	#ifdef USE_SHEEN
+		reflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_Sheen(
+			material.specularRoughness,
+			directLight.direction,
+			geometry,
+			material.sheenColor
+		);
+	#else
+		reflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness);
+	#endif
 
 	reflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
-
 }
 
 void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {

+ 1 - 0
src/renderers/shaders/ShaderLib.js

@@ -275,6 +275,7 @@ ShaderLib.physical = {
 		{
 			clearCoat: { value: 0 },
 			clearCoatRoughness: { value: 0 },
+			sheenColor: { value: new Color( 0x000000 ) },
 			clearCoatNormalScale: { value: new Vector2( 1, 1 ) },
 			clearCoatNormalMap: { value: null },
 		}

+ 4 - 0
src/renderers/shaders/ShaderLib/meshphysical_frag.glsl.js

@@ -12,6 +12,10 @@ uniform float opacity;
 	uniform float clearCoatRoughness;
 #endif
 
+#ifdef USE_SHEEN
+	uniform vec3 sheenColor;
+#endif
+
 varying vec3 vViewPosition;
 
 #ifndef FLAT_SHADED

+ 2 - 0
src/renderers/webgl/WebGLProgram.js

@@ -515,6 +515,8 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters,
 			parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
 			parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
 
+			parameters.sheen ? '#define USE_SHEEN' : '',
+
 			parameters.vertexTangents ? '#define USE_TANGENT' : '',
 			parameters.vertexColors ? '#define USE_COLOR' : '',
 			parameters.vertexUvs ? '#define USE_UV' : '',

+ 4 - 1
src/renderers/webgl/WebGLPrograms.js

@@ -37,7 +37,8 @@ function WebGLPrograms( renderer, extensions, capabilities ) {
 		"maxMorphTargets", "maxMorphNormals", "premultipliedAlpha",
 		"numDirLights", "numPointLights", "numSpotLights", "numHemiLights", "numRectAreaLights",
 		"shadowMapEnabled", "shadowMapType", "toneMapping", 'physicallyCorrectLights',
-		"alphaTest", "doubleSided", "flipSided", "numClippingPlanes", "numClipIntersection", "depthPacking", "dithering"
+		"alphaTest", "doubleSided", "flipSided", "numClippingPlanes", "numClipIntersection", "depthPacking", "dithering",
+		"sheen"
 	];
 
 
@@ -162,6 +163,8 @@ function WebGLPrograms( renderer, extensions, capabilities ) {
 
 			gradientMap: !! material.gradientMap,
 
+			sheen: !! material.sheenColor,
+
 			combine: material.combine,
 
 			vertexTangents: ( material.normalMap && material.vertexTangents ),