Ver código fonte

Merged node-clearcoat-normals into dev

arobertson0 6 anos atrás
pai
commit
bee7f0e854
36 arquivos alterados com 537 adições e 51 exclusões
  1. 1 1
      README.md
  2. 1 0
      examples/files.js
  3. BIN
      examples/textures/nvidia_tentacle/tentacle_object_space.png
  4. BIN
      examples/textures/nvidia_tentacle/tentacle_tangent_space.png
  5. 353 0
      examples/webgl_materials_clearcoat_normalmap.html
  6. 3 3
      examples/webgl_materials_envmaps_parallax.html
  7. 8 0
      src/materials/MeshPhysicalMaterial.d.ts
  8. 10 0
      src/materials/MeshPhysicalMaterial.js
  9. 13 0
      src/renderers/WebGLRenderer.js
  10. 3 0
      src/renderers/shaders/ShaderChunk.d.ts
  11. 6 0
      src/renderers/shaders/ShaderChunk.js
  12. 7 7
      src/renderers/shaders/ShaderChunk/bsdfs.glsl.js
  13. 7 0
      src/renderers/shaders/ShaderChunk/clearcoat_normal_fragment_begin.glsl.js
  14. 19 0
      src/renderers/shaders/ShaderChunk/clearcoat_normal_fragment_maps.glsl.js
  15. 10 0
      src/renderers/shaders/ShaderChunk/clearcoat_normalmap_pars_fragment.glsl.js
  16. 8 0
      src/renderers/shaders/ShaderChunk/common.glsl.js
  17. 1 1
      src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js
  18. 2 2
      src/renderers/shaders/ShaderChunk/envmap_pars_fragment.glsl.js
  19. 1 1
      src/renderers/shaders/ShaderChunk/envmap_pars_vertex.glsl.js
  20. 3 3
      src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js
  21. 1 1
      src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js
  22. 9 0
      src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js
  23. 6 3
      src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js
  24. 38 14
      src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js
  25. 3 0
      src/renderers/shaders/ShaderChunk/normal_fragment_begin.glsl.js
  26. 1 1
      src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js
  27. 5 5
      src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl.js
  28. 1 1
      src/renderers/shaders/ShaderChunk/uv_pars_fragment.glsl.js
  29. 1 1
      src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl.js
  30. 1 1
      src/renderers/shaders/ShaderChunk/uv_vertex.glsl.js
  31. 4 1
      src/renderers/shaders/ShaderLib.js
  32. 3 0
      src/renderers/shaders/ShaderLib/meshphysical_frag.glsl.js
  33. 1 1
      src/renderers/shaders/ShaderLib/normal_frag.glsl.js
  34. 2 2
      src/renderers/shaders/ShaderLib/normal_vert.glsl.js
  35. 3 1
      src/renderers/webgl/WebGLProgram.js
  36. 2 1
      src/renderers/webgl/WebGLPrograms.js

+ 1 - 1
README.md

@@ -1,4 +1,4 @@
-three.js 
+three.js
 ========
 
 [![NPM package][npm]][npm-url]

+ 1 - 0
examples/files.js

