Browse Source

Merge pull request #5841 from sebavan/master

Add Clear Coat to PBR
sebavan 6 năm trước cách đây
mục cha
commit
e1c2b811d3

+ 2 - 0
dist/preview release/what's new.md

@@ -24,6 +24,8 @@
   - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
   - Added support for [nine patch stretch](https://www.babylonjs-playground.com/#G5H9IN#2) mode for images. ([Deltakosh](https://github.com/deltakosh))
   - InvalidateRect added to AdvancedDynamicTexture to improve perf for heavily populated GUIs, works with shadows ([TrevorDev](https://github.com/TrevorDev))
+- Migrate the code to modules and deploy es6 npm packages ([Sebavan](https://github.com/Sebavan))
+- Add clear coat support to PBR ([Sebavan](https://github.com/Sebavan))
 
 ## Updates
 

+ 142 - 3
src/Materials/PBR/pbrBaseMaterial.ts

@@ -14,6 +14,7 @@ import { AbstractMesh } from "../../Meshes/abstractMesh";
 import { Mesh } from "../../Meshes/mesh";
 import { _TimeToken } from "../../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../../States/index";
+import { IMaterialClearCoatDefines, PBRClearCoatConfiguration } from "./pbrClearCoatConfiguration";
 
 import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from "../../Materials/imageProcessingConfiguration";
 import { Effect, EffectFallbacks, EffectCreationOptions } from "../../Materials/effect";
@@ -37,7 +38,7 @@ import "../../Shaders/pbr.vertex";
  * Manages the defines for the PBR Material.
  * @hiddenChildren
  */
-class PBRMaterialDefines extends MaterialDefines implements IImageProcessingConfigurationDefines {
+class PBRMaterialDefines extends MaterialDefines implements IImageProcessingConfigurationDefines, IMaterialClearCoatDefines {
     public PBR = true;
 
     public MAINUV1 = false;
@@ -177,6 +178,12 @@ class PBRMaterialDefines extends MaterialDefines implements IImageProcessingConf
 
     public SPECULARAA = false;
 
+    public CLEARCOAT = false;
+    public CLEARCOAT_TEXTURE = false;
+    public CLEARCOAT_TEXTUREDIRECTUV = 0;
+    public CLEARCOAT_BUMP = false;
+    public CLEARCOAT_BUMPDIRECTUV = 0;
+
     public UNLIT = false;
 
     /**
@@ -643,6 +650,11 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     private _unlit = false;
 
     /**
+     * Defines the clear coat layer parameters for the material.
+     */
+    public readonly clearCoat = new PBRClearCoatConfiguration(this._markAllSubMeshesAsTexturesDirty.bind(this));
+
+    /**
      * Instantiates a new PBRMaterial instance.
      *
      * @param name The material name
@@ -909,6 +921,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             }
         }
 
+        if (!this.clearCoat.isReadyForSubMesh(defines, scene, engine, this._disableBumpMap)) {
+            return false;
+        }
+
         if (defines._areImageProcessingDirty && this._imageProcessingConfiguration) {
             if (!this._imageProcessingConfiguration.isReady()) {
                 return false;
@@ -994,6 +1010,8 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             fallbacks.addFallback(fallbackRank++, "PARALLAXOCCLUSION");
         }
 
+        fallbackRank = PBRClearCoatConfiguration.AddFallbacks(defines, fallbacks, fallbackRank);
+
         if (defines.ENVIRONMENTBRDF) {
             fallbacks.addFallback(fallbackRank++, "ENVIRONMENTBRDF");
         }
@@ -1091,8 +1109,12 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             "refractionSampler", "refractionSamplerLow", "refractionSamplerHigh",
             "reflectionSampler", "reflectionSamplerLow", "reflectionSamplerHigh",
             "microSurfaceSampler", "environmentBrdfSampler", "boneSampler"];
+
         var uniformBuffers = ["Material", "Scene"];
 
+        PBRClearCoatConfiguration.AddUniforms(uniforms);
+        PBRClearCoatConfiguration.AddSamplers(samplers);
+
         if (ImageProcessingConfiguration) {
             ImageProcessingConfiguration.PrepareUniforms(uniforms, defines);
             ImageProcessingConfiguration.PrepareSamplers(samplers, defines);
@@ -1369,6 +1391,8 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             defines.SPECULARAA = scene.getEngine().getCaps().standardDerivatives && this._enableSpecularAntiAliasing;
         }
 
+        this.clearCoat.prepareDefines(defines, scene);
+
         if (defines._areImageProcessingDirty && this._imageProcessingConfiguration) {
             this._imageProcessingConfiguration.prepareDefines(defines);
         }
@@ -1456,6 +1480,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         this._uniformBuffer.addUniform("vEmissiveColor", 3);
 
         this._uniformBuffer.addUniform("pointSize", 1);
+
+        PBRClearCoatConfiguration.PrepareUniformBuffer(this._uniformBuffer);
+
         this._uniformBuffer.create();
     }
 
@@ -1512,6 +1539,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
         let reflectionTexture: Nullable<BaseTexture> = null;
         if (mustRebind) {
+            var engine = scene.getEngine();
             this._uniformBuffer.bindToEffect(effect, "Material");
 
             this.bindViewProjection(effect);
@@ -1597,7 +1625,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                         }
                     }
 
-                    if (this._bumpTexture && scene.getEngine().getCaps().standardDerivatives && MaterialFlags.BumpTextureEnabled && !this._disableBumpMap) {
+                    if (this._bumpTexture && engine.getCaps().standardDerivatives && MaterialFlags.BumpTextureEnabled && !this._disableBumpMap) {
                         this._uniformBuffer.updateFloat3("vBumpInfos", this._bumpTexture.coordinatesIndex, this._bumpTexture.level, this._parallaxScaleBias);
                         MaterialHelper.BindTextureMatrix(this._bumpTexture, this._uniformBuffer, "bump");
 
@@ -1714,11 +1742,13 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                     }
                 }
 
-                if (this._bumpTexture && scene.getEngine().getCaps().standardDerivatives && MaterialFlags.BumpTextureEnabled && !this._disableBumpMap) {
+                if (this._bumpTexture && engine.getCaps().standardDerivatives && MaterialFlags.BumpTextureEnabled && !this._disableBumpMap) {
                     this._uniformBuffer.setTexture("bumpSampler", this._bumpTexture);
                 }
             }
 
+            this.clearCoat.bindForSubMesh(this._uniformBuffer, scene, engine, this._disableBumpMap, this.isFrozen, this._invertNormalMapX, this._invertNormalMapY);
+
             // Clip plane
             MaterialHelper.BindClipPlane(this._activeEffect, scene);
 
@@ -1812,6 +1842,8 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             results.push(this._refractionTexture);
         }
 
+        this.clearCoat.getAnimatables(results);
+
         return results;
     }
 
@@ -1845,6 +1877,111 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     }
 
     /**
+     * Returns an array of the actively used textures.
+     * @returns - Array of BaseTextures
+     */
+    public getActiveTextures(): BaseTexture[] {
+        var activeTextures = super.getActiveTextures();
+
+        if (this._albedoTexture) {
+            activeTextures.push(this._albedoTexture);
+        }
+
+        if (this._ambientTexture) {
+            activeTextures.push(this._ambientTexture);
+        }
+
+        if (this._opacityTexture) {
+            activeTextures.push(this._opacityTexture);
+        }
+
+        if (this._reflectionTexture) {
+            activeTextures.push(this._reflectionTexture);
+        }
+
+        if (this._emissiveTexture) {
+            activeTextures.push(this._emissiveTexture);
+        }
+
+        if (this._reflectivityTexture) {
+            activeTextures.push(this._reflectivityTexture);
+        }
+
+        if (this._metallicTexture) {
+            activeTextures.push(this._metallicTexture);
+        }
+
+        if (this._microSurfaceTexture) {
+            activeTextures.push(this._microSurfaceTexture);
+        }
+
+        if (this._bumpTexture) {
+            activeTextures.push(this._bumpTexture);
+        }
+
+        if (this._lightmapTexture) {
+            activeTextures.push(this._lightmapTexture);
+        }
+
+        if (this._refractionTexture) {
+            activeTextures.push(this._refractionTexture);
+        }
+
+        this.clearCoat.getActiveTextures(activeTextures);
+
+        return activeTextures;
+    }
+
+    /**
+     * Checks to see if a texture is used in the material.
+     * @param texture - Base texture to use.
+     * @returns - Boolean specifying if a texture is used in the material.
+     */
+    public hasTexture(texture: BaseTexture): boolean {
+        if (super.hasTexture(texture)) {
+            return true;
+        }
+
+        if (this._albedoTexture === texture) {
+            return true;
+        }
+
+        if (this._ambientTexture === texture) {
+            return true;
+        }
+
+        if (this._opacityTexture === texture) {
+            return true;
+        }
+
+        if (this._reflectionTexture === texture) {
+            return true;
+        }
+
+        if (this._reflectivityTexture === texture) {
+            return true;
+        }
+
+        if (this._metallicTexture === texture) {
+            return true;
+        }
+
+        if (this._microSurfaceTexture === texture) {
+            return true;
+        }
+
+        if (this._bumpTexture === texture) {
+            return true;
+        }
+
+        if (this._lightmapTexture === texture) {
+            return true;
+        }
+
+        return this.clearCoat.hasTexture(texture);
+    }
+
+    /**
      * Disposes the resources of the material.
      * @param forceDisposeEffect - Forces the disposal of effects.
      * @param forceDisposeTextures - Forces the disposal of all textures.
@@ -1896,6 +2033,8 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             }
         }
 
+        this.clearCoat.dispose(forceDisposeTextures);
+
         this._renderTargets.dispose();
 
         if (this._imageProcessingConfiguration && this._imageProcessingObserver) {

+ 0 - 41
src/Materials/PBR/pbrBaseSimpleMaterial.ts

@@ -125,47 +125,6 @@ export abstract class PBRBaseSimpleMaterial extends PBRBaseMaterial {
     public useLightmapAsShadowmap = false;
 
     /**
-     * Return the active textures of the material.
-     */
-    public getActiveTextures(): BaseTexture[] {
-        var activeTextures = super.getActiveTextures();
-
-        if (this.environmentTexture) {
-            activeTextures.push(this.environmentTexture);
-        }
-
-        if (this.normalTexture) {
-            activeTextures.push(this.normalTexture);
-        }
-
-        if (this.emissiveTexture) {
-            activeTextures.push(this.emissiveTexture);
-        }
-
-        if (this.occlusionTexture) {
-            activeTextures.push(this.occlusionTexture);
-        }
-
-        if (this.lightmapTexture) {
-            activeTextures.push(this.lightmapTexture);
-        }
-
-        return activeTextures;
-    }
-
-    public hasTexture(texture: BaseTexture): boolean {
-        if (super.hasTexture(texture)) {
-            return true;
-        }
-
-        if (this.lightmapTexture === texture) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
      * Instantiates a new PBRMaterial instance.
      *
      * @param name The material name

+ 327 - 0
src/Materials/PBR/pbrClearCoatConfiguration.ts

@@ -0,0 +1,327 @@
+import { Nullable } from "../../types";
+import { IAnimatable } from "../../Misc/tools";
+import { SerializationHelper, serialize, serializeAsTexture, expandToProperty } from "../../Misc/decorators";
+import { BaseTexture } from "../../Materials/Textures/baseTexture";
+import { EffectFallbacks } from "../../Materials/effect";
+import { MaterialFlags } from "../materialFlags";
+import { UniformBuffer } from "../../Materials/uniformBuffer";
+import { MaterialHelper } from "../../Materials/materialHelper";
+
+declare type Engine = import("../../Engines/engine").Engine;
+declare type Scene = import("../../scene").Scene;
+
+/**
+ * @hidden
+ */
+export interface IMaterialClearCoatDefines {
+    CLEARCOAT: boolean;
+    CLEARCOAT_TEXTURE: boolean;
+    CLEARCOAT_TEXTUREDIRECTUV: number;
+    CLEARCOAT_BUMP: boolean;
+    CLEARCOAT_BUMPDIRECTUV: number;
+
+    /** @hidden */
+    _areTexturesDirty: boolean;
+}
+
+/**
+ * Define the code related to the clear coat parameters of the pbr material.
+ */
+export class PBRClearCoatConfiguration {
+
+    /**
+     * Defines if the clear coat is enabled in the material.
+     */
+    @serialize()
+    public enable = false;
+
+    /**
+     * Defines the clear coat layer strength (between 0 and 1) it defaults to 1.
+     */
+    @serialize()
+    public intensity: number = 1;
+
+    /**
+     * Defines the clear coat layer roughness.
+     */
+    @serialize()
+    public roughness: number = 0;
+
+    @serializeAsTexture()
+    private _texture: Nullable<BaseTexture> = null;
+    /**
+     * Stores the clear coat values in a texture.
+     */
+    @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+    public texture: Nullable<BaseTexture> = null;
+
+    @serializeAsTexture()
+    private _bumpTexture: Nullable<BaseTexture> = null;
+    /**
+     * Define the clear coat specific bump texture.
+     */
+    @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+    public bumpTexture: Nullable<BaseTexture> = null;
+
+    /** @hidden */
+    private _internalMarkAllSubMeshesAsTexturesDirty: () => void;
+
+    /** @hidden */
+    public _markAllSubMeshesAsTexturesDirty(): void {
+        this._internalMarkAllSubMeshesAsTexturesDirty();
+    }
+
+    /**
+     * Instantiate a new istance of clear coat configuration.
+     * @param markAllSubMeshesAsTexturesDirty Callback to flag the material to dirty
+     */
+    constructor(markAllSubMeshesAsTexturesDirty: () => void) {
+        this._internalMarkAllSubMeshesAsTexturesDirty = markAllSubMeshesAsTexturesDirty;
+    }
+
+    /**
+     * Specifies that the submesh is ready to be used.
+     * @param defines defines the Base texture to use.
+     * @param scene defines the scene the material belongs to.
+     * @param engine defines the engine the material belongs to.
+     * @param disableBumpMap defines wether the material disables bump or not.
+     * @returns - boolean indicating that the submesh is ready or not.
+     */
+    public isReadyForSubMesh(defines: IMaterialClearCoatDefines, scene: Scene, engine: Engine, disableBumpMap: boolean): boolean {
+        if (defines._areTexturesDirty) {
+            if (scene.texturesEnabled) {
+                if (this._texture && MaterialFlags.ClearCoatTextureEnabled) {
+                    if (!this._texture.isReadyOrNotBlocking()) {
+                        return false;
+                    }
+                }
+
+                if (engine.getCaps().standardDerivatives && this._bumpTexture && MaterialFlags.ClearCoatBumpTextureEnabled && !disableBumpMap) {
+                    // Bump texture cannot be not blocking.
+                    if (!this._bumpTexture.isReady()) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Checks to see if a texture is used in the material.
+     * @param defines defines the Base texture to use.
+     * @param scene defines the scene to the material belongs to.
+     */
+    public prepareDefines(defines: IMaterialClearCoatDefines, scene: Scene): void {
+        if (this.enable) {
+            defines.CLEARCOAT = true;
+
+            if (defines._areTexturesDirty) {
+                if (scene.texturesEnabled) {
+                    if (this._texture && MaterialFlags.ClearCoatTextureEnabled) {
+                        MaterialHelper.PrepareDefinesForMergedUV(this._texture, defines, "CLEARCOAT_TEXTURE");
+                    } else {
+                        defines.CLEARCOAT_TEXTURE = false;
+                    }
+
+                    if (this._bumpTexture && MaterialFlags.ClearCoatBumpTextureEnabled) {
+                        MaterialHelper.PrepareDefinesForMergedUV(this._bumpTexture, defines, "CLEARCOAT_BUMP");
+                    } else {
+                        defines.CLEARCOAT_BUMP = false;
+                    }
+                }
+            }
+        }
+        else {
+            defines.CLEARCOAT = false;
+            defines.CLEARCOAT_TEXTURE = false;
+            defines.CLEARCOAT_BUMP = false;
+        }
+    }
+
+    /**
+     * Binds the material data.
+     * @param uniformBuffer defines the Uniform buffer to fill in.
+     * @param scene defines the scene the material belongs to.
+     * @param engine defines the engine the material belongs to.
+     * @param disableBumpMap defines wether the material disables bump or not.
+     * @param isFrozen defines wether the material is frozen or not.
+     * @param invertNormalMapX If sets to true, x component of normal map value will be inverted (x = 1.0 - x).
+     * @param invertNormalMapY If sets to true, y component of normal map value will be inverted (y = 1.0 - y).
+     */
+    public bindForSubMesh(uniformBuffer: UniformBuffer, scene: Scene, engine: Engine, disableBumpMap: boolean, isFrozen: boolean, invertNormalMapX: boolean, invertNormalMapY: boolean): void {
+        if (!uniformBuffer.useUbo || !isFrozen || !uniformBuffer.isSync) {
+            if (this._texture && MaterialFlags.ClearCoatTextureEnabled) {
+                uniformBuffer.updateFloat2("vClearCoatInfos", this._texture.coordinatesIndex, this._texture.level);
+                MaterialHelper.BindTextureMatrix(this._texture, uniformBuffer, "clearCoat");
+            }
+
+            if (this._bumpTexture && engine.getCaps().standardDerivatives && MaterialFlags.ClearCoatTextureEnabled && !disableBumpMap) {
+                uniformBuffer.updateFloat2("vClearCoatBumpInfos", this._bumpTexture.coordinatesIndex, 1.0 / this._bumpTexture.level);
+                MaterialHelper.BindTextureMatrix(this._bumpTexture, uniformBuffer, "clearCoatBump");
+
+                if (scene._mirroredCameraPosition) {
+                    uniformBuffer.updateFloat2("vClearCoatTangentSpaceParams", invertNormalMapX ? 1.0 : -1.0, invertNormalMapY ? 1.0 : -1.0);
+                } else {
+                    uniformBuffer.updateFloat2("vClearCoatTangentSpaceParams", invertNormalMapX ? -1.0 : 1.0, invertNormalMapY ? -1.0 : 1.0);
+                }
+            }
+
+            // Clear Coat
+            uniformBuffer.updateFloat2("vClearCoatParams", this.intensity, this.roughness);
+        }
+
+        // Textures
+        if (scene.texturesEnabled) {
+            if (this._texture && MaterialFlags.ClearCoatTextureEnabled) {
+                uniformBuffer.setTexture("clearCoatSampler", this._texture);
+            }
+
+            if (this._bumpTexture && engine.getCaps().standardDerivatives && MaterialFlags.ClearCoatBumpTextureEnabled && !disableBumpMap) {
+                uniformBuffer.setTexture("clearCoatBumpSampler", this._bumpTexture);
+            }
+        }
+    }
+
+    /**
+     * Checks to see if a texture is used in the material.
+     * @param texture - Base texture to use.
+     * @returns - Boolean specifying if a texture is used in the material.
+     */
+    public hasTexture(texture: BaseTexture): boolean {
+        if (this._texture === texture) {
+            return true;
+        }
+
+        if (this._bumpTexture === texture) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns an array of the actively used textures.
+     * @param activeTextures Array of BaseTextures
+     */
+    public getActiveTextures(activeTextures: BaseTexture[]): void {
+        if (this._texture) {
+            activeTextures.push(this._texture);
+        }
+
+        if (this._bumpTexture) {
+            activeTextures.push(this._bumpTexture);
+        }
+    }
+
+    /**
+     * Returns the animatable textures.
+     * @param animatables Array of animatable textures.
+     */
+    public getAnimatables(animatables: IAnimatable[]): void {
+        if (this._texture && this._texture.animations && this._texture.animations.length > 0) {
+            animatables.push(this._texture);
+        }
+
+        if (this._bumpTexture && this._bumpTexture.animations && this._bumpTexture.animations.length > 0) {
+            animatables.push(this._bumpTexture);
+        }
+    }
+
+    /**
+     * Disposes the resources of the material.
+     * @param forceDisposeTextures - Forces the disposal of all textures.
+     */
+    public dispose(forceDisposeTextures?: boolean): void {
+        if (forceDisposeTextures) {
+            if (this._texture) {
+                this._texture.dispose();
+            }
+
+            if (this._bumpTexture) {
+                this._bumpTexture.dispose();
+            }
+        }
+    }
+
+    /**
+    * Get the current class name of the texture useful for serialization or dynamic coding.
+    * @returns "PBRClearCoatConfiguration"
+    */
+   public getClassName(): string {
+        return "PBRClearCoatConfiguration";
+    }
+
+    /**
+     * Makes a duplicate of the current configuration into another one.
+     * @param clearCoatconfiguration define the config where to copy the info
+     */
+    public copyTo(clearCoatconfiguration: PBRClearCoatConfiguration): void {
+        SerializationHelper.Clone(() => clearCoatconfiguration, this);
+    }
+
+    /**
+     * Serializes this clear coat configuration.
+     * @returns - An object with the serialized config.
+     */
+    public serialize(): any {
+        return SerializationHelper.Serialize(this);
+    }
+
+    /**
+     * Parses a Clear Coat Configuration from a serialized object.
+     * @param source - Serialized object.
+     */
+    public parse(source: any): void {
+        SerializationHelper.Parse(() => this, source, null);
+    }
+
+    /**
+     * Add fallbacks to the effect fallbacks list.
+     * @param defines defines the Base texture to use.
+     * @param fallbacks defines the current fallback list.
+     * @param currentRank defines the current fallback rank.
+     * @returns the new fallback rank.
+     */
+    public static AddFallbacks(defines: IMaterialClearCoatDefines, fallbacks: EffectFallbacks, currentRank: number): number {
+        if (defines.CLEARCOAT_BUMP) {
+            fallbacks.addFallback(currentRank++, "CLEARCOAT_BUMP");
+        }
+        if (defines.CLEARCOAT) {
+            fallbacks.addFallback(currentRank++, "CLEARCOAT");
+        }
+        return currentRank;
+    }
+
+    /**
+     * Add the required uniforms to the current list.
+     * @param uniforms defines the current uniform list.
+     */
+    public static AddUniforms(uniforms: string[]): void {
+        uniforms.push("vClearCoatTangentSpaceParams", "vClearCoatParams",
+            "clearCoatMatrix", "clearCoatBumpMatrix",
+            "vClearCoatInfos", "vClearCoatBumpInfos");
+    }
+
+    /**
+     * Add the required samplers to the current list.
+     * @param samplers defines the current sampler list.
+     */
+    public static AddSamplers(samplers: string[]): void {
+        samplers.push("clearCoatSampler", "clearCoatBumpSampler");
+    }
+
+    /**
+     * Add the required uniforms to the current buffer.
+     * @param uniformBuffer defines the current uniform buffer.
+     */
+    public static PrepareUniformBuffer(uniformBuffer: UniformBuffer): void {
+        uniformBuffer.addUniform("vClearCoatParams", 2);
+        uniformBuffer.addUniform("vClearCoatInfos", 2);
+        uniformBuffer.addUniform("clearCoatMatrix", 16);
+        uniformBuffer.addUniform("vClearCoatBumpInfos", 2);
+        uniformBuffer.addUniform("vClearCoatTangentSpaceParams", 2);
+        uniformBuffer.addUniform("clearCoatBumpMatrix", 16);
+    }
+}

+ 10 - 108
src/Materials/PBR/pbrMaterial.ts

@@ -683,113 +683,6 @@ export class PBRMaterial extends PBRBaseMaterial {
     }
 
     /**
-     * Returns an array of the actively used textures.
-     * @returns - Array of BaseTextures
-     */
-    public getActiveTextures(): BaseTexture[] {
-        var activeTextures = super.getActiveTextures();
-
-        if (this._albedoTexture) {
-            activeTextures.push(this._albedoTexture);
-        }
-
-        if (this._ambientTexture) {
-            activeTextures.push(this._ambientTexture);
-        }
-
-        if (this._opacityTexture) {
-            activeTextures.push(this._opacityTexture);
-        }
-
-        if (this._reflectionTexture) {
-            activeTextures.push(this._reflectionTexture);
-        }
-
-        if (this._emissiveTexture) {
-            activeTextures.push(this._emissiveTexture);
-        }
-
-        if (this._reflectivityTexture) {
-            activeTextures.push(this._reflectivityTexture);
-        }
-
-        if (this._metallicTexture) {
-            activeTextures.push(this._metallicTexture);
-        }
-
-        if (this._microSurfaceTexture) {
-            activeTextures.push(this._microSurfaceTexture);
-        }
-
-        if (this._bumpTexture) {
-            activeTextures.push(this._bumpTexture);
-        }
-
-        if (this._lightmapTexture) {
-            activeTextures.push(this._lightmapTexture);
-        }
-
-        if (this._refractionTexture) {
-            activeTextures.push(this._refractionTexture);
-        }
-
-        return activeTextures;
-    }
-
-    /**
-     * Checks to see if a texture is used in the material.
-     * @param texture - Base texture to use.
-     * @returns - Boolean specifying if a texture is used in the material.
-     */
-    public hasTexture(texture: BaseTexture): boolean {
-        if (super.hasTexture(texture)) {
-            return true;
-        }
-
-        if (this._albedoTexture === texture) {
-            return true;
-        }
-
-        if (this._ambientTexture === texture) {
-            return true;
-        }
-
-        if (this._opacityTexture === texture) {
-            return true;
-        }
-
-        if (this._reflectionTexture === texture) {
-            return true;
-        }
-
-        if (this._reflectivityTexture === texture) {
-            return true;
-        }
-
-        if (this._metallicTexture === texture) {
-            return true;
-        }
-
-        if (this._microSurfaceTexture === texture) {
-            return true;
-        }
-
-        if (this._bumpTexture === texture) {
-            return true;
-        }
-
-        if (this._lightmapTexture === texture) {
-            return true;
-        }
-
-        if (this._refractionTexture === texture) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
      * Makes a duplicate of the current material.
      * @param name - name to use for the new material.
      */
@@ -799,6 +692,8 @@ export class PBRMaterial extends PBRBaseMaterial {
         clone.id = name;
         clone.name = name;
 
+        this.clearCoat.copyTo(clone.clearCoat);
+
         return clone;
     }
 
@@ -809,6 +704,9 @@ export class PBRMaterial extends PBRBaseMaterial {
     public serialize(): any {
         var serializationObject = SerializationHelper.Serialize(this);
         serializationObject.customType = "BABYLON.PBRMaterial";
+
+        serializationObject.clearCoat = this.clearCoat.serialize();
+
         return serializationObject;
     }
 
@@ -821,7 +719,11 @@ export class PBRMaterial extends PBRBaseMaterial {
      * @returns - PBRMaterial
      */
     public static Parse(source: any, scene: Scene, rootUrl: string): PBRMaterial {
-        return SerializationHelper.Parse(() => new PBRMaterial(source.name, scene), source, scene, rootUrl);
+        const material = SerializationHelper.Parse(() => new PBRMaterial(source.name, scene), source, scene, rootUrl);
+        if (source.clearCoat) {
+            material.clearCoat.parse(source.clearCoat);
+        }
+        return material;
     }
 }
 

+ 10 - 39
src/Materials/PBR/pbrMetallicRoughnessMaterial.ts

@@ -80,44 +80,6 @@ export class PBRMetallicRoughnessMaterial extends PBRBaseSimpleMaterial {
     }
 
     /**
-     * Return the active textures of the material.
-     */
-    public getActiveTextures(): BaseTexture[] {
-        var activeTextures = super.getActiveTextures();
-
-        if (this.baseTexture) {
-            activeTextures.push(this.baseTexture);
-        }
-
-        if (this.metallicRoughnessTexture) {
-            activeTextures.push(this.metallicRoughnessTexture);
-        }
-
-        return activeTextures;
-    }
-
-    /**
-     * Checks to see if a texture is used in the material.
-     * @param texture - Base texture to use.
-     * @returns - Boolean specifying if a texture is used in the material.
-     */
-    public hasTexture(texture: BaseTexture): boolean {
-        if (super.hasTexture(texture)) {
-            return true;
-        }
-
-        if (this.baseTexture === texture) {
-            return true;
-        }
-
-        if (this.metallicRoughnessTexture === texture) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
      * Makes a duplicate of the current material.
      * @param name - name to use for the new material.
      */
@@ -127,6 +89,8 @@ export class PBRMetallicRoughnessMaterial extends PBRBaseSimpleMaterial {
         clone.id = name;
         clone.name = name;
 
+        this.clearCoat.copyTo(clone.clearCoat);
+
         return clone;
     }
 
@@ -136,6 +100,9 @@ export class PBRMetallicRoughnessMaterial extends PBRBaseSimpleMaterial {
     public serialize(): any {
         var serializationObject = SerializationHelper.Serialize(this);
         serializationObject.customType = "BABYLON.PBRMetallicRoughnessMaterial";
+
+        serializationObject.clearCoat = this.clearCoat.serialize();
+
         return serializationObject;
     }
 
@@ -143,7 +110,11 @@ export class PBRMetallicRoughnessMaterial extends PBRBaseSimpleMaterial {
      * Parses a JSON object correponding to the serialize function.
      */
     public static Parse(source: any, scene: Scene, rootUrl: string): PBRMetallicRoughnessMaterial {
-        return SerializationHelper.Parse(() => new PBRMetallicRoughnessMaterial(source.name, scene), source, scene, rootUrl);
+        const material = SerializationHelper.Parse(() => new PBRMetallicRoughnessMaterial(source.name, scene), source, scene, rootUrl);
+        if (source.clearCoat) {
+            material.clearCoat.parse(source.clearCoat);
+        }
+        return material;
     }
 }
 

+ 10 - 39
src/Materials/PBR/pbrSpecularGlossinessMaterial.ts

@@ -70,44 +70,6 @@ export class PBRSpecularGlossinessMaterial extends PBRBaseSimpleMaterial {
     }
 
     /**
-     * Return the active textures of the material.
-     */
-    public getActiveTextures(): BaseTexture[] {
-        var activeTextures = super.getActiveTextures();
-
-        if (this.diffuseTexture) {
-            activeTextures.push(this.diffuseTexture);
-        }
-
-        if (this.specularGlossinessTexture) {
-            activeTextures.push(this.specularGlossinessTexture);
-        }
-
-        return activeTextures;
-    }
-
-    /**
-     * Checks to see if a texture is used in the material.
-     * @param texture - Base texture to use.
-     * @returns - Boolean specifying if a texture is used in the material.
-     */
-    public hasTexture(texture: BaseTexture): boolean {
-        if (super.hasTexture(texture)) {
-            return true;
-        }
-
-        if (this.diffuseTexture === texture) {
-            return true;
-        }
-
-        if (this.specularGlossinessTexture === texture) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
      * Makes a duplicate of the current material.
      * @param name - name to use for the new material.
      */
@@ -117,6 +79,8 @@ export class PBRSpecularGlossinessMaterial extends PBRBaseSimpleMaterial {
         clone.id = name;
         clone.name = name;
 
+        this.clearCoat.copyTo(clone.clearCoat);
+
         return clone;
     }
 
@@ -126,6 +90,9 @@ export class PBRSpecularGlossinessMaterial extends PBRBaseSimpleMaterial {
     public serialize(): any {
         var serializationObject = SerializationHelper.Serialize(this);
         serializationObject.customType = "BABYLON.PBRSpecularGlossinessMaterial";
+
+        serializationObject.clearCoat = this.clearCoat.serialize();
+
         return serializationObject;
     }
 
@@ -133,7 +100,11 @@ export class PBRSpecularGlossinessMaterial extends PBRBaseSimpleMaterial {
      * Parses a JSON object correponding to the serialize function.
      */
     public static Parse(source: any, scene: Scene, rootUrl: string): PBRSpecularGlossinessMaterial {
-        return SerializationHelper.Parse(() => new PBRSpecularGlossinessMaterial(source.name, scene), source, scene, rootUrl);
+        const material = SerializationHelper.Parse(() => new PBRSpecularGlossinessMaterial(source.name, scene), source, scene, rootUrl);
+        if (source.clearCoat) {
+            material.clearCoat.parse(source.clearCoat);
+        }
+        return material;
     }
 }
 

+ 32 - 0
src/Materials/materialFlags.ts

@@ -181,4 +181,36 @@ export class MaterialFlags {
         this._FresnelEnabled = value;
         Engine.MarkAllMaterialsAsDirty(Constants.MATERIAL_FresnelDirtyFlag);
     }
+
+    private static _ClearCoatTextureEnabled = true;
+    /**
+     * Are clear coat textures enabled in the application.
+     */
+    public static get ClearCoatTextureEnabled(): boolean {
+        return this._ClearCoatTextureEnabled;
+    }
+    public static set ClearCoatTextureEnabled(value: boolean) {
+        if (this._ClearCoatTextureEnabled === value) {
+            return;
+        }
+
+        this._ClearCoatTextureEnabled = value;
+        Engine.MarkAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+    }
+
+    private static _ClearCoatBumpTextureEnabled = true;
+    /**
+     * Are clear coat bump textures enabled in the application.
+     */
+    public static get ClearCoatBumpTextureEnabled(): boolean {
+        return this._ClearCoatBumpTextureEnabled;
+    }
+    public static set ClearCoatBumpTextureEnabled(value: boolean) {
+        if (this._ClearCoatBumpTextureEnabled === value) {
+            return;
+        }
+
+        this._ClearCoatBumpTextureEnabled = value;
+        Engine.MarkAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+    }
 }

+ 6 - 6
src/Shaders/ShadersInclude/bumpFragment.fx

@@ -25,10 +25,10 @@
 #endif
 
 #ifdef BUMP
-#ifdef OBJECTSPACE_NORMALMAP
-	normalW = normalize(texture2D(bumpSampler, vBumpUV).xyz  * 2.0 - 1.0);
-	normalW = normalize(mat3(normalMatrix) * normalW);	
-#else
-	normalW = perturbNormal(TBN, vBumpUV + uvOffset);
-#endif
+	#ifdef OBJECTSPACE_NORMALMAP
+		normalW = normalize(texture2D(bumpSampler, vBumpUV).xyz  * 2.0 - 1.0);
+		normalW = normalize(mat3(normalMatrix) * normalW);	
+	#else
+		normalW = perturbNormal(TBN, vBumpUV + uvOffset);
+	#endif
 #endif

+ 87 - 73
src/Shaders/ShadersInclude/bumpFragmentFunctions.fx

@@ -1,22 +1,26 @@
-#ifdef BUMP
-	#if BUMPDIRECTUV == 1
-		#define vBumpUV vMainUV1
-	#elif BUMPDIRECTUV == 2
-		#define vBumpUV vMainUV2
-	#else
-		varying vec2 vBumpUV;
+#if defined(BUMP) || defined(CLEARCOAT_BUMP)
+	#if defined(TANGENT) && defined(NORMAL) 
+		varying mat3 vTBN;
 	#endif
-	uniform sampler2D bumpSampler;
-#if defined(TANGENT) && defined(NORMAL) 
-	varying mat3 vTBN;
-#endif
 
-#ifdef OBJECTSPACE_NORMALMAP
-uniform mat4 normalMatrix;
-#endif
+	#ifdef OBJECTSPACE_NORMALMAP
+		uniform mat4 normalMatrix;
+	#endif
+
+	vec3 perturbNormal(mat3 cotangentFrame, vec2 uv, sampler2D textureSampler, float scale)
+	{
+		vec3 map = texture2D(textureSampler, uv).xyz;
+		map = map * 2.0 - 1.0;
+
+		#ifdef NORMALXYSCALE
+			map = normalize(map * vec3(scale, scale, 1.0));
+		#endif
+
+		return normalize(cotangentFrame * map);
+	}
 
 	// Thanks to http://www.thetenthplanet.de/archives/1180
-	mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv)
+	mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv, vec2 tangentSpaceParams)
 	{
 		// flip the uv for the backface
 		uv = gl_FrontFacing ? uv : -uv;
@@ -34,83 +38,93 @@ uniform mat4 normalMatrix;
 		vec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y;
 
 		// invert the tangent/bitangent if requested
-		tangent *= vTangentSpaceParams.x;
-		bitangent *= vTangentSpaceParams.y;
+		tangent *= tangentSpaceParams.x;
+		bitangent *= tangentSpaceParams.y;
 
 		// construct a scale-invariant frame
 		float invmax = inversesqrt(max(dot(tangent, tangent), dot(bitangent, bitangent)));
 		return mat3(tangent * invmax, bitangent * invmax, normal);
 	}
+#endif
+
+#ifdef BUMP
+	#if BUMPDIRECTUV == 1
+		#define vBumpUV vMainUV1
+	#elif BUMPDIRECTUV == 2
+		#define vBumpUV vMainUV2
+	#else
+		varying vec2 vBumpUV;
+	#endif
+	uniform sampler2D bumpSampler;
 
 	vec3 perturbNormal(mat3 cotangentFrame, vec2 uv)
 	{
-		vec3 map = texture2D(bumpSampler, uv).xyz;
-		map = map * 2.0 - 1.0;
-
-		#ifdef NORMALXYSCALE
-			map = normalize(map * vec3(vBumpInfos.y, vBumpInfos.y, 1.0));
-		#endif
+		return perturbNormal(cotangentFrame, uv, bumpSampler, vBumpInfos.y);
+	}
 
-		return normalize(cotangentFrame * map);
+	// Thanks to http://www.thetenthplanet.de/archives/1180
+	mat3 cotangent_frame(vec3 normal, vec3 p, vec2 uv)
+	{
+		return cotangent_frame(normal, p, uv, vTangentSpaceParams);
 	}
+#endif
+
+#if defined(BUMP) && defined(PARALLAX)
+	const float minSamples = 4.;
+	const float maxSamples = 15.;
+	const int iMaxSamples = 15;
 
-	#ifdef PARALLAX
-		const float minSamples = 4.;
-		const float maxSamples = 15.;
-		const int iMaxSamples = 15;
+	// http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/a-closer-look-at-parallax-occlusion-mapping-r3262
+	vec2 parallaxOcclusion(vec3 vViewDirCoT, vec3 vNormalCoT, vec2 texCoord, float parallaxScale) {
 
-		// http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/a-closer-look-at-parallax-occlusion-mapping-r3262
-		vec2 parallaxOcclusion(vec3 vViewDirCoT, vec3 vNormalCoT, vec2 texCoord, float parallaxScale) {
+		float parallaxLimit = length(vViewDirCoT.xy) / vViewDirCoT.z;
+		parallaxLimit *= parallaxScale;
+		vec2 vOffsetDir = normalize(vViewDirCoT.xy);
+		vec2 vMaxOffset = vOffsetDir * parallaxLimit;
+		float numSamples = maxSamples + (dot(vViewDirCoT, vNormalCoT) * (minSamples - maxSamples));
+		float stepSize = 1.0 / numSamples;
 
-			float parallaxLimit = length(vViewDirCoT.xy) / vViewDirCoT.z;
-			parallaxLimit *= parallaxScale;
-			vec2 vOffsetDir = normalize(vViewDirCoT.xy);
-			vec2 vMaxOffset = vOffsetDir * parallaxLimit;
-			float numSamples = maxSamples + (dot(vViewDirCoT, vNormalCoT) * (minSamples - maxSamples));
-			float stepSize = 1.0 / numSamples;
+		// Initialize the starting view ray height and the texture offsets.
+		float currRayHeight = 1.0;
+		vec2 vCurrOffset = vec2(0, 0);
+		vec2 vLastOffset = vec2(0, 0);
 
-			// Initialize the starting view ray height and the texture offsets.
-			float currRayHeight = 1.0;
-			vec2 vCurrOffset = vec2(0, 0);
-			vec2 vLastOffset = vec2(0, 0);
+		float lastSampledHeight = 1.0;
+		float currSampledHeight = 1.0;
 
-			float lastSampledHeight = 1.0;
-			float currSampledHeight = 1.0;
+		for (int i = 0; i < iMaxSamples; i++)
+		{
+			currSampledHeight = texture2D(bumpSampler, vBumpUV + vCurrOffset).w;
 
-			for (int i = 0; i < iMaxSamples; i++)
+			// Test if the view ray has intersected the surface.
+			if (currSampledHeight > currRayHeight)
 			{
-				currSampledHeight = texture2D(bumpSampler, vBumpUV + vCurrOffset).w;
-
-				// Test if the view ray has intersected the surface.
-				if (currSampledHeight > currRayHeight)
-				{
-					float delta1 = currSampledHeight - currRayHeight;
-					float delta2 = (currRayHeight + stepSize) - lastSampledHeight;
-					float ratio = delta1 / (delta1 + delta2);
-					vCurrOffset = (ratio)* vLastOffset + (1.0 - ratio) * vCurrOffset;
-
-					// Force the exit of the loop
-					break;
-				}
-				else
-				{
-					currRayHeight -= stepSize;
-					vLastOffset = vCurrOffset;
-					vCurrOffset += stepSize * vMaxOffset;
-
-					lastSampledHeight = currSampledHeight;
-				}
+				float delta1 = currSampledHeight - currRayHeight;
+				float delta2 = (currRayHeight + stepSize) - lastSampledHeight;
+				float ratio = delta1 / (delta1 + delta2);
+				vCurrOffset = (ratio)* vLastOffset + (1.0 - ratio) * vCurrOffset;
+
+				// Force the exit of the loop
+				break;
 			}
+			else
+			{
+				currRayHeight -= stepSize;
+				vLastOffset = vCurrOffset;
+				vCurrOffset += stepSize * vMaxOffset;
 
-			return vCurrOffset;
+				lastSampledHeight = currSampledHeight;
+			}
 		}
 
-		vec2 parallaxOffset(vec3 viewDir, float heightScale)
-		{
-			// calculate amount of offset for Parallax Mapping With Offset Limiting
-			float height = texture2D(bumpSampler, vBumpUV).w;
-			vec2 texCoordOffset = heightScale * viewDir.xy * height;
-			return -texCoordOffset;
-		}
-	#endif
+		return vCurrOffset;
+	}
+
+	vec2 parallaxOffset(vec3 viewDir, float heightScale)
+	{
+		// calculate amount of offset for Parallax Mapping With Offset Limiting
+		float height = texture2D(bumpSampler, vBumpUV).w;
+		vec2 texCoordOffset = heightScale * viewDir.xy * height;
+		return -texCoordOffset;
+	}
 #endif

+ 1 - 1
src/Shaders/ShadersInclude/bumpVertex.fx

@@ -1,4 +1,4 @@
-#if defined(BUMP) || defined(PARALLAX)
+#if defined(BUMP) || defined(PARALLAX) || defined(CLEARCOAT_BUMP)
 	#if defined(TANGENT) && defined(NORMAL)
 		vec3 tbnNormal = normalize(normalUpdated);
 		vec3 tbnTangent = normalize(tangentUpdated.xyz);

+ 1 - 1
src/Shaders/ShadersInclude/bumpVertexDeclaration.fx

@@ -1,4 +1,4 @@
-#if defined(BUMP) || defined(PARALLAX)
+#if defined(BUMP) || defined(PARALLAX) || defined(CLEARCOAT_BUMP)
 	#if defined(TANGENT) && defined(NORMAL) 
 		varying mat3 vTBN;
 	#endif

+ 75 - 23
src/Shaders/ShadersInclude/lightFragment.fx

@@ -3,42 +3,84 @@
         //No light calculation
     #else
         #ifdef PBR
+            // Compute Pre Lighting infos
             #ifdef SPOTLIGHT{X}
-                spotInfo = computeSpotLightingInfo(light{X}.vLightData);
+                preInfo = computePointAndSpotPreLightingInfo(light{X}.vLightData, viewDirectionW, normalW);
+            #elif defined(POINTLIGHT{X})
+                preInfo = computePointAndSpotPreLightingInfo(light{X}.vLightData, viewDirectionW, normalW);
+            #elif defined(HEMILIGHT{X})
+                preInfo = computeHemisphericPreLightingInfo(light{X}.vLightData, viewDirectionW, normalW);
+            #elif defined(DIRLIGHT{X})
+                preInfo = computeDirectionalPreLightingInfo(light{X}.vLightData, viewDirectionW, normalW);
+            #endif
+
+            preInfo.NdotV = NdotV;
 
+            // Compute Attenuation infos
+            #ifdef SPOTLIGHT{X}
                 #ifdef LIGHT_FALLOFF_GLTF{X}
-                    spotInfo.attenuation = computeDistanceLightFalloff_GLTF(spotInfo.lightDistanceSquared, light{X}.vLightFalloff.y);
-                    spotInfo.attenuation *= computeDirectionalLightFalloff_GLTF(light{X}.vLightDirection.xyz, spotInfo.directionToLightCenterW, light{X}.vLightFalloff.z, light{X}.vLightFalloff.w);
+                    preInfo.attenuation = computeDistanceLightFalloff_GLTF(preInfo.lightDistanceSquared, light{X}.vLightFalloff.y);
+                    preInfo.attenuation *= computeDirectionalLightFalloff_GLTF(light{X}.vLightDirection.xyz, preInfo.L, light{X}.vLightFalloff.z, light{X}.vLightFalloff.w);
                 #elif defined(LIGHT_FALLOFF_PHYSICAL{X})
-                    spotInfo.attenuation = computeDistanceLightFalloff_Physical(spotInfo.lightDistanceSquared);
-                    spotInfo.attenuation *= computeDirectionalLightFalloff_Physical(light{X}.vLightDirection.xyz, spotInfo.directionToLightCenterW, light{X}.vLightDirection.w);
+                    preInfo.attenuation = computeDistanceLightFalloff_Physical(preInfo.lightDistanceSquared);
+                    preInfo.attenuation *= computeDirectionalLightFalloff_Physical(light{X}.vLightDirection.xyz, preInfo.L, light{X}.vLightDirection.w);
                 #elif defined(LIGHT_FALLOFF_STANDARD{X})
-                    spotInfo.attenuation = computeDistanceLightFalloff_Standard(spotInfo.lightOffset, light{X}.vLightFalloff.x);
-                    spotInfo.attenuation *= computeDirectionalLightFalloff_Standard(light{X}.vLightDirection.xyz, spotInfo.directionToLightCenterW, light{X}.vLightDirection.w, light{X}.vLightData.w);
+                    preInfo.attenuation = computeDistanceLightFalloff_Standard(preInfo.lightOffset, light{X}.vLightFalloff.x);
+                    preInfo.attenuation *= computeDirectionalLightFalloff_Standard(light{X}.vLightDirection.xyz, preInfo.L, light{X}.vLightDirection.w, light{X}.vLightData.w);
                 #else
-                    spotInfo.attenuation = computeDistanceLightFalloff(spotInfo.lightOffset, spotInfo.lightDistanceSquared, light{X}.vLightFalloff.x, light{X}.vLightFalloff.y);
-                    spotInfo.attenuation *= computeDirectionalLightFalloff(light{X}.vLightDirection.xyz, spotInfo.directionToLightCenterW, light{X}.vLightDirection.w, light{X}.vLightData.w, light{X}.vLightFalloff.z, light{X}.vLightFalloff.w);
+                    preInfo.attenuation = computeDistanceLightFalloff(preInfo.lightOffset, preInfo.lightDistanceSquared, light{X}.vLightFalloff.x, light{X}.vLightFalloff.y);
+                    preInfo.attenuation *= computeDirectionalLightFalloff(light{X}.vLightDirection.xyz, preInfo.L, light{X}.vLightDirection.w, light{X}.vLightData.w, light{X}.vLightFalloff.z, light{X}.vLightFalloff.w);
                 #endif
-
-                info = computeSpotLighting(spotInfo, viewDirectionW, normalW, light{X}.vLightDirection, light{X}.vLightDiffuse.rgb, light{X}.vLightDiffuse.a, roughness, NdotV, specularEnvironmentR0, specularEnvironmentR90, geometricRoughnessFactor, NdotL);
             #elif defined(POINTLIGHT{X})
-                pointInfo = computePointLightingInfo(light{X}.vLightData);
-
                 #ifdef LIGHT_FALLOFF_GLTF{X}
-                    pointInfo.attenuation = computeDistanceLightFalloff_GLTF(pointInfo.lightDistanceSquared, light{X}.vLightFalloff.y);
+                    preInfo.attenuation = computeDistanceLightFalloff_GLTF(preInfo.lightDistanceSquared, light{X}.vLightFalloff.y);
                 #elif defined(LIGHT_FALLOFF_PHYSICAL{X})
-                    pointInfo.attenuation = computeDistanceLightFalloff_Physical(pointInfo.lightDistanceSquared);
+                    preInfo.attenuation = computeDistanceLightFalloff_Physical(preInfo.lightDistanceSquared);
                 #elif defined(LIGHT_FALLOFF_STANDARD{X})
-                    pointInfo.attenuation = computeDistanceLightFalloff_Standard(pointInfo.lightOffset, light{X}.vLightFalloff.x);
+                    preInfo.attenuation = computeDistanceLightFalloff_Standard(preInfo.lightOffset, light{X}.vLightFalloff.x);
                 #else
-                    pointInfo.attenuation = computeDistanceLightFalloff(pointInfo.lightOffset, pointInfo.lightDistanceSquared, light{X}.vLightFalloff.x, light{X}.vLightFalloff.y);
+                    preInfo.attenuation = computeDistanceLightFalloff(preInfo.lightOffset, preInfo.lightDistanceSquared, light{X}.vLightFalloff.x, light{X}.vLightFalloff.y);
+                #endif
+            #else
+                preInfo.attenuation = 1.0;
+            #endif
+
+            // Simulates Light radius for diffuse and spec term
+            // clear coat is using a dedicated roughness
+            #ifdef HEMILIGHT{X}
+                preInfo.roughness = roughness;
+            #else
+                preInfo.roughness = adjustRoughnessFromLightProperties(roughness, light{X}.vLightDiffuse.a, preInfo.lightDistance);
+            #endif
+
+            // Diffuse contribution
+            #ifdef HEMILIGHT{X}
+                info.diffuse = computeHemisphericDiffuseLighting(preInfo, light{X}.vLightDiffuse.rgb, light{X}.vLightGround);
+            #else
+                info.diffuse = computeDiffuseLighting(preInfo, light{X}.vLightDiffuse.rgb);
+            #endif
+
+            // Specular contribution
+            #ifdef SPECULARTERM
+                info.specular = computeSpecularLighting(preInfo, normalW, specularEnvironmentR0, specularEnvironmentR90, AARoughnessFactors.x, light{X}.vLightDiffuse.rgb);
+            #endif
+
+            // Clear Coat contribution
+            #ifdef CLEARCOAT
+                // Simulates Light radius
+                #ifdef HEMILIGHT{X}
+                    preInfo.roughness = clearCoatRoughness;
+                #else
+                    preInfo.roughness = adjustRoughnessFromLightProperties(clearCoatRoughness, light{X}.vLightDiffuse.a, preInfo.lightDistance);
+                #endif
+
+                info.clearCoat = computeClearCoatLighting(preInfo, clearCoatNormalW, clearCoatAARoughnessFactors.x, clearCoatIntensity, light{X}.vLightDiffuse.rgb);
+
+                // Apply energy conservation on diffuse and specular term.
+                info.diffuse *= info.clearCoat.w;
+                #ifdef SPECULARTERM
+                    info.specular *= info.clearCoat.w * info.clearCoat.w;
                 #endif
-                
-                info = computePointLighting(pointInfo, viewDirectionW, normalW, light{X}.vLightDiffuse.rgb, light{X}.vLightDiffuse.a, roughness, NdotV, specularEnvironmentR0, specularEnvironmentR90, geometricRoughnessFactor, NdotL);
-            #elif defined(HEMILIGHT{X})
-                info = computeHemisphericLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular, light{X}.vLightGround, roughness, NdotV, specularEnvironmentR0, specularEnvironmentR90, geometricRoughnessFactor, NdotL);
-            #elif defined(DIRLIGHT{X})
-                info = computeDirectionalLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular, light{X}.vLightDiffuse.a, roughness, NdotV, specularEnvironmentR0, specularEnvironmentR90, geometricRoughnessFactor, NdotL);
             #endif
         #else
             #ifdef SPOTLIGHT{X}
@@ -49,10 +91,12 @@
                 info = computeLighting(viewDirectionW, normalW, light{X}.vLightData, light{X}.vLightDiffuse.rgb, light{X}.vLightSpecular, light{X}.vLightDiffuse.a, glossiness);
             #endif
         #endif
+
         #ifdef PROJECTEDLIGHTTEXTURE{X}
             info.diffuse *= computeProjectionTextureDiffuseLighting(projectionLightSampler{X}, textureProjectionMatrix{X});
         #endif
     #endif
+
     #ifdef SHADOW{X}
         #ifdef SHADOWCLOSEESM{X}
             #if defined(SHADOWCUBE{X})
@@ -120,11 +164,19 @@
                     specularBase += info.specular * shadow * lightmapColor;
                 #endif
             #endif
+            #ifdef CLEARCOAT
+                #ifndef LIGHTMAPNOSPECULAR{X}
+                    clearCoatBase += info.clearCoat.rgb * shadow * lightmapColor;
+                #endif
+            #endif
         #else
             diffuseBase += info.diffuse * shadow;
             #ifdef SPECULARTERM
                 specularBase += info.specular * shadow;
             #endif
+            #ifdef CLEARCOAT
+                clearCoatBase += info.clearCoat.rgb * shadow;
+            #endif
         #endif
     #endif
 #endif

+ 90 - 0
src/Shaders/ShadersInclude/pbrFalloffLightingFunctions.fx

@@ -0,0 +1,90 @@
+float computeDistanceLightFalloff_Standard(vec3 lightOffset, float range)
+{
+    return max(0., 1.0 - length(lightOffset) / range);
+}
+
+float computeDistanceLightFalloff_Physical(float lightDistanceSquared)
+{
+    return 1.0 / ((lightDistanceSquared + 0.001));
+}
+
+float computeDistanceLightFalloff_GLTF(float lightDistanceSquared, float inverseSquaredRange)
+{
+    const float minDistanceSquared = 0.01*0.01;
+    float lightDistanceFalloff = 1.0 / (max(lightDistanceSquared, minDistanceSquared));
+
+    float factor = lightDistanceSquared * inverseSquaredRange;
+    float attenuation = clamp(1.0 - factor * factor, 0., 1.);
+    attenuation *= attenuation;
+
+    // Smooth attenuation of the falloff defined by the range.
+    lightDistanceFalloff *= attenuation;
+    
+    return lightDistanceFalloff;
+}
+
+float computeDistanceLightFalloff(vec3 lightOffset, float lightDistanceSquared, float range, float inverseSquaredRange)
+{
+    #ifdef USEPHYSICALLIGHTFALLOFF
+        return computeDistanceLightFalloff_Physical(lightDistanceSquared);
+    #elif defined(USEGLTFLIGHTFALLOFF)
+        return computeDistanceLightFalloff_GLTF(lightDistanceSquared, inverseSquaredRange);
+    #else
+        return computeDistanceLightFalloff_Standard(lightOffset, range);
+    #endif
+}
+
+float computeDirectionalLightFalloff_Standard(vec3 lightDirection, vec3 directionToLightCenterW, float cosHalfAngle, float exponent)
+{
+    float falloff = 0.0;
+
+    float cosAngle = max(0.000000000000001, dot(-lightDirection, directionToLightCenterW));
+    if (cosAngle >= cosHalfAngle)
+    {
+        falloff = max(0., pow(cosAngle, exponent));
+    }
+    
+    return falloff;
+}
+
+float computeDirectionalLightFalloff_Physical(vec3 lightDirection, vec3 directionToLightCenterW, float cosHalfAngle)
+{
+    const float kMinusLog2ConeAngleIntensityRatio = 6.64385618977; // -log2(0.01)
+
+    // Calculate a Spherical Gaussian (von Mises-Fisher distribution, not angle-based Gaussian) such that the peak is in the light direction,
+    // and the value at the nominal cone angle is 1% of the peak. Because we want the distribution to decay from unity (100%)
+    // at the peak direction (dot product = 1) down to 1% at the nominal cone cutoff (dot product = cosAngle) 
+    // the falloff rate expressed in terms of the base-two dot product is therefore -log2(ConeAngleIntensityRatio) / (1.0 - cosAngle).
+    // Note that the distribution is unnormalised in that peak density is unity, rather than the total energy is unity.
+    float concentrationKappa = kMinusLog2ConeAngleIntensityRatio / (1.0 - cosHalfAngle);
+
+    // Evaluate spherical gaussian for light directional falloff for spot light type (note: spot directional falloff; 
+    // not directional light type)
+    vec4 lightDirectionSpreadSG = vec4(-lightDirection * concentrationKappa, -concentrationKappa);
+    float falloff = exp2(dot(vec4(directionToLightCenterW, 1.0), lightDirectionSpreadSG));
+    return falloff;
+}
+
+float computeDirectionalLightFalloff_GLTF(vec3 lightDirection, vec3 directionToLightCenterW, float lightAngleScale, float lightAngleOffset)
+{
+    // On the CPU
+    // float lightAngleScale = 1.0 f / max (0.001f, ( cosInner - cosOuter ));
+    // float lightAngleOffset = -cosOuter * angleScale;
+
+    float cd = dot(-lightDirection, directionToLightCenterW);
+    float falloff = clamp(cd * lightAngleScale + lightAngleOffset, 0., 1.);
+    // smooth the transition
+    falloff *= falloff;
+    return falloff;
+}
+
+float computeDirectionalLightFalloff(vec3 lightDirection, vec3 directionToLightCenterW, float cosHalfAngle, float exponent, float lightAngleScale, float lightAngleOffset)
+{
+    #ifdef USEPHYSICALLIGHTFALLOFF
+        return computeDirectionalLightFalloff_Physical(lightDirection, directionToLightCenterW, cosHalfAngle);
+    #elif defined(USEGLTFLIGHTFALLOFF)
+        return computeDirectionalLightFalloff_GLTF(lightDirection, directionToLightCenterW, lightAngleScale, lightAngleOffset);
+    #else
+        return computeDirectionalLightFalloff_Standard(lightDirection, directionToLightCenterW, cosHalfAngle, exponent);
+    #endif
+}

+ 16 - 0
src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx

@@ -63,4 +63,20 @@ uniform mat4 view;
 	    uniform vec3 vReflectionPosition;
 	    uniform vec3 vReflectionSize; 
     #endif
+#endif
+
+// Clear Coat
+#ifdef CLEARCOAT
+    uniform vec2 vClearCoatParams;
+
+    #ifdef CLEARCOAT_TEXTURE
+        uniform vec2 vClearCoatInfos;
+        uniform mat4 clearCoatMatrix;
+    #endif
+
+    #ifdef CLEARCOAT_BUMP
+        uniform vec2 vClearCoatBumpInfos;
+        uniform vec2 vClearCoatTangentSpaceParams;
+        uniform mat4 clearCoatBumpMatrix;
+    #endif
 #endif

+ 100 - 26
src/Shaders/ShadersInclude/pbrFunctions.fx

@@ -2,10 +2,6 @@
 #define RECIPROCAL_PI2 0.15915494
 #define FRESNEL_MAXIMUM_ON_ROUGH 0.25
 
-// PBR CUSTOM CONSTANTS
-const float kRougnhessToAlphaScale = 0.1;
-const float kRougnhessToAlphaOffset = 0.29248125;
-
 float convertRoughnessToAverageSlope(float roughness)
 {
     // Calculate AlphaG as square of roughness; add epsilon to avoid numerical issues
@@ -14,16 +10,70 @@ float convertRoughnessToAverageSlope(float roughness)
     return alphaG;
 }
 
+vec2 getAARoughnessFactors(vec3 normalVector) {
+    #ifdef SPECULARAA
+        vec3 nDfdx = dFdx(normalVector.xyz);
+        vec3 nDfdy = dFdy(normalVector.xyz);
+        float slopeSquare = max(dot(nDfdx, nDfdx), dot(nDfdy, nDfdy));
+
+        // Vive analytical lights roughness factor.
+        float geometricRoughnessFactor = pow(clamp(slopeSquare , 0., 1.), 0.333);
+
+        // BJS factor.
+        float geometricAlphaGFactor = sqrt(slopeSquare);
+        // Adapt linear roughness (alphaG) to geometric curvature of the current pixel.
+        // 75% accounts a bit for the bigger tail linked to Gaussian Filtering.
+        geometricAlphaGFactor *= 0.75;
+
+        return vec2(geometricRoughnessFactor, geometricAlphaGFactor);
+    #else
+        return vec2(0.);
+    #endif
+}
+
 // From Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007
-float smithVisibilityG1_TrowbridgeReitzGGX(float dot, float alphaG)
+// Kepp for references
+// float smithVisibilityG1_TrowbridgeReitzGGX(float dot, float alphaG)
+// {
+//     float tanSquared = (1.0 - dot * dot) / (dot * dot);
+//     return 2.0 / (1.0 + sqrt(1.0 + alphaG * alphaG * tanSquared));
+// }
+
+// float smithVisibility_TrowbridgeReitzGGX_Walter(float NdotL, float NdotV, float alphaG)
+// {
+//     float visibility = smithVisibilityG1_TrowbridgeReitzGGX(NdotL, alphaG) * smithVisibilityG1_TrowbridgeReitzGGX(NdotV, alphaG);
+//     visibility /= (4.0 * NdotL * NdotV); // Cook Torance Denominator  integrated in visibility to avoid issues when visibility function changes.
+//     return visibility;
+// }
+
+// From smithVisibilityG1_TrowbridgeReitzGGX * dot / dot to cancel the cook
+// torrance denominator :-)
+float smithVisibilityG1_TrowbridgeReitzGGXFast(float dot, float alphaG)
 {
-    float tanSquared = (1.0 - dot * dot) / (dot * dot);
-    return 2.0 / (1.0 + sqrt(1.0 + alphaG * alphaG * tanSquared));
+    float alphaSquared = alphaG * alphaG;
+    return 1.0 / (dot + sqrt(alphaSquared + (1.0 - alphaSquared) * dot * dot));
 }
 
-float smithVisibilityG_TrowbridgeReitzGGX_Walter(float NdotL, float NdotV, float alphaG)
+// From smithVisibilityG1_TrowbridgeReitzGGXFast
+// Appply simplification as all squared root terms are below 1 and squared
+// Ready to be used
+// float smithVisibilityG1_TrowbridgeReitzGGXMobile(float dot, float alphaG)
+// {
+//     return 1.0 / (dot + alpha + (1.0 - alpha) * dot ));
+// }
+
+float smithVisibility_TrowbridgeReitzGGXFast(float NdotL, float NdotV, float alphaG)
 {
-    return smithVisibilityG1_TrowbridgeReitzGGX(NdotL, alphaG) * smithVisibilityG1_TrowbridgeReitzGGX(NdotV, alphaG);
+    float visibility = smithVisibilityG1_TrowbridgeReitzGGXFast(NdotL, alphaG) * smithVisibilityG1_TrowbridgeReitzGGXFast(NdotV, alphaG);
+    // No Cook Torance Denominator as it is canceled out in the previous form
+    return visibility;
+}
+
+float kelemenVisibility(float VdotH) {
+    // Simplified form integration the cook torrance denminator.
+    // Expanded is nl * nv / vh2 which factor with 1 / (4 * nl * nv)
+    // giving 1 / (4 * vh2))
+    return 0.25 / (VdotH * VdotH); 
 }
 
 // Trowbridge-Reitz (GGX)
@@ -38,9 +88,14 @@ float normalDistributionFunction_TrowbridgeReitzGGX(float NdotH, float alphaG)
     return a2 / (PI * d * d);
 }
 
-vec3 fresnelSchlickGGX(float VdotH, vec3 reflectance0, vec3 reflectance90)
+vec3 fresnelSchlickGGXVec3(float VdotH, vec3 reflectance0, vec3 reflectance90)
 {
-    return reflectance0 + (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotH, 0., 1.), 5.0);
+    return reflectance0 + (reflectance90 - reflectance0) * pow(1.0 - VdotH, 5.0);
+}
+
+float fresnelSchlickGGXFloat(float VdotH, float reflectance0, float reflectance90)
+{
+    return reflectance0 + (reflectance90 - reflectance0) * pow(1.0 - VdotH, 5.0);
 }
 
 vec3 fresnelSchlickEnvironmentGGX(float VdotN, vec3 reflectance0, vec3 reflectance90, float smoothness)
@@ -50,6 +105,20 @@ vec3 fresnelSchlickEnvironmentGGX(float VdotN, vec3 reflectance0, vec3 reflectan
     return reflectance0 + weight * (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotN, 0., 1.), 5.0);
 }
 
+float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
+{
+    // Diffuse fresnel falloff as per Disney principled BRDF, and in the spirit of
+    // of general coupled diffuse/specular models e.g. Ashikhmin Shirley.
+    float diffuseFresnelNV = pow(clamp(1.0 - NdotL, 0.000001, 1.), 5.0);
+    float diffuseFresnelNL = pow(clamp(1.0 - NdotV, 0.000001, 1.), 5.0);
+    float diffuseFresnel90 = 0.5 + 2.0 * VdotH * VdotH * roughness;
+    float fresnel =
+        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNL) *
+        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNV);
+
+    return fresnel / PI;
+}
+
 // Cook Torance Specular computation.
 vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, float roughness, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor)
 {
@@ -57,26 +126,31 @@ vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, flo
     float alphaG = convertRoughnessToAverageSlope(roughness);
 
     float distribution = normalDistributionFunction_TrowbridgeReitzGGX(NdotH, alphaG);
-    float visibility = smithVisibilityG_TrowbridgeReitzGGX_Walter(NdotL, NdotV, alphaG);
-    visibility /= (4.0 * NdotL * NdotV); // Cook Torance Denominator  integated in viibility to avoid issues when visibility function changes.
-    float specTerm = max(0., visibility * distribution) * NdotL;
+    float visibility = smithVisibility_TrowbridgeReitzGGXFast(NdotL, NdotV, alphaG);
+    float specTerm = max(0., visibility * distribution);
 
-    vec3 fresnel = fresnelSchlickGGX(VdotH, reflectance0, reflectance90);
+    vec3 fresnel = fresnelSchlickGGXVec3(VdotH, reflectance0, reflectance90);
     return fresnel * specTerm;
 }
 
-float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
-{
-    // Diffuse fresnel falloff as per Disney principled BRDF, and in the spirit of
-    // of general coupled diffuse/specular models e.g. Ashikhmin Shirley.
-    float diffuseFresnelNV = pow(clamp(1.0 - NdotL, 0.000001, 1.), 5.0);
-    float diffuseFresnelNL = pow(clamp(1.0 - NdotV, 0.000001, 1.), 5.0);
-    float diffuseFresnel90 = 0.5 + 2.0 * VdotH * VdotH * roughness;
-    float fresnel =
-        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNL) *
-        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNV);
+vec2 computeClearCoatTerm(float NdotH, float VdotH, float clearCoatRoughness, float geometricRoughnessFactor, float clearCoatIntensity) {
+    clearCoatRoughness = max(clearCoatRoughness, geometricRoughnessFactor);
+    float alphaG = convertRoughnessToAverageSlope(clearCoatRoughness);
 
-    return fresnel * NdotL / PI;
+    float distribution = normalDistributionFunction_TrowbridgeReitzGGX(NdotH, alphaG);
+    float visibility = kelemenVisibility(VdotH);
+    float clearCoatTerm = max(0., visibility * distribution);
+
+    // fo = 4% based on the IOR of a air-polyurethane interface.
+    // the max reflectance is relying on our special trick to prevent weird values on highly diffuse materials.
+    // To let as a configuration if required
+    const float reflectance0 = 0.04;
+    const float reflectance90 = 1.;
+
+    float fresnel = fresnelSchlickGGXFloat(VdotH, reflectance0, reflectance90);
+    fresnel *= clearCoatIntensity;
+    
+    return vec2(fresnel * clearCoatTerm, 1.0 - fresnel);
 }
 
 float adjustRoughnessFromLightProperties(float roughness, float lightRadius, float lightDistance)

+ 0 - 249
src/Shaders/ShadersInclude/pbrLightFunctions.fx

@@ -1,249 +0,0 @@
-// Light Computing
-struct lightingInfo
-{
-    vec3 diffuse;
-    #ifdef SPECULARTERM
-        vec3 specular;
-    #endif
-};
-
-struct pointLightingInfo
-{
-    vec3 lightOffset;
-    float lightDistanceSquared;
-
-    float attenuation;
-};
-
-struct spotLightingInfo
-{
-    vec3 lightOffset;
-    float lightDistanceSquared;
-    vec3 directionToLightCenterW;
-
-    float attenuation;
-};
-
-float computeDistanceLightFalloff_Standard(vec3 lightOffset, float range)
-{
-    return max(0., 1.0 - length(lightOffset) / range);
-}
-
-float computeDistanceLightFalloff_Physical(float lightDistanceSquared)
-{
-    return 1.0 / ((lightDistanceSquared + 0.001));
-}
-
-float computeDistanceLightFalloff_GLTF(float lightDistanceSquared, float inverseSquaredRange)
-{
-    const float minDistanceSquared = 0.01*0.01;
-    float lightDistanceFalloff = 1.0 / (max(lightDistanceSquared, minDistanceSquared));
-
-    float factor = lightDistanceSquared * inverseSquaredRange;
-    float attenuation = clamp(1.0 - factor * factor, 0., 1.);
-    attenuation *= attenuation;
-
-    // Smooth attenuation of the falloff defined by the range.
-    lightDistanceFalloff *= attenuation;
-    
-    return lightDistanceFalloff;
-}
-
-float computeDistanceLightFalloff(vec3 lightOffset, float lightDistanceSquared, float range, float inverseSquaredRange)
-{   
-    #ifdef USEPHYSICALLIGHTFALLOFF
-        return computeDistanceLightFalloff_Physical(lightDistanceSquared);
-    #elif defined(USEGLTFLIGHTFALLOFF)
-        return computeDistanceLightFalloff_GLTF(lightDistanceSquared, inverseSquaredRange);
-    #else
-        return computeDistanceLightFalloff_Standard(lightOffset, range);
-    #endif
-}
-
-float computeDirectionalLightFalloff_Standard(vec3 lightDirection, vec3 directionToLightCenterW, float cosHalfAngle, float exponent)
-{
-    float falloff = 0.0;
-
-    float cosAngle = max(0.000000000000001, dot(-lightDirection, directionToLightCenterW));
-    if (cosAngle >= cosHalfAngle)
-    {
-        falloff = max(0., pow(cosAngle, exponent));
-    }
-    
-    return falloff;
-}
-
-float computeDirectionalLightFalloff_Physical(vec3 lightDirection, vec3 directionToLightCenterW, float cosHalfAngle)
-{
-    const float kMinusLog2ConeAngleIntensityRatio = 6.64385618977; // -log2(0.01)
-
-    // Calculate a Spherical Gaussian (von Mises-Fisher distribution, not angle-based Gaussian) such that the peak is in the light direction,
-    // and the value at the nominal cone angle is 1% of the peak. Because we want the distribution to decay from unity (100%)
-    // at the peak direction (dot product = 1) down to 1% at the nominal cone cutoff (dot product = cosAngle) 
-    // the falloff rate expressed in terms of the base-two dot product is therefore -log2(ConeAngleIntensityRatio) / (1.0 - cosAngle).
-    // Note that the distribution is unnormalised in that peak density is unity, rather than the total energy is unity.
-    float concentrationKappa = kMinusLog2ConeAngleIntensityRatio / (1.0 - cosHalfAngle);
-
-    // Evaluate spherical gaussian for light directional falloff for spot light type (note: spot directional falloff; 
-    // not directional light type)
-    vec4 lightDirectionSpreadSG = vec4(-lightDirection * concentrationKappa, -concentrationKappa);
-    float falloff = exp2(dot(vec4(directionToLightCenterW, 1.0), lightDirectionSpreadSG));
-    return falloff;
-}
-
-float computeDirectionalLightFalloff_GLTF(vec3 lightDirection, vec3 directionToLightCenterW, float lightAngleScale, float lightAngleOffset)
-{
-    // On the CPU
-    // float lightAngleScale = 1.0 f / max (0.001f, ( cosInner - cosOuter ));
-    // float lightAngleOffset = -cosOuter * angleScale;
-
-    float cd = dot(-lightDirection, directionToLightCenterW);
-    float falloff = clamp(cd * lightAngleScale + lightAngleOffset, 0., 1.);
-    // smooth the transition
-    falloff *= falloff;
-    return falloff;
-}
-
-float computeDirectionalLightFalloff(vec3 lightDirection, vec3 directionToLightCenterW, float cosHalfAngle, float exponent, float lightAngleScale, float lightAngleOffset)
-{
-    #ifdef USEPHYSICALLIGHTFALLOFF
-        return computeDirectionalLightFalloff_Physical(lightDirection, directionToLightCenterW, cosHalfAngle);
-    #elif defined(USEGLTFLIGHTFALLOFF)
-        return computeDirectionalLightFalloff_GLTF(lightDirection, directionToLightCenterW, lightAngleScale, lightAngleOffset);
-    #else
-        return computeDirectionalLightFalloff_Standard(lightDirection, directionToLightCenterW, cosHalfAngle, exponent);
-    #endif
-}
-
-pointLightingInfo computePointLightingInfo(vec4 lightData) {
-    pointLightingInfo result;
-
-    result.lightOffset = lightData.xyz - vPositionW;
-    result.lightDistanceSquared = dot(result.lightOffset, result.lightOffset);
-
-    return result;
-}
-
-spotLightingInfo computeSpotLightingInfo(vec4 lightData) {
-    spotLightingInfo result;
-
-    result.lightOffset = lightData.xyz - vPositionW;
-    result.directionToLightCenterW = normalize(result.lightOffset);
-    result.lightDistanceSquared = dot(result.lightOffset, result.lightOffset);
-
-    return result;
-}
-
-lightingInfo computePointLighting(pointLightingInfo info, vec3 viewDirectionW, vec3 vNormal, vec3 diffuseColor,  float lightRadius, float roughness, float NdotV, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, out float NdotL) {
-    lightingInfo result;
-
-    float lightDistance = sqrt(info.lightDistanceSquared);
-    vec3 lightDirection = normalize(info.lightOffset);
-    
-    // Roughness
-    roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
-    
-    // diffuse
-    vec3 H = normalize(viewDirectionW + lightDirection);
-    NdotL = clamp(dot(vNormal, lightDirection), 0.00000000001, 1.0);
-    float VdotH = clamp(dot(viewDirectionW, H), 0.0, 1.0);
-
-    float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
-    result.diffuse = diffuseTerm * diffuseColor * info.attenuation;
-
-    #ifdef SPECULARTERM
-        // Specular
-        float NdotH = clamp(dot(vNormal, H), 0.000000000001, 1.0);
-
-        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, reflectance0, reflectance90, geometricRoughnessFactor);
-        result.specular = specTerm * diffuseColor * info.attenuation;
-    #endif
-
-    return result;
-}
-
-lightingInfo computeSpotLighting(spotLightingInfo info, vec3 viewDirectionW, vec3 vNormal, vec4 lightDirection, vec3 diffuseColor, float lightRadius, float roughness, float NdotV, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, out float NdotL) {
-    lightingInfo result;
-
-    // Roughness.
-    float lightDistance = sqrt(info.lightDistanceSquared);
-    roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
-    
-    // Diffuse
-    vec3 H = normalize(viewDirectionW + info.directionToLightCenterW);
-    NdotL = clamp(dot(vNormal, info.directionToLightCenterW), 0.000000000001, 1.0);
-    float VdotH = clamp(dot(viewDirectionW, H), 0.0, 1.0);
-
-    float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
-    result.diffuse = diffuseTerm * diffuseColor * info.attenuation;
-
-    #ifdef SPECULARTERM
-        // Specular
-        float NdotH = clamp(dot(vNormal, H), 0.000000000001, 1.0);
-
-        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, reflectance0, reflectance90, geometricRoughnessFactor);
-        result.specular = specTerm * diffuseColor * info.attenuation;
-    #endif
-
-    return result;
-}
-
-lightingInfo computeDirectionalLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, float lightRadius, float roughness, float NdotV, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, out float NdotL) {
-    lightingInfo result;
-
-    float lightDistance = length(-lightData.xyz);
-    vec3 lightDirection = normalize(-lightData.xyz);
-
-    // Roughness
-    roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
-    
-    // diffuse
-    vec3 H = normalize(viewDirectionW + lightDirection);
-    NdotL = clamp(dot(vNormal, lightDirection), 0.00000000001, 1.0);
-    float VdotH = clamp(dot(viewDirectionW, H), 0.0, 1.0);
-
-    float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
-    result.diffuse = diffuseTerm * diffuseColor;
-
-    #ifdef SPECULARTERM
-        // Specular
-        float NdotH = clamp(dot(vNormal, H), 0.000000000001, 1.0);
-
-        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, reflectance0, reflectance90, geometricRoughnessFactor);
-        result.specular = specTerm * diffuseColor;
-    #endif
-
-    return result;
-}
-
-lightingInfo computeHemisphericLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, vec3 groundColor, float roughness, float NdotV, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, out float NdotL) {
-    lightingInfo result;
-
-    // Roughness
-    // Do not touch roughness on hemispheric.
-
-    // Diffuse
-    NdotL = dot(vNormal, lightData.xyz) * 0.5 + 0.5;
-    result.diffuse = mix(groundColor, diffuseColor, NdotL);
-
-    #ifdef SPECULARTERM
-        // Specular
-        vec3 lightVectorW = normalize(lightData.xyz);
-        vec3 H = normalize(viewDirectionW + lightVectorW);
-        float NdotH = clamp(dot(vNormal, H), 0.000000000001, 1.0);
-        NdotL = clamp(NdotL, 0.000000000001, 1.0);
-        float VdotH = clamp(dot(viewDirectionW, H), 0.0, 1.0);
-
-        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, reflectance0, reflectance90, geometricRoughnessFactor);
-        result.specular = specTerm * diffuseColor;
-    #endif
-
-    return result;
-}
-
-vec3 computeProjectionTextureDiffuseLighting(sampler2D projectionLightSampler, mat4 textureProjectionMatrix){
-	vec4 strq = textureProjectionMatrix * vec4(vPositionW, 1.0);
-	strq /= strq.w;
-	vec3 textureColor = texture2D(projectionLightSampler, strq.xy).rgb;
-	return toLinearSpace(textureColor);
-}

+ 48 - 0
src/Shaders/ShadersInclude/pbrLightingFunctions.fx

@@ -0,0 +1,48 @@
+// Light Results
+struct lightingInfo
+{
+    vec3 diffuse;
+    #ifdef SPECULARTERM
+        vec3 specular;
+    #endif
+    #ifdef CLEARCOAT
+        // xyz contains the clearcoat color.
+        // w contains the 1 - clearcoat fresnel to ease the energy conservation computation.
+        vec4 clearCoat;
+    #endif
+};
+
+vec3 computeHemisphericDiffuseLighting(preLightingInfo info, vec3 lightColor, vec3 groundColor) {
+    return mix(groundColor, lightColor, info.NdotL);
+}
+
+vec3 computeDiffuseLighting(preLightingInfo info, vec3 lightColor) {
+    float diffuseTerm = computeDiffuseTerm(info.NdotL, info.NdotV, info.VdotH, info.roughness);
+    return diffuseTerm * info.attenuation * info.NdotL * lightColor;
+}
+
+vec3 computeSpecularLighting(preLightingInfo info, vec3 normal, vec3 reflectance0, vec3 reflectance90, float geometricRoughnessFactor, vec3 lightColor) {
+    float NdotH = clamp(dot(normal, info.H), 0.000000000001, 1.0);
+
+    vec3 specTerm = computeSpecularTerm(NdotH, info.NdotL, info.NdotV, info.VdotH, info.roughness, reflectance0, reflectance90, geometricRoughnessFactor);
+    return specTerm * info.attenuation * info.NdotL * lightColor;
+}
+
+vec4 computeClearCoatLighting(preLightingInfo info, vec3 normalClearCoat, float geometricRoughnessFactor, float clearCoatIntensity, vec3 lightColor) {
+    float NccdotL = clamp(dot(normalClearCoat, info.L), 0.00000000001, 1.0);
+    float NccdotH = clamp(dot(normalClearCoat, info.H), 0.000000000001, 1.0);
+
+    vec2 clearCoatTerm = computeClearCoatTerm(NccdotH, info.VdotH, info.roughness, geometricRoughnessFactor, clearCoatIntensity);
+
+    vec4 result = vec4(0.);
+    result.rgb = clearCoatTerm.x * info.attenuation * NccdotL * lightColor;
+    result.a = clearCoatTerm.y;
+    return result;
+}
+
+vec3 computeProjectionTextureDiffuseLighting(sampler2D projectionLightSampler, mat4 textureProjectionMatrix){
+	vec4 strq = textureProjectionMatrix * vec4(vPositionW, 1.0);
+	strq /= strq.w;
+	vec3 textureColor = texture2D(projectionLightSampler, strq.xy).rgb;
+	return toLinearSpace(textureColor);
+}

+ 69 - 0
src/Shaders/ShadersInclude/pbrPreLightingFunctions.fx

@@ -0,0 +1,69 @@
+// Pre Light Computing
+struct preLightingInfo
+{
+    // Pre Falloff Info
+    vec3 lightOffset;
+    float lightDistanceSquared;
+    float lightDistance;
+
+    // Falloff Info
+    float attenuation;
+
+    // Lighting Info
+    vec3 L;
+    vec3 H;
+    float NdotV;
+    float NdotL;
+    float VdotH;
+    float roughness;
+};
+
+preLightingInfo computePointAndSpotPreLightingInfo(vec4 lightData, vec3 V, vec3 N) {
+    preLightingInfo result;
+
+    // Attenuation data.
+    result.lightOffset = lightData.xyz - vPositionW;
+    result.lightDistanceSquared = dot(result.lightOffset, result.lightOffset);
+
+    // Roughness.
+    result.lightDistance = sqrt(result.lightDistanceSquared);
+
+    // Geometry Data.
+    result.L = normalize(result.lightOffset);
+    result.H = normalize(V + result.L);
+    result.NdotL = clamp(dot(N, result.L), 0.000000000001, 1.0);
+    result.VdotH = clamp(dot(V, result.H), 0.0, 1.0);
+
+    return result;
+}
+
+preLightingInfo computeDirectionalPreLightingInfo(vec4 lightData, vec3 V, vec3 N) {
+    preLightingInfo result;
+
+    // Roughness
+    result.lightDistance = length(-lightData.xyz);
+
+    // Geometry Data.
+    result.L = normalize(-lightData.xyz);
+    result.H = normalize(V + result.L);
+    result.NdotL = clamp(dot(N, result.L), 0.00000000001, 1.0);
+    result.VdotH = clamp(dot(V, result.H), 0.0, 1.0);
+
+    return result;
+}
+
+preLightingInfo computeHemisphericPreLightingInfo(vec4 lightData, vec3 V, vec3 N) {
+    preLightingInfo result;
+
+    // Geometry Data.
+    result.NdotL = dot(N, lightData.xyz) * 0.5 + 0.5;
+    result.NdotL = clamp(result.NdotL, 0.000000000001, 1.0);
+
+    #ifdef SPECULARTERM
+        result.L = normalize(lightData.xyz);
+        result.H = normalize(V + result.L);
+        result.VdotH = clamp(dot(V, result.H), 0.0, 1.0);
+    #endif
+
+    return result;
+}

+ 38 - 31
src/Shaders/ShadersInclude/pbrUboDeclaration.fx

@@ -2,44 +2,51 @@ layout(std140, column_major) uniform;
 
 uniform Material
 {
-	uniform vec2 vAlbedoInfos;
-	uniform vec4 vAmbientInfos;
-	uniform vec2 vOpacityInfos;
-	uniform vec2 vEmissiveInfos;
-	uniform vec2 vLightmapInfos;
-	uniform vec3 vReflectivityInfos;
-	uniform vec2 vMicroSurfaceSamplerInfos;
-	uniform vec4 vRefractionInfos;
-	uniform vec2 vReflectionInfos;
-	uniform vec3 vReflectionPosition;
-	uniform vec3 vReflectionSize;	
-	uniform vec3 vBumpInfos;
-	uniform mat4 albedoMatrix;
-	uniform mat4 ambientMatrix;
-	uniform mat4 opacityMatrix;
-	uniform mat4 emissiveMatrix;
-	uniform mat4 lightmapMatrix;
-	uniform mat4 reflectivityMatrix;
-	uniform mat4 microSurfaceSamplerMatrix;
-	uniform mat4 bumpMatrix;
-	uniform vec2 vTangentSpaceParams;
-	uniform mat4 refractionMatrix;
-	uniform mat4 reflectionMatrix;
+    uniform vec2 vAlbedoInfos;
+    uniform vec4 vAmbientInfos;
+    uniform vec2 vOpacityInfos;
+    uniform vec2 vEmissiveInfos;
+    uniform vec2 vLightmapInfos;
+    uniform vec3 vReflectivityInfos;
+    uniform vec2 vMicroSurfaceSamplerInfos;
+    uniform vec4 vRefractionInfos;
+    uniform vec2 vReflectionInfos;
+    uniform vec3 vReflectionPosition;
+    uniform vec3 vReflectionSize;	
+    uniform vec3 vBumpInfos;
+    uniform mat4 albedoMatrix;
+    uniform mat4 ambientMatrix;
+    uniform mat4 opacityMatrix;
+    uniform mat4 emissiveMatrix;
+    uniform mat4 lightmapMatrix;
+    uniform mat4 reflectivityMatrix;
+    uniform mat4 microSurfaceSamplerMatrix;
+    uniform mat4 bumpMatrix;
+    uniform vec2 vTangentSpaceParams;
+    uniform mat4 refractionMatrix;
+    uniform mat4 reflectionMatrix;
 
-	uniform vec3 vReflectionColor;
-	uniform vec4 vAlbedoColor;
-	uniform vec4 vLightingIntensity;
+    uniform vec3 vReflectionColor;
+    uniform vec4 vAlbedoColor;
+    uniform vec4 vLightingIntensity;
 
     uniform vec3 vRefractionMicrosurfaceInfos;
     uniform vec3 vReflectionMicrosurfaceInfos;
 
-	uniform vec4 vReflectivityColor;
-	uniform vec3 vEmissiveColor;
+    uniform vec4 vReflectivityColor;
+    uniform vec3 vEmissiveColor;
 
-	uniform float pointSize;
+    uniform float pointSize;
+
+    uniform vec2 vClearCoatParams;
+    uniform vec2 vClearCoatInfos;
+    uniform mat4 clearCoatMatrix;
+    uniform vec2 vClearCoatBumpInfos;
+    uniform vec2 vClearCoatTangentSpaceParams;
+    uniform mat4 clearCoatBumpMatrix;
 };
 
 uniform Scene {
-	mat4 viewProjection;
-	mat4 view;
+    mat4 viewProjection;
+    mat4 view;
 };

+ 13 - 0
src/Shaders/ShadersInclude/pbrVertexDeclaration.fx

@@ -58,3 +58,16 @@ uniform float pointSize;
     uniform mat4 reflectionMatrix;
     uniform vec3 vReflectionMicrosurfaceInfos;
 #endif
+
+// Clear Coat
+#ifdef CLEARCOAT
+    #ifdef CLEARCOAT_TEXTURE
+        uniform vec2 vClearCoatInfos;
+        uniform mat4 clearCoatMatrix;
+    #endif
+
+    #ifdef CLEARCOAT_BUMP
+        uniform vec2 vClearCoatBumpInfos;
+        uniform mat4 clearCoatBumpMatrix;
+    #endif
+#endif

+ 270 - 37
src/Shaders/pbr.fragment.fx

@@ -1,4 +1,4 @@
-#if defined(BUMP) || !defined(NORMAL) || defined(FORCENORMALFORWARD) || defined(SPECULARAA)
+#if defined(BUMP) || !defined(NORMAL) || defined(FORCENORMALFORWARD) || defined(SPECULARAA) || defined(CLEARCOAT_BUMP)
 #extension GL_OES_standard_derivatives : enable
 #endif
 
@@ -121,6 +121,30 @@ varying vec4 vColor;
     uniform sampler2D microSurfaceSampler;
 #endif
 
+#ifdef CLEARCOAT
+    #ifdef CLEARCOAT_TEXTURE
+        #if CLEARCOAT_TEXTUREDIRECTUV == 1
+            #define vClearCoatUV vMainUV1
+        #elif CLEARCOAT_TEXTUREDIRECTUV == 2
+            #define vClearCoatUV vMainUV2
+        #else
+            varying vec2 vClearCoatUV;
+        #endif
+        uniform sampler2D clearCoatSampler;
+    #endif
+
+    #ifdef CLEARCOAT_BUMP
+        #if CLEARCOAT_BUMPDIRECTUV == 1
+            #define vClearCoatBumpUV vMainUV1
+        #elif CLEARCOAT_BUMPDIRECTUV == 2
+            #define vClearCoatBumpUV vMainUV2
+        #else
+            varying vec2 vClearCoatBumpUV;
+        #endif
+        uniform sampler2D clearCoatBumpSampler;
+    #endif
+#endif
+
 // Refraction
 #ifdef REFRACTION
     #ifdef REFRACTIONMAP_3D
@@ -204,7 +228,9 @@ varying vec4 vColor;
 #include<shadowsFragmentFunctions>
 #include<pbrFunctions>
 #include<harmonicsFunctions>
-#include<pbrLightFunctions>
+#include<pbrPreLightingFunctions>
+#include<pbrFalloffLightingFunctions>
+#include<pbrLightingFunctions>
 
 #include<bumpFragmentFunctions>
 #include<clipPlaneFragmentDeclaration>
@@ -226,20 +252,13 @@ void main(void) {
     vec3 normalW = normalize(cross(dFdx(vPositionW), dFdy(vPositionW))) * vEyePosition.w;
 #endif
 
-#include<bumpFragment>
-
-#ifdef SPECULARAA
-    vec3 nDfdx = dFdx(normalW.xyz);
-    vec3 nDfdy = dFdy(normalW.xyz);
-    float slopeSquare = max(dot(nDfdx, nDfdx), dot(nDfdy, nDfdy));
-    // Vive analytical lights roughness factor.
-    float geometricRoughnessFactor = pow(clamp(slopeSquare , 0., 1.), 0.333);
-    // BJS factor.
-    float geometricAlphaGFactor = sqrt(slopeSquare);
-#else
-    float geometricRoughnessFactor = 0.;
+#ifdef CLEARCOAT
+    // Needs to use the geometric normal before bump for this.
+    vec3 clearCoatNormalW = normalW;
 #endif
 
+#include<bumpFragment>
+
 #if defined(FORCENORMALFORWARD) && defined(NORMAL)
     vec3 faceNormal = normalize(cross(dFdx(vPositionW), dFdy(vPositionW))) * vEyePosition.w;
     #if defined(TWOSIDEDLIGHTING)
@@ -270,6 +289,10 @@ void main(void) {
     surfaceAlbedo *= vAlbedoInfos.y;
 #endif
 
+#ifdef VERTEXCOLOR
+    surfaceAlbedo *= vColor.rgb;
+#endif
+
 // _____________________________ Alpha Information _______________________________
 #ifdef OPACITY
     vec4 opacityMap = texture2D(opacitySampler, vOpacityUV + uvOffset);
@@ -301,10 +324,6 @@ void main(void) {
 
 #include<depthPrePass>
 
-#ifdef VERTEXCOLOR
-    surfaceAlbedo *= vColor.rgb;
-#endif
-
 // _____________________________ AO    Information _______________________________
     vec3 ambientOcclusionColor = vec3(1., 1., 1.);
 
@@ -320,8 +339,8 @@ void main(void) {
     vec3 diffuseBase = vec3(1., 1., 1.);
 #else
     // _____________________________ Reflectivity Info _______________________________
-        float microSurface = vReflectivityColor.a;
-        vec3 surfaceReflectivityColor = vReflectivityColor.rgb;
+    float microSurface = vReflectivityColor.a;
+    vec3 surfaceReflectivityColor = vReflectivityColor.rgb;
 
     #ifdef METALLICWORKFLOW
         vec2 metallicRoughness = surfaceReflectivityColor.rg;
@@ -429,16 +448,16 @@ void main(void) {
     #endif
 
     // _____________________________ Compute LODs Fetch ____________________________________
-        // Compute N dot V.
-        float NdotVUnclamped = dot(normalW, viewDirectionW);
-        float NdotV = clamp(NdotVUnclamped,0., 1.) + 0.00001;
-        float alphaG = convertRoughnessToAverageSlope(roughness);
-
-        #ifdef SPECULARAA
-            // Adapt linear roughness (alphaG) to geometric curvature of the current pixel.
-            // 75% accounts a bit for the bigger tail linked to Gaussian Filtering.
-            alphaG += (0.75 * geometricAlphaGFactor);
-        #endif
+    float NdotVUnclamped = dot(normalW, viewDirectionW);
+    float NdotV = clamp(NdotVUnclamped,0., 1.) + 0.00001;
+    float alphaG = convertRoughnessToAverageSlope(roughness);
+    vec2 AARoughnessFactors = getAARoughnessFactors(normalW.xyz);
+
+    #ifdef SPECULARAA
+        // Adapt linear roughness (alphaG) to geometric curvature of the current pixel.
+        // 75% accounts a bit for the bigger tail linked to Gaussian Filtering.
+        alphaG += AARoughnessFactors.y;
+    #endif
 
     // _____________________________ Refraction Info _______________________________________
     #ifdef REFRACTION
@@ -616,6 +635,132 @@ void main(void) {
         environmentIrradiance *= vReflectionColor.rgb;
     #endif
 
+    // _____________________________ Clear Coat Information ____________________________
+    #ifdef CLEARCOAT
+        // Clear COAT parameters.
+        float clearCoatIntensity = vClearCoatParams.x;
+        float clearCoatRoughness = vClearCoatParams.y;
+
+        #ifdef CLEARCOAT_TEXTURE
+            vec2 clearCoatMapData = texture2D(clearCoatSampler, vClearCoatUV + uvOffset).rg * vClearCoatInfos.y;
+            clearCoatIntensity *= clearCoatMapData.x;
+            clearCoatRoughness *= clearCoatMapData.y;
+
+            // remapping and linearization of clear coat roughness
+            // Let s see how it ends up in gltf
+            // clearCoatRoughness = mix(0.089, 0.6, clearCoatRoughness);
+        #endif
+
+        #ifdef CLEARCOAT_BUMP
+            #ifdef NORMALXYSCALE
+                float clearCoatNormalScale = 1.0;
+            #else
+                float clearCoatNormalScale = vClearCoatBumpInfos.y;
+            #endif
+
+            #if defined(TANGENT) && defined(NORMAL)
+                mat3 TBNClearCoat = vTBN;
+            #else
+                mat3 TBNClearCoat = cotangent_frame(clearCoatNormalW * clearCoatNormalScale, vPositionW, vClearCoatBumpUV, vClearCoatTangentSpaceParams);
+            #endif
+
+            #ifdef OBJECTSPACE_NORMALMAP
+                clearCoatNormalW = normalize(texture2D(clearCoatBumpSampler, vClearCoatBumpUV + uvOffset).xyz  * 2.0 - 1.0);
+                clearCoatNormalW = normalize(mat3(normalMatrix) * clearCoatNormalW);
+            #else
+                clearCoatNormalW = perturbNormal(TBN, vClearCoatBumpUV + uvOffset, clearCoatBumpSampler, vClearCoatBumpInfos.y);
+            #endif
+        #endif
+
+        #if defined(FORCENORMALFORWARD) && defined(NORMAL)
+            clearCoatNormalW *= sign(dot(clearCoatNormalW, faceNormal));
+        #endif
+
+        #if defined(TWOSIDEDLIGHTING) && defined(NORMAL)
+            clearCoatNormalW = gl_FrontFacing ? clearCoatNormalW : -clearCoatNormalW;
+        #endif
+
+        // Clear Coat AA
+        vec2 clearCoatAARoughnessFactors = getAARoughnessFactors(clearCoatNormalW.xyz);
+
+        // Compute N dot V.
+        float clearCoatNdotVUnclamped = dot(clearCoatNormalW, viewDirectionW);
+        float clearCoatNdotV = clamp(clearCoatNdotVUnclamped,0., 1.) + 0.00001;
+
+        // Clear Coat Reflection
+        #if defined(REFLECTION)
+            float clearCoatAlphaG = convertRoughnessToAverageSlope(clearCoatRoughness);
+
+            #ifdef SPECULARAA
+                // Adapt linear roughness (alphaG) to geometric curvature of the current pixel.
+                // 75% accounts a bit for the bigger tail linked to Gaussian Filtering.
+                clearCoatAlphaG += clearCoatAARoughnessFactors.y;
+            #endif
+
+            vec4 environmentClearCoatRadiance = vec4(0., 0., 0., 0.);
+
+            vec3 clearCoatReflectionVector = computeReflectionCoords(vec4(vPositionW, 1.0), clearCoatNormalW);
+            #ifdef REFLECTIONMAP_OPPOSITEZ
+                clearCoatReflectionVector.z *= -1.0;
+            #endif
+
+            // _____________________________ 2D vs 3D Maps ________________________________
+            #ifdef REFLECTIONMAP_3D
+                vec3 clearCoatReflectionCoords = clearCoatReflectionVector;
+            #else
+                vec2 clearCoatReflectionCoords = clearCoatReflectionVector.xy;
+                #ifdef REFLECTIONMAP_PROJECTION
+                    clearCoatReflectionCoords /= clearCoatReflectionVector.z;
+                #endif
+                clearCoatReflectionCoords.y = 1.0 - clearCoatReflectionCoords.y;
+            #endif
+
+            #if defined(LODINREFLECTIONALPHA) && !defined(REFLECTIONMAP_SKYBOX)
+                float clearCoatReflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, clearCoatAlphaG, clearCoatNdotVUnclamped);
+            #else
+                float clearCoatReflectionLOD = getLodFromAlphaG(vReflectionMicrosurfaceInfos.x, clearCoatAlphaG, 1.);
+            #endif
+
+            #ifdef LODBASEDMICROSFURACE
+                // Apply environment convolution scale/offset filter tuning parameters to the mipmap LOD selection
+                clearCoatReflectionLOD = clearCoatReflectionLOD * vReflectionMicrosurfaceInfos.y + vReflectionMicrosurfaceInfos.z;
+                float requestedClearCoatReflectionLOD = clearCoatReflectionLOD;
+
+                environmentClearCoatRadiance = sampleReflectionLod(reflectionSampler, clearCoatReflectionCoords, requestedClearCoatReflectionLOD);
+            #else
+                float lodClearCoatReflectionNormalized = clamp(clearCoatReflectionLOD / log2(vReflectionMicrosurfaceInfos.x), 0., 1.);
+                float lodClearCoatReflectionNormalizedDoubled = lodClearCoatReflectionNormalized * 2.0;
+
+                vec4 environmentClearCoatMid = sampleReflection(reflectionSampler, reflectionCoords);
+                if(lodClearCoatReflectionNormalizedDoubled < 1.0){
+                    environmentClearCoatRadiance = mix(
+                        sampleReflection(reflectionSamplerHigh, clearCoatReflectionCoords),
+                        environmentClearCoatMid,
+                        lodClearCoatReflectionNormalizedDoubled
+                    );
+                }else{
+                    environmentClearCoatRadiance = mix(
+                        environmentClearCoatMid,
+                        sampleReflection(reflectionSamplerLow, clearCoatReflectionCoords),
+                        lodClearCoatReflectionNormalizedDoubled - 1.0
+                    );
+                }
+            #endif
+
+            #ifdef RGBDREFLECTION
+                environmentClearCoatRadiance.rgb = fromRGBD(environmentClearCoatRadiance);
+            #endif
+
+            #ifdef GAMMAREFLECTION
+                environmentClearCoatRadiance.rgb = toLinearSpace(environmentClearCoatRadiance.rgb);
+            #endif
+
+            // _____________________________ Levels _____________________________________
+            environmentClearCoatRadiance.rgb *= vReflectionInfos.x;
+            environmentClearCoatRadiance.rgb *= vReflectionColor.rgb;
+        #endif
+    #endif
+
     // ____________________________________________________________________________________
     // _____________________________ Direct Lighting Param ________________________________
         // Compute reflectance.
@@ -629,6 +774,9 @@ void main(void) {
     #ifdef SPECULARTERM
         vec3 specularBase = vec3(0., 0., 0.);
     #endif
+    #ifdef CLEARCOAT
+        vec3 clearCoatBase = vec3(0., 0., 0.);
+    #endif
 
     #ifdef LIGHTMAP
         vec3 lightmapColor = texture2D(lightmapSampler, vLightmapUV + uvOffset).rgb;
@@ -638,14 +786,11 @@ void main(void) {
         lightmapColor *= vLightmapInfos.y;
     #endif
 
-        lightingInfo info;
+    preLightingInfo preInfo;
+    lightingInfo info;
 
-        pointLightingInfo pointInfo;
-
-        spotLightingInfo spotInfo;
-
-        float shadow = 1.; // 1 - shadowLevel
-        float NdotL = -1.;
+    // 1 - shadowLevel
+    float shadow = 1.;
 
     #include<lightFragment>[0..maxSimultaneousLights]
 
@@ -683,6 +828,54 @@ void main(void) {
         vec3 specularEnvironmentReflectance = fresnelSchlickEnvironmentGGX(NdotV, specularEnvironmentR0, specularEnvironmentR90, sqrt(microSurface));
     #endif
 
+    // _________________________ Clear Coat Environment Oclusion __________________________
+    #ifdef CLEARCOAT
+        #if defined(ENVIRONMENTBRDF) && !defined(REFLECTIONMAP_SKYBOX)
+            // Indexed on cos(theta) and roughness
+            vec2 brdfClearCoatSamplerUV = vec2(clearCoatNdotV, clearCoatRoughness);
+            
+            // We can find the scale and offset to apply to the specular value.
+            vec4 environmentClearCoatBrdf = texture2D(environmentBrdfSampler, brdfClearCoatSamplerUV);
+
+            vec3 clearCoatEnvironmentReflectance = vec3(0.04) * environmentClearCoatBrdf.x + environmentClearCoatBrdf.y;
+
+            #ifdef RADIANCEOCCLUSION
+                float clearCoatSeo = environmentRadianceOcclusion(ambientMonochrome, clearCoatNdotVUnclamped);
+                clearCoatEnvironmentReflectance *= clearCoatSeo;
+            #endif
+
+            #ifdef HORIZONOCCLUSION
+                #ifdef BUMP
+                    #ifdef REFLECTIONMAP_3D
+                        float clearCoatEho = environmentHorizonOcclusion(-viewDirectionW, clearCoatNormalW);
+                        clearCoatEnvironmentReflectance *= clearCoatEho;
+                    #endif
+                #endif
+            #endif
+        #else
+            // Jones implementation of a well balanced fast analytical solution.
+            vec3 clearCoatEnvironmentReflectance = fresnelSchlickEnvironmentGGX(clearCoatNdotV, vec3(1.), vec3(1.), sqrt(1. - clearCoatRoughness));
+        #endif
+
+        // clear coar energy conservation
+        // fo = 4% based on the IOR of a air-polyurethane interface.
+        // the max reflectance is relying on our special trick to prevent weird values on highly diffuse materials.
+        // To let as a configuration if required
+        const float IBLClearCoatReflectance0 = 0.04;
+        const float IBLClearCoatReflectance90 = 1.;
+
+        float fresnelIBLClearCoat = fresnelSchlickGGXFloat(clearCoatNdotV, IBLClearCoatReflectance0, IBLClearCoatReflectance90);
+        fresnelIBLClearCoat *= clearCoatIntensity;
+        
+        float conservationFactor = (1. - fresnelIBLClearCoat);
+
+        clearCoatEnvironmentReflectance *= clearCoatIntensity;
+        #ifdef REFLECTION
+            environmentIrradiance *= conservationFactor;
+        #endif
+        specularEnvironmentReflectance *= (conservationFactor * conservationFactor);
+    #endif
+
     // _____________________________ Refractance+Tint ________________________________
     #ifdef REFRACTION
         vec3 refractance = vec3(0.0, 0.0, 0.0);
@@ -757,23 +950,53 @@ void main(void) {
         finalRefraction *= refractance;
     #endif
 
+    // _____________________________ Clear Coat _______________________________________
+    #ifdef CLEARCOAT
+        vec3 finalClearCoat = clearCoatBase;
+        finalClearCoat = max(finalClearCoat, 0.0);
+
+        // Full value needed for alpha.
+        vec3 finalClearCoatScaled = finalClearCoat * vLightingIntensity.x * vLightingIntensity.w;
+
+    // ____________________________ Clear Coat Radiance _______________________________
+        #ifdef REFLECTION
+            vec3 finalClearCoatRadiance = environmentClearCoatRadiance.rgb;
+            finalClearCoatRadiance *= clearCoatEnvironmentReflectance;
+
+            // Full value needed for alpha. 
+            vec3 finalClearCoatRadianceScaled = finalClearCoatRadiance * vLightingIntensity.z;
+        #endif
+
+        #ifdef REFRACTION
+            finalRefraction *= (conservationFactor * conservationFactor);
+        #endif
+    #endif
+
     // _____________________________ Highlights on Alpha _____________________________
     #ifdef ALPHABLEND
         float luminanceOverAlpha = 0.0;
         #if	defined(REFLECTION) && defined(RADIANCEOVERALPHA)
             luminanceOverAlpha += getLuminance(finalRadianceScaled);
+            #if defined(CLEARCOAT)
+                luminanceOverAlpha += getLuminance(finalClearCoatRadianceScaled);
+            #endif
         #endif
 
         #if defined(SPECULARTERM) && defined(SPECULAROVERALPHA)
             luminanceOverAlpha += getLuminance(finalSpecularScaled);
         #endif
 
+        #if defined(CLEARCOAT) && defined(CLEARCOATOVERALPHA)
+            luminanceOverAlpha += getLuminance(finalClearCoatScaled);
+        #endif
+
         #if defined(RADIANCEOVERALPHA) || defined(SPECULAROVERALPHA)
             alpha = clamp(alpha + luminanceOverAlpha * luminanceOverAlpha, 0., 1.);
         #endif
     #endif
 #endif
 
+// _______________ Not done before as it is unlit only __________________________
 // _____________________________ Diffuse ________________________________________
     vec3 finalDiffuse = diffuseBase;
     finalDiffuse.rgb += vAmbientColor;
@@ -809,10 +1032,20 @@ void main(void) {
     //	finalSpecular			* vLightingIntensity.x * vLightingIntensity.w +
         finalSpecularScaled +
     #endif
+    #ifdef CLEARCOAT
+    // Computed in the previous step to help with alpha luminance.
+    //	finalClearCoat			* vLightingIntensity.x * vLightingIntensity.w +
+        finalClearCoatScaled +
+    #endif
     #ifdef REFLECTION
     // Comupted in the previous step to help with alpha luminance.
     //	finalRadiance			* vLightingIntensity.z +
         finalRadianceScaled +
+        #ifdef CLEARCOAT
+        //  Comupted in the previous step to help with alpha luminance.
+        //  finalClearCoatRadiance * vLightingIntensity.z 
+            finalClearCoatRadianceScaled +
+        #endif
     #endif
     #ifdef REFRACTION
         finalRefraction			* vLightingIntensity.z +

+ 34 - 0
src/Shaders/pbr.vertex.fx

@@ -64,6 +64,16 @@ varying vec2 vMicroSurfaceSamplerUV;
 varying vec2 vBumpUV;
 #endif
 
+#ifdef CLEARCOAT
+    #if defined(CLEARCOAT_TEXTURE) && CLEARCOAT_TEXTUREDIRECTUV == 0 
+        varying vec2 vClearCoatUV;
+    #endif
+
+    #if defined(CLEARCOAT_BUMP) && CLEARCOAT_BUMPDIRECTUV == 0 
+        varying vec2 vClearCoatBumpUV;
+    #endif
+#endif
+
 // Output
 varying vec3 vPositionW;
 #ifdef NORMAL
@@ -250,6 +260,30 @@ void main(void) {
     }
 #endif
 
+#ifdef CLEARCOAT
+    #if defined(CLEARCOAT_TEXTURE) && CLEARCOAT_TEXTUREDIRECTUV == 0 
+        if (vClearCoatInfos.x == 0.)
+        {
+            vClearCoatUV = vec2(clearCoatMatrix * vec4(uv, 1.0, 0.0));
+        }
+        else
+        {
+            vClearCoatUV = vec2(clearCoatMatrix * vec4(uv2, 1.0, 0.0));
+        }
+    #endif
+
+    #if defined(CLEARCOAT_BUMP) && CLEARCOAT_BUMPDIRECTUV == 0 
+        if (vClearCoatBumpInfos.x == 0.)
+        {
+            vClearCoatBumpUV = vec2(clearCoatBumpMatrix * vec4(uv, 1.0, 0.0));
+        }
+        else
+        {
+            vClearCoatBumpUV = vec2(clearCoatBumpMatrix * vec4(uv2, 1.0, 0.0));
+        }
+    #endif
+#endif
+
     // TBN
 #include<bumpVertex>
 

BIN
tests/validation/ReferenceImages/clearCoat.png


+ 5 - 0
tests/validation/config.json

@@ -2,6 +2,11 @@
   "root": "https://rawgit.com/BabylonJS/Website/master",
   "tests": [
     {
+      "title": "Clear Coat",
+      "playgroundId": "#YACNQS#1",
+      "referenceImage": "clearCoat.png"
+    },
+    {
       "title": "GUI Transform StackPanel",
       "playgroundId": "#BS60AB#0",
       "referenceImage": "TransformStackPanel.png"