@@ -147,6 +147,7 @@ var files = {
 		"webgl_materials_bumpmap_skin",
 		"webgl_materials_cars",
 		"webgl_materials_channels",
+		"webgl_materials_clearcoat_normalmap",
 		"webgl_materials_compile",
 		"webgl_materials_cubemap",
 		"webgl_materials_cubemap_balls_reflection",

BIN
examples/textures/nvidia_tentacle/tentacle_object_space.png


BIN
examples/textures/nvidia_tentacle/tentacle_tangent_space.png


+ 353 - 0
examples/webgl_materials_clearcoat_normalmap.html

@@ -0,0 +1,353 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+	<title>three.js webgl - materials - clearcoat normal</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>
+		#vt {
+			display: none
+		}
+
+		#vt,
+		#vt a {
+			color: orange;
+		}
+	</style>
+</head>
+
+<body>
+	<div id="info">
+		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl - materials - clearcoat normal
+		demo.<br />
+	</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 { OrbitControls } from './jsm/controls/OrbitControls.js';
+		import { HDRCubeTextureLoader } from './jsm/loaders/HDRCubeTextureLoader.js';
+		import { PMREMGenerator } from './jsm/pmrem/PMREMGenerator.js';
+		import { PMREMCubeUVPacker } from './jsm/pmrem/PMREMCubeUVPacker.js';
+
+		var container, stats, loader;
+
+		var camera, scene, renderer;
+
+		var guiParams = {
+			metalness: 0.0,
+			roughness: 1.0,
+			clearCoat: 1.0,
+			clearCoatRoughness: 0.0,
+			reflectivity: 0.0,
+		};
+
+		var guiNormalMapNames = [
+			"face",
+			"tentacle"
+		];
+
+		var guiMatParams = {
+			normalMap: "face",
+			normalScale: 1.0,
+			clearCoatNormalMap: "tentacle",
+			clearCoatNormalScale: 1.0,
+		};
+
+		var particleLight;
+		var meshes = [];
+		var mats = [];
+		var normalMaps = {};
+
+		var windowHalfX = window.innerWidth / 2;
+		var windowHalfY = window.innerHeight / 2;
+
+		const sphereSize = 80;
+		const sphereSpacing = 1.25 * sphereSize * 2;
+
+		const variationsX = [
+			(mat, scale, map) => { mat.clearCoatNormalScale.copy(mat.normalScale); },
+			(mat, scale, map) => {
+				mat.clearCoatNormalScale = new THREE.Vector2(scale, scale);
+				if (mat.clearCoatNormalMap !== map) {
+					mat.clearCoatNormalMap = map;
+					mat.needsUpdate = true;
+				}
+			},
+		];
+
+		const variationsY = [
+			(mat, scale, map) => { },
+			(mat, scale, map) => {
+				mat.normalScale = new THREE.Vector2(scale, scale);
+				if (mat.normalMap !== map) {
+					mat.normalMap = map;
+					mat.needsUpdate = true;
+				}
+			},
+		];
+
+		const maxI = variationsX.length;
+		const maxJ = variationsY.length;
+
+		new THREE.FontLoader()
+			.load('fonts/gentilis_regular.typeface.json', function (font) {
+				init(font);
+				animate();
+			});
+
+
+		function init(font) {
+
+			container = document.createElement('div');
+			document.body.appendChild(container);
+
+			camera = new THREE.PerspectiveCamera(27, window.innerWidth / window.innerHeight, 1, 10000);
+			camera.position.z = 1500;
+
+			var genCubeUrls = function (prefix, postfix) {
+
+				return [
+					prefix + 'px' + postfix, prefix + 'nx' + postfix,
+					prefix + 'py' + postfix, prefix + 'ny' + postfix,
+					prefix + 'pz' + postfix, prefix + 'nz' + postfix
+				];
+
+			};
+
+			scene = new THREE.Scene();
+
+			var hdrUrls = genCubeUrls('./textures/cube/pisaHDR/', '.hdr');
+
+			new HDRCubeTextureLoader()
+				.setType(THREE.UnsignedByteType)
+				.load(hdrUrls, function (hdrCubeMap) {
+
+					var pmremGenerator = new PMREMGenerator(hdrCubeMap);
+					pmremGenerator.update(renderer);
+
+					var pmremCubeUVPacker = new PMREMCubeUVPacker(pmremGenerator.cubeLods);
+					pmremCubeUVPacker.update(renderer);
+
+					var hdrCubeRenderTarget = pmremCubeUVPacker.CubeUVRenderTarget;
+
+					var geometry = new THREE.SphereBufferGeometry(80, 64, 32);
+
+					var textureLoader = new THREE.TextureLoader();
+
+					normalMaps.tentacle = textureLoader
+						.load("textures/nvidia_tentacle/tentacle_tangent_space.png");
+
+					normalMaps.face = textureLoader
+						.load("models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg");
+
+					// objectNormalMap = textureLoader
+					// 	.load("textures/nvidia_tentacle/tentacle_object_space.png");
+
+					var matParams = {
+						envMap: hdrCubeRenderTarget.texture,
+					};
+
+
+					for (var ii = 0; ii < maxI; ii++) {
+						if (!mats[ii]) {
+							mats[ii] = [];
+						}
+						for (var jj = 0; jj < maxJ; jj++) {
+
+							var mat = mats[ii][jj] = new THREE.MeshPhysicalMaterial(matParams);
+
+							variationsX[ii](mat, 1, normalMaps.tentacle);
+							variationsY[jj](mat, 1, normalMaps.face);
+							var mesh = new THREE.Mesh(geometry, mat);
+							meshes.push(mesh);
+							mesh.position.x = (ii - (maxI - 1) / 2) * sphereSpacing;
+							mesh.position.y = (jj - (maxJ - 1) / 2) * sphereSpacing;
+							scene.add(mesh);
+						}
+					}
+
+					hdrCubeMap.magFilter = THREE.LinearFilter;
+					hdrCubeMap.needsUpdate = true;
+					scene.background = hdrCubeMap;
+
+					pmremGenerator.dispose();
+					pmremCubeUVPacker.dispose();
+				});
+
+			function addLabel(name, location, fontSize) {
+				fontSize = fontSize | 20;
+				var textGeo = new THREE.TextBufferGeometry(name, {
+
+					font: font,
+					size: fontSize,
+					height: 1,
+					curveSegments: 1
+
+				});
+
+				var textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
+				var textMesh = new THREE.Mesh(textGeo, textMaterial);
+
+				textGeo.computeBoundingBox();
+				var bb = textGeo.boundingBox.clone();
+				var displace = bb.max.sub(bb.min).divide(new THREE.Vector3(2, 2, 2));
+
+				textMesh.position.copy(location.sub(displace));
+				scene.add(textMesh);
+
+			}
+
+
+			addLabel("Normal Map", new THREE.Vector3(-350, 0, 0), 30);
+			addLabel("None", new THREE.Vector3(-250, -100, 0));
+			addLabel("Map", new THREE.Vector3(-300, 100, 0));
+
+			addLabel("Clearcoat Normal Map", new THREE.Vector3(0, 260, 0), 30);
+			addLabel("None", new THREE.Vector3(-100, 200, 0));
+			addLabel("Map", new THREE.Vector3(100, 200, 0));
+
+			// LIGHTS
+
+			particleLight = new THREE.Mesh(new THREE.SphereBufferGeometry(4, 8, 8), new THREE.MeshBasicMaterial({ color: 0xffffff }));
+			scene.add(particleLight);
+
+			{
+				var ambientLight = new THREE.AmbientLight(0x444444);
+				scene.add(ambientLight);
+			}
+			{
+				var pointLight = new THREE.PointLight(0xffffff, 1.25, 1000);
+				particleLight.add(pointLight);
+			}
+			{
+				var directionalLight = new THREE.DirectionalLight(0xffffff);
+				directionalLight.position.set(1, - 0.5, - 1);
+				scene.add(directionalLight);
+			}
+
+
+			renderer = new THREE.WebGLRenderer();
+			renderer.setSize(window.innerWidth, window.innerHeight);
+			container.appendChild(renderer.domElement);
+
+			//
+
+			renderer.gammaInput = true;
+			renderer.gammaOutput = true;
+
+			//
+
+			stats = new Stats();
+			container.appendChild(stats.dom);
+
+			// EVENTS
+
+			var controls = new OrbitControls(camera, renderer.domElement);
+
+			window.addEventListener('resize', onWindowResize, false);
+
+			var gui = new GUI();
+
+			{
+				var materialGui = gui.addFolder( 'Physical Material' );
+				materialGui.add(guiParams, 'metalness', 0, 1, 0.01);
+				materialGui.add(guiParams, 'roughness', 0, 1, 0.01);
+				materialGui.add(guiParams, 'reflectivity', 0, 1, 0.01);
+				materialGui.open();
+			}
+			{
+				var clearCoatGui = gui.addFolder( 'ClearCoat' );
+				clearCoatGui.add(guiParams, 'clearCoat', 0, 1, 0.01);
+				clearCoatGui.add(guiParams, 'clearCoatRoughness', 0, 1, 0.01);
+				clearCoatGui.open();
+			}
+			{
+				var normalGui = gui.addFolder( 'Normal Maps' );
+				normalGui.add(guiMatParams, 'normalMap', guiNormalMapNames);
+				normalGui.add(guiMatParams, 'normalScale', 0, 1, 0.01);
+				normalGui.add(guiMatParams, 'clearCoatNormalMap', guiNormalMapNames);
+				normalGui.add(guiMatParams, 'clearCoatNormalScale', 0, 1, 0.01);
+				normalGui.open();
+			}
+
+
+
+
+
+			gui.open();
+		}
+
+		//
+
+		function onWindowResize() {
+			var width = window.innerWidth;
+			var height = window.innerHeight;
+
+			camera.aspect = width / height;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize(width, height);
+		}
+
+		//
+
+		function animate() {
+
+			requestAnimationFrame(animate);
+
+			render();
+
+			stats.update();
+
+		}
+
+		function render() {
+
+			var matColor = new THREE.Color().setHSL(0.0, 0.5, 0.25);
+
+			for (var ii = 0; ii < maxI; ii++) {
+				if (mats[ii]) {
+					for (var jj = 0; jj < maxJ; jj++) {
+						var mat = mats[ii][jj];
+						mat.color = matColor;
+						for (var matParam in guiParams) {
+							mat[matParam] = guiParams[matParam];
+						}
+
+						var normalMap = normalMaps[guiMatParams.normalMap];
+						var clearCoatNormalMap = normalMaps[guiMatParams.clearCoatNormalMap];
+
+						variationsX[ii](mat, guiMatParams.clearCoatNormalScale, clearCoatNormalMap);
+						variationsY[jj](mat, guiMatParams.normalScale, normalMap);
+					}
+				}
+			}
+
+			var 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 (var mesh of meshes) {
+				mesh.rotation.y += 0.01;
+			}
+
+
+			renderer.render(scene, camera);
+
+		}
+
+	</script>
+
+</body>
+
+</html>

+ 3 - 3
examples/webgl_materials_envmaps_parallax.html

@@ -156,15 +156,15 @@
 					return clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );
 				}
 
-				vec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {
+				vec3 getLightProbeIndirectRadiance( const in vec3 viewDir, const in vec3 normal, const in float blinnShininessExponent, const in int maxMIPLevel ) {
 
 					#ifdef ENVMAP_MODE_REFLECTION
 
-						vec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );
+						vec3 reflectVec = reflect( -viewDir, normal );
 
 					#else
 
-						vec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );
+						vec3 reflectVec = refract( -viewDir, normal, refractionRatio );
 
 					#endif
 

+ 8 - 0
src/materials/MeshPhysicalMaterial.d.ts

@@ -1,3 +1,5 @@
+import { Texture } from './../textures/Texture';
+import { Vector2 } from './../math/Vector2';
 import {
 	MeshStandardMaterialParameters,
 	MeshStandardMaterial,
@@ -8,6 +10,9 @@ export interface MeshPhysicalMaterialParameters
 	reflectivity?: number;
 	clearCoat?: number;
 	clearCoatRoughness?: number;
+
+	clearCoatNormalScale?: Vector2;
+	clearCoatNormalMap?: Texture;
 }
 
 export class MeshPhysicalMaterial extends MeshStandardMaterial {
@@ -19,4 +24,7 @@ export class MeshPhysicalMaterial extends MeshStandardMaterial {
 	clearCoat: number;
 	clearCoatRoughness: number;
 
+	clearCoatNormalScale: Vector2;
+	clearCoatNormalMap: Texture | null;
+
 }

+ 10 - 0
src/materials/MeshPhysicalMaterial.js

@@ -1,3 +1,4 @@
+import { Vector2 } from '../math/Vector2.js';
 import { MeshStandardMaterial } from './MeshStandardMaterial.js';
 
 /**
@@ -7,6 +8,9 @@ import { MeshStandardMaterial } from './MeshStandardMaterial.js';
  *  reflectivity: <float>
  *  clearCoat: <float>
  *  clearCoatRoughness: <float>
+ *
+ *  clearCoatNormalScale: <Vector2>,
+ *  clearCoatNormalMap: new THREE.Texture( <Image> ),
  * }
  */
 
@@ -23,6 +27,9 @@ function MeshPhysicalMaterial( parameters ) {
 	this.clearCoat = 0.0;
 	this.clearCoatRoughness = 0.0;
 
+	this.clearCoatNormalScale = new Vector2( 1, 1 );
+	this.clearCoatNormalMap = null;
+
 	this.setValues( parameters );
 
 }
@@ -43,6 +50,9 @@ MeshPhysicalMaterial.prototype.copy = function ( source ) {
 	this.clearCoat = source.clearCoat;
 	this.clearCoatRoughness = source.clearCoatRoughness;
 
+	this.clearCoatNormalMap = source.clearCoatNormalMap;
+	this.clearCoatNormalScale.copy( source.clearCoatNormalScale );
+
 	return this;
 
 };

+ 13 - 0
src/renderers/WebGLRenderer.js

@@ -2275,6 +2275,19 @@ function WebGLRenderer( parameters ) {
 		uniforms.clearCoat.value = material.clearCoat;
 		uniforms.clearCoatRoughness.value = material.clearCoatRoughness;
 
+		if ( material.clearCoatNormalMap ) {
+
+			uniforms.clearCoatNormalScale.value.copy( material.clearCoatNormalScale );
+			uniforms.clearCoatNormalMap.value = material.clearCoatNormalMap;
+
+			if ( material.side === BackSide ) {
+
+				uniforms.clearCoatNormalScale.value.negate();
+
+			}
+
+		}
+
 	}
 
 	function refreshUniformsMatcap( uniforms, material ) {

+ 3 - 0
src/renderers/shaders/ShaderChunk.d.ts

@@ -83,6 +83,9 @@ export let ShaderChunk: {
 	normal_fragment_maps: string;
 	normal_vert: string;
 	normalmap_pars_fragment: string;
+	clearcoat_normal_fragment_begin: string;
+	clearcoat_normal_fragment_maps: string;
+	clearcoat_normalmap_pars_fragment: string;
 	packing: string;
 	points_frag: string;
 	points_vert: string;

+ 6 - 0
src/renderers/shaders/ShaderChunk.js

@@ -61,6 +61,9 @@ import morphtarget_vertex from './ShaderChunk/morphtarget_vertex.glsl.js';
 import normal_fragment_begin from './ShaderChunk/normal_fragment_begin.glsl.js';
 import normal_fragment_maps from './ShaderChunk/normal_fragment_maps.glsl.js';
 import normalmap_pars_fragment from './ShaderChunk/normalmap_pars_fragment.glsl.js';
+import clearcoat_normal_fragment_begin from './ShaderChunk/clearcoat_normal_fragment_begin.glsl.js';
+import clearcoat_normal_fragment_maps from './ShaderChunk/clearcoat_normal_fragment_maps.glsl.js';
+import clearcoat_normalmap_pars_fragment from './ShaderChunk/clearcoat_normalmap_pars_fragment.glsl.js';
 import packing from './ShaderChunk/packing.glsl.js';
 import premultiplied_alpha_fragment from './ShaderChunk/premultiplied_alpha_fragment.glsl.js';
 import project_vertex from './ShaderChunk/project_vertex.glsl.js';
@@ -183,6 +186,9 @@ export var ShaderChunk = {
 	normal_fragment_begin: normal_fragment_begin,
 	normal_fragment_maps: normal_fragment_maps,
 	normalmap_pars_fragment: normalmap_pars_fragment,
+	clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin,
+	clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps,
+	clearcoat_normalmap_pars_fragment: clearcoat_normalmap_pars_fragment,
 	packing: packing,
 	premultiplied_alpha_fragment: premultiplied_alpha_fragment,
 	project_vertex: project_vertex,

+ 7 - 7
src/renderers/shaders/ShaderChunk/bsdfs.glsl.js

@@ -125,15 +125,15 @@ float D_GGX( const in float alpha, const in float dotNH ) {
 }
 
 // GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
-vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {
+vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {
 
 	float alpha = pow2( roughness ); // UE4's roughness
 
-	vec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );
+	vec3 halfDir = normalize( incidentLight.direction + viewDir );
 
-	float dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );
-	float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );
-	float dotNH = saturate( dot( geometry.normal, halfDir ) );
+	float dotNL = saturate( dot( normal, incidentLight.direction ) );
+	float dotNV = saturate( dot( normal, viewDir ) );
+	float dotNH = saturate( dot( normal, halfDir ) );
 	float dotLH = saturate( dot( incidentLight.direction, halfDir ) );
 
 	vec3 F = F_Schlick( specularColor, dotLH );
@@ -264,9 +264,9 @@ vec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in m
 // End Rect Area Light
 
 // ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
-vec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {
+vec3 BRDF_Specular_GGX_Environment( const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {
 
-	float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );
+	float dotNV = saturate( dot( normal, viewDir ) );
 
 	vec2 brdf = integrateSpecularBRDF( dotNV, roughness );
 

+ 7 - 0
src/renderers/shaders/ShaderChunk/clearcoat_normal_fragment_begin.glsl.js

@@ -0,0 +1,7 @@
+export default /* glsl */`
+#ifndef STANDARD
+  #ifdef USE_CLEARCOAT_NORMALMAP
+    vec3 clearCoatNormal = geometryNormal;
+  #endif
+#endif
+`;

+ 19 - 0
src/renderers/shaders/ShaderChunk/clearcoat_normal_fragment_maps.glsl.js

@@ -0,0 +1,19 @@
+export default /* glsl */`
+#ifndef STANDARD
+	#ifdef USE_CLEARCOAT_NORMALMAP
+
+		#ifdef USE_TANGENT
+
+			mat3 vTBN = mat3( tangent, bitangent, clearCoatNormal );
+			vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;
+			mapN.xy = clearCoatNormalScale * mapN.xy;
+			clearCoatNormal = normalize( vTBN * mapN );
+
+		#else
+
+			clearCoatNormal = perturbNormal2Arb( -vViewPosition, clearCoatNormal, clearCoatNormalScale, clearCoatNormalMap );
+
+		#endif
+	#endif
+#endif
+`;

+ 10 - 0
src/renderers/shaders/ShaderChunk/clearcoat_normalmap_pars_fragment.glsl.js

@@ -0,0 +1,10 @@
+export default /* glsl */`
+#ifndef STANDARD
+	#ifdef USE_CLEARCOAT_NORMALMAP
+
+	  uniform sampler2D clearCoatNormalMap;
+		uniform vec2 clearCoatNormalScale;
+
+	#endif
+#endif
+`;

+ 8 - 0
src/renderers/shaders/ShaderChunk/common.glsl.js

@@ -39,6 +39,14 @@ struct GeometricContext {
 	vec3 position;
 	vec3 normal;
 	vec3 viewDir;
+
+	#ifndef STANDARD
+		#ifdef USE_CLEARCOAT_NORMALMAP
+
+			vec3 clearCoatNormal;
+
+		#endif
+  #endif
 };
 
 vec3 transformDirection( in vec3 dir, in mat4 matrix ) {

+ 1 - 1
src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js

@@ -1,7 +1,7 @@
 export default /* glsl */`
 #ifdef USE_ENVMAP
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( PHONG )
 
 		vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );
 

+ 2 - 2
src/renderers/shaders/ShaderChunk/envmap_pars_fragment.glsl.js

@@ -6,7 +6,7 @@ export default /* glsl */`
 
 #ifdef USE_ENVMAP
 
-	#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )
+	#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( PHONG ) )
 		varying vec3 vWorldPosition;
 	#endif
 
@@ -18,7 +18,7 @@ export default /* glsl */`
 	uniform float flipEnvMap;
 	uniform int maxMipLevel;
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )
 		uniform float refractionRatio;
 	#else
 		varying vec3 vReflect;

+ 1 - 1
src/renderers/shaders/ShaderChunk/envmap_pars_vertex.glsl.js

@@ -1,7 +1,7 @@
 export default /* glsl */`
 #ifdef USE_ENVMAP
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( PHONG )
 		varying vec3 vWorldPosition;
 
 	#else

+ 3 - 3
src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js

@@ -54,15 +54,15 @@ export default /* glsl */`
 
 	}
 
-	vec3 getLightProbeIndirectRadiance( /*const in SpecularLightProbe specularLightProbe,*/ const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {
+	vec3 getLightProbeIndirectRadiance( /*const in SpecularLightProbe specularLightProbe,*/ const in vec3 viewDir, const in vec3 normal, const in float blinnShininessExponent, const in int maxMIPLevel ) {
 
 		#ifdef ENVMAP_MODE_REFLECTION
 
-			vec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );
+		  vec3 reflectVec = reflect( -viewDir, normal );
 
 		#else
 
-			vec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );
+		  vec3 reflectVec = refract( -viewDir, normal, refractionRatio );
 
 		#endif
 

+ 1 - 1
src/renderers/shaders/ShaderChunk/envmap_vertex.glsl.js

@@ -1,7 +1,7 @@
 export default /* glsl */`
 #ifdef USE_ENVMAP
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( PHONG )
 
 		vWorldPosition = worldPosition.xyz;
 

+ 9 - 0
src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js

@@ -20,6 +20,15 @@ geometry.position = - vViewPosition;
 geometry.normal = normal;
 geometry.viewDir = normalize( vViewPosition );
 
+#ifdef PHYSICAL
+	#ifndef STANDARD
+		#ifdef USE_CLEARCOAT_NORMALMAP
+
+			geometry.clearCoatNormal = clearCoatNormal;
+
+		#endif
+	#endif
+#endif
 IncidentLight directLight;
 
 #if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )

+ 6 - 3
src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js

@@ -25,11 +25,14 @@ export default /* glsl */`
 
 #if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )
 
-	radiance += getLightProbeIndirectRadiance( /*specularLightProbe,*/ geometry, Material_BlinnShininessExponent( material ), maxMipLevel );
+  radiance += getLightProbeIndirectRadiance( /*specularLightProbe,*/ geometry.viewDir, geometry.normal, Material_BlinnShininessExponent( material ), maxMipLevel );
 
 	#ifndef STANDARD
-		clearCoatRadiance += getLightProbeIndirectRadiance( /*specularLightProbe,*/ geometry, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );
+		#ifdef USE_CLEARCOAT_NORMALMAP
+			clearCoatRadiance += getLightProbeIndirectRadiance( /*specularLightProbe,*/ geometry.viewDir, geometry.clearCoatNormal, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );
+		#else
+			clearCoatRadiance += getLightProbeIndirectRadiance( /*specularLightProbe,*/ geometry.viewDir, geometry.normal, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );
+		#endif
 	#endif
-
 #endif
 `;

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

@@ -65,9 +65,8 @@ float clearCoatDHRApprox( const in float roughness, const in float dotNL ) {
 #endif
 
 void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {
-
+ 
 	float dotNL = saturate( dot( geometry.normal, directLight.direction ) );
-
 	vec3 irradiance = dotNL * directLight.color;
 
 	#ifndef PHYSICALLY_CORRECT_LIGHTS
@@ -77,19 +76,38 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC
 	#endif
 
 	#ifndef STANDARD
-		float clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );
+		#ifdef USE_CLEARCOAT_NORMALMAP
+			float ccDotNL = saturate( dot( geometry.clearCoatNormal, directLight.direction ) );
+			vec3 ccIrradiance = ccDotNL * directLight.color;
+
+			#ifndef PHYSICALLY_CORRECT_LIGHTS
+
+				ccIrradiance *= PI; // punctual light
+
+			#endif
+		#endif
+	#endif
+
+	#ifndef STANDARD
+		#ifdef USE_CLEARCOAT_NORMALMAP
+			float clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, ccDotNL );
+		#else
+			float clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );
+		#endif
 	#else
 		float clearCoatDHR = 0.0;
 	#endif
 
-	reflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );
+	reflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness );
 
 	reflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );
 
 	#ifndef STANDARD
-
-		reflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );
-
+		#ifdef USE_CLEARCOAT_NORMALMAP
+			reflectedLight.directSpecular += ccIrradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearCoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );
+		#else
+			reflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );
+		#endif
 	#endif
 
 }
@@ -109,9 +127,13 @@ void RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricCo
 void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
 
 	#ifndef STANDARD
-		float dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );
-		float dotNL = dotNV;
-		float clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );
+		#ifdef USE_CLEARCOAT_NORMALMAP
+			float ccDotNV = saturate( dot( geometry.clearCoatNormal, geometry.viewDir ) );
+		#else
+			float ccDotNV = saturate( dot( geometry.normal, geometry.viewDir ) );
+		#endif
+		float ccDotNL = ccDotNV;
+		float clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, ccDotNL );
 	#else
 		float clearCoatDHR = 0.0;
 	#endif
@@ -136,14 +158,16 @@ void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradia
 
 	#else
 
-		reflectedLight.indirectSpecular += clearCoatInv * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );
+		reflectedLight.indirectSpecular += clearCoatInv * radiance * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness );
 
 	#endif
 
 	#ifndef STANDARD
-
-		reflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );
-
+		#ifdef USE_CLEARCOAT_NORMALMAP
+			reflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.clearCoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );
+		#else
+			reflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.normal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );
+		#endif
 	#endif
 }
 

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

@@ -32,4 +32,7 @@ export default /* glsl */`
 	#endif
 
 #endif
+#if defined( PHYSICAL ) && !defined( STANDARD )
+	vec3 geometryNormal = normal;
+#endif
 `;

+ 1 - 1
src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js

@@ -30,7 +30,7 @@ export default /* glsl */`
 
 		#else
 
-			normal = perturbNormal2Arb( -vViewPosition, normal );
+			normal = perturbNormal2Arb( -vViewPosition, normal, normalScale, normalMap );
 
 		#endif
 

+ 5 - 5
src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl.js

@@ -8,12 +8,15 @@ export default /* glsl */`
 
 		uniform mat3 normalMatrix;
 
-	#else
+	#endif
+#endif
+
+#if ( defined ( USE_NORMALMAP ) && !defined ( OBJECTSPACE_NORMALMAP )) || defined ( USE_CLEARCOAT_NORMALMAP )
 
 		// Per-Pixel Tangent Space Normal Mapping
 		// http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html
 
-		vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {
+		vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec2 normalScale, in sampler2D normalMap ) {
 
 			// Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988
 
@@ -37,8 +40,5 @@ export default /* glsl */`
 			return normalize( tsn * mapN );
 
 		}
-
-	#endif
-
 #endif
 `;

+ 1 - 1
src/renderers/shaders/ShaderChunk/uv_pars_fragment.glsl.js

@@ -1,5 +1,5 @@
 export default /* glsl */`
-#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
+#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
 
 	varying vec2 vUv;
 

+ 1 - 1
src/renderers/shaders/ShaderChunk/uv_pars_vertex.glsl.js

@@ -1,5 +1,5 @@
 export default /* glsl */`
-#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
+#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
 
 	varying vec2 vUv;
 	uniform mat3 uvTransform;

+ 1 - 1
src/renderers/shaders/ShaderChunk/uv_vertex.glsl.js

@@ -1,5 +1,5 @@
 export default /* glsl */`
-#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
+#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )  || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )
 
 	vUv = ( uvTransform * vec3( uv, 1 ) ).xy;
 

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

@@ -1,5 +1,6 @@
 import { ShaderChunk } from './ShaderChunk.js';
 import { mergeUniforms } from './UniformsUtils.js';
+import { Vector2 } from '../../math/Vector2.js';
 import { Vector3 } from '../../math/Vector3.js';
 import { UniformsLib } from './UniformsLib.js';
 import { Color } from '../../math/Color.js';
@@ -273,7 +274,9 @@ ShaderLib.physical = {
 		ShaderLib.standard.uniforms,
 		{
 			clearCoat: { value: 0 },
-			clearCoatRoughness: { value: 0 }
+			clearCoatRoughness: { value: 0 },
+			clearCoatNormalScale: { value: new Vector2( 1, 1 ) },
+			clearCoatNormalMap: { value: null },
 		}
 	] ),
 

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

@@ -48,6 +48,7 @@ varying vec3 vViewPosition;
 #include <shadowmap_pars_fragment>
 #include <bumpmap_pars_fragment>
 #include <normalmap_pars_fragment>
+#include <clearcoat_normalmap_pars_fragment>
 #include <roughnessmap_pars_fragment>
 #include <metalnessmap_pars_fragment>
 #include <logdepthbuf_pars_fragment>
@@ -70,6 +71,8 @@ void main() {
 	#include <metalnessmap_fragment>
 	#include <normal_fragment_begin>
 	#include <normal_fragment_maps>
+	#include <clearcoat_normal_fragment_begin>
+	#include <clearcoat_normal_fragment_maps>
 	#include <emissivemap_fragment>
 
 	// accumulation

+ 1 - 1
src/renderers/shaders/ShaderLib/normal_frag.glsl.js

@@ -3,7 +3,7 @@ export default /* glsl */`
 
 uniform float opacity;
 
-#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) )
+#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) ) || defined( USE_CLEARCOAT_NORMALMAP )
 
 	varying vec3 vViewPosition;
 

+ 2 - 2
src/renderers/shaders/ShaderLib/normal_vert.glsl.js

@@ -1,7 +1,7 @@
 export default /* glsl */`
 #define NORMAL
 
-#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) )
+#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) ) || defined( USE_CLEARCOAT_NORMALMAP )
 
 	varying vec3 vViewPosition;
 
@@ -58,7 +58,7 @@ void main() {
 	#include <logdepthbuf_vertex>
 	#include <clipping_planes_vertex>
 
-#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) )
+#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) ) || defined( USE_CLEARCOAT_NORMALMAP )
 
 	vViewPosition = - mvPosition.xyz;
 

+ 3 - 1
src/renderers/webgl/WebGLProgram.js

@@ -118,7 +118,7 @@ function generateExtensions( extensions, parameters, rendererExtensions ) {
 	extensions = extensions || {};
 
 	var chunks = [
-		( extensions.derivatives || parameters.envMapCubeUV || parameters.bumpMap || ( parameters.normalMap && ! parameters.objectSpaceNormalMap ) || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '',
+		( extensions.derivatives || parameters.envMapCubeUV || parameters.bumpMap || ( parameters.normalMap && ! parameters.objectSpaceNormalMap ) || parameters.clearCoatNormalMap || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '',
 		( extensions.fragDepth || parameters.logarithmicDepthBuffer ) && rendererExtensions.get( 'EXT_frag_depth' ) ? '#extension GL_EXT_frag_depth : enable' : '',
 		( extensions.drawBuffers ) && rendererExtensions.get( 'WEBGL_draw_buffers' ) ? '#extension GL_EXT_draw_buffers : require' : '',
 		( extensions.shaderTextureLOD || parameters.envMap ) && rendererExtensions.get( 'EXT_shader_texture_lod' ) ? '#extension GL_EXT_shader_texture_lod : enable' : ''
@@ -384,6 +384,7 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters,
 			parameters.bumpMap ? '#define USE_BUMPMAP' : '',
 			parameters.normalMap ? '#define USE_NORMALMAP' : '',
 			( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
+			parameters.clearCoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
 			parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '',
 			parameters.specularMap ? '#define USE_SPECULARMAP' : '',
 			parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
@@ -500,6 +501,7 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters,
 			parameters.bumpMap ? '#define USE_BUMPMAP' : '',
 			parameters.normalMap ? '#define USE_NORMALMAP' : '',
 			( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
+			parameters.clearCoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
 			parameters.specularMap ? '#define USE_SPECULARMAP' : '',
 			parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
 			parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',

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

@@ -29,7 +29,7 @@ function WebGLPrograms( renderer, extensions, capabilities ) {
 
 	var parameterNames = [
 		"precision", "supportsVertexTextures", "map", "mapEncoding", "matcap", "matcapEncoding", "envMap", "envMapMode", "envMapEncoding",
-		"lightMap", "aoMap", "emissiveMap", "emissiveMapEncoding", "bumpMap", "normalMap", "objectSpaceNormalMap", "displacementMap", "specularMap",
+		"lightMap", "aoMap", "emissiveMap", "emissiveMapEncoding", "bumpMap", "normalMap", "objectSpaceNormalMap", "clearCoatNormalMap", "displacementMap", "specularMap",
 		"roughnessMap", "metalnessMap", "gradientMap",
 		"alphaMap", "combine", "vertexColors", "vertexTangents", "fog", "useFog", "fogExp",
 		"flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning",
@@ -153,6 +153,7 @@ function WebGLPrograms( renderer, extensions, capabilities ) {
 			bumpMap: !! material.bumpMap,
 			normalMap: !! material.normalMap,
 			objectSpaceNormalMap: material.normalMapType === ObjectSpaceNormalMap,
+			clearCoatNormalMap: !! material.clearCoatNormalMap,
 			displacementMap: !! material.displacementMap,
 			roughnessMap: !! material.roughnessMap,
 			metalnessMap: !! material.metalnessMap,