sebavan 6 anos atrás
pai
commit
ac757b16ba

BIN
assets/environments/correlatedBRDF.dds


BIN
assets/environments/correlatedBRDF.png


BIN
assets/environments/correlatedBRDFRGBA.png


BIN
assets/environments/correlatedMSBRDF.dds


BIN
assets/environments/correlatedMSBRDF.png


BIN
assets/environments/correlatedMSBRDFRGBA.png


BIN
assets/environments/uncorrelatedBRDF.dds


BIN
assets/environments/uncorrelatedBRDF.png


BIN
assets/environments/uncorrelatedBRDFRGB.png


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

@@ -25,9 +25,12 @@
   - 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)) **** NEED DEMO or DOC LINK)
 - Migrated the code to modules and deploy [ES6 npm packages](https://doc.babylonjs.com/features/es6_support) ([Sebavan](https://github.com/Sebavan))
-- Added clear coat support to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
-- Added anisotropy support to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
 - Added `TrailMesh` class. Credit to furcatomasz ([danjpar](https://github.com/danjpar)) **** NEED DEMO or DOC LINK)
+- PBR:
+  - Added Smith Height Correlated Visibility term to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
+  - Added energy conservation through Multiscattering BRDF support to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
+  - Added clear coat support to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
+  - Added anisotropy support to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
 
 ## Updates
 

+ 3 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx

@@ -145,6 +145,9 @@ export class PBRMaterialPropertyGridComponent extends React.Component<IPBRMateri
                     <CheckBoxLineComponent label="Specular anti-aliasing" target={material} propertyName="enableSpecularAntiAliasing" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="ADVANCED" closed={true}>
+                    <CheckBoxLineComponent label="Energy Conservation" target={material.brdf} propertyName="useEnergyConservation"
+                        onValueChanged={() => this.forceUpdate()}
+                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <CheckBoxLineComponent label="Radiance occlusion" target={material} propertyName="useRadianceOcclusion" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <CheckBoxLineComponent label="Horizon occlusion " target={material} propertyName="useHorizonOcclusion" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <CheckBoxLineComponent label="Unlit" target={material} propertyName="unlit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

+ 96 - 0
src/Materials/PBR/pbrBRDFConfiguration.ts

@@ -0,0 +1,96 @@
+import { SerializationHelper, serialize, expandToProperty } from "../../Misc/decorators";
+
+/**
+ * @hidden
+ */
+export interface IMaterialBRDFDefines {
+    BRDF_V_HEIGHT_CORRELATED: boolean;
+    MS_BRDF_ENERGY_CONSERVATION: boolean;
+
+    /** @hidden */
+    _areMiscDirty: boolean;
+}
+
+/**
+ * Define the code related to the BRDF parameters of the pbr material.
+ */
+export class PBRBRDFConfiguration {
+
+    @serialize()
+    private _useEnergyConservation = true;
+    /**
+     * Defines if the material uses energy conservation.
+     */
+    @expandToProperty("_markAllSubMeshesAsMiscDirty")
+    public useEnergyConservation = true;
+
+    @serialize()
+    private _useSmithVisibilityHeightCorrelated = true;
+    /**
+     * LEGACY Mode set to false
+     * Defines if the material uses height smith correlated visibility term.
+     * If you intent to not use our default BRDF, you need to load a separate BRDF Texture for the PBR
+     * You can either load https://assets.babylonjs.com/environments/uncorrelatedBRDF.png
+     * or https://assets.babylonjs.com/environments/uncorrelatedBRDF.dds to have more precision
+     * Not relying on height correlated will also disable energy conservation.
+     */
+    @expandToProperty("_markAllSubMeshesAsMiscDirty")
+    public useSmithVisibilityHeightCorrelated = true;
+
+    /** @hidden */
+    private _internalMarkAllSubMeshesAsMiscDirty: () => void;
+
+    /** @hidden */
+    public _markAllSubMeshesAsMiscDirty(): void {
+        this._internalMarkAllSubMeshesAsMiscDirty();
+    }
+
+    /**
+     * Instantiate a new istance of clear coat configuration.
+     * @param markAllSubMeshesAsMiscDirty Callback to flag the material to dirty
+     */
+    constructor(markAllSubMeshesAsMiscDirty: () => void) {
+        this._internalMarkAllSubMeshesAsMiscDirty = markAllSubMeshesAsMiscDirty;
+    }
+
+    /**
+     * Checks to see if a texture is used in the material.
+     * @param defines the list of "defines" to update.
+     */
+    public prepareDefines(defines: IMaterialBRDFDefines): void {
+        defines.BRDF_V_HEIGHT_CORRELATED = this._useSmithVisibilityHeightCorrelated;
+        defines.MS_BRDF_ENERGY_CONSERVATION = this._useEnergyConservation && this._useSmithVisibilityHeightCorrelated;
+    }
+
+    /**
+    * 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 brdfConfiguration define the config where to copy the info
+     */
+    public copyTo(brdfConfiguration: PBRBRDFConfiguration): void {
+        SerializationHelper.Clone(() => brdfConfiguration, this);
+    }
+
+    /**
+     * Serializes this BRDF configuration.
+     * @returns - An object with the serialized config.
+     */
+    public serialize(): any {
+        return SerializationHelper.Serialize(this);
+    }
+
+    /**
+     * Parses a BRDF Configuration from a serialized object.
+     * @param source - Serialized object.
+     */
+    public parse(source: any): void {
+        SerializationHelper.Parse(() => this, source, null);
+    }
+}

+ 11 - 2
src/Materials/PBR/pbrBaseMaterial.ts

@@ -16,6 +16,7 @@ import { _TimeToken } from "../../Instrumentation/timeToken";
 import { _DepthCullingState, _StencilState, _AlphaState } from "../../States/index";
 import { IMaterialClearCoatDefines, PBRClearCoatConfiguration } from "./pbrClearCoatConfiguration";
 import { IMaterialAnisotropicDefines, PBRAnisotropicConfiguration } from "./pbrAnisotropicConfiguration";
+import { IMaterialBRDFDefines, PBRBRDFConfiguration } from "./pbrBRDFConfiguration";
 
 import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from "../../Materials/imageProcessingConfiguration";
 import { Effect, EffectFallbacks, EffectCreationOptions } from "../../Materials/effect";
@@ -40,7 +41,7 @@ import "../../Shaders/pbr.vertex";
  * @hiddenChildren
  */
 class PBRMaterialDefines extends MaterialDefines
-    implements IImageProcessingConfigurationDefines, IMaterialClearCoatDefines, IMaterialAnisotropicDefines {
+    implements IImageProcessingConfigurationDefines, IMaterialClearCoatDefines, IMaterialAnisotropicDefines, IMaterialBRDFDefines {
     public PBR = true;
 
     public MAINUV1 = false;
@@ -192,6 +193,9 @@ class PBRMaterialDefines extends MaterialDefines
 
     public ANISOTROPIC = false;
 
+    public BRDF_V_HEIGHT_CORRELATED = false;
+    public MS_BRDF_ENERGY_CONSERVATION = false;
+
     public UNLIT = false;
 
     /**
@@ -245,7 +249,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
      * Defines the default value of how much AO map is occluding the analytical lights
      * (point spot...).
      */
-    public static DEFAULT_AO_ON_ANALYTICAL_LIGHTS = 1;
+    public static DEFAULT_AO_ON_ANALYTICAL_LIGHTS = 0;
 
     /**
      * PBRMaterialLightFalloff Physical: light is falling off following the inverse squared distance law.
@@ -668,6 +672,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     public readonly anisotropy = new PBRAnisotropicConfiguration(this._markAllSubMeshesAsMiscDirty.bind(this));
 
     /**
+     * Defines the BRDF parameters for the material.
+     */
+    public readonly brdf = new PBRBRDFConfiguration(this._markAllSubMeshesAsMiscDirty.bind(this));
+    /**
      * Instantiates a new PBRMaterial instance.
      *
      * @param name The material name
@@ -1426,6 +1434,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         // External config
         this.clearCoat.prepareDefines(defines, scene);
         this.anisotropy.prepareDefines(defines, mesh);
+        this.brdf.prepareDefines(defines);
 
         // Values that need to be evaluated on every frame
         MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances ? true : false, useClipPlane);

+ 16 - 2
src/Materials/PBR/pbrMaterial.ts

@@ -496,8 +496,12 @@ export class PBRMaterial extends PBRBaseMaterial {
     public useLinearAlphaFresnel = false;
 
     /**
-     * A fresnel is applied to the alpha of the model to ensure grazing angles edges are not alpha tested.
-     * And/Or occlude the blended part.
+     * Let user defines the brdf lookup texture used for IBL.
+     * A default 8bit version is embedded but you could point at :
+     * * Default texture: https://assets.babylonjs.com/environments/correlatedMSBRDF.png
+     * * Default 16bit pixel depth texture: https://assets.babylonjs.com/environments/correlatedMSBRDF.dds
+     * * LEGACY Default None correlated https://assets.babylonjs.com/environments/uncorrelatedBRDF.png
+     * * LEGACY Default None correlated 16bit pixel depth https://assets.babylonjs.com/environments/uncorrelatedBRDF.dds
      */
     @serializeAsTexture()
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
@@ -693,6 +697,8 @@ export class PBRMaterial extends PBRBaseMaterial {
         clone.name = name;
 
         this.clearCoat.copyTo(clone.clearCoat);
+        this.anisotropy.copyTo(clone.anisotropy);
+        this.brdf.copyTo(clone.brdf);
 
         return clone;
     }
@@ -706,6 +712,8 @@ export class PBRMaterial extends PBRBaseMaterial {
         serializationObject.customType = "BABYLON.PBRMaterial";
 
         serializationObject.clearCoat = this.clearCoat.serialize();
+        serializationObject.anisotropy = this.anisotropy.serialize();
+        serializationObject.brdf = this.brdf.serialize();
 
         return serializationObject;
     }
@@ -723,6 +731,12 @@ export class PBRMaterial extends PBRBaseMaterial {
         if (source.clearCoat) {
             material.clearCoat.parse(source.clearCoat);
         }
+        if (source.anisotropy) {
+            material.anisotropy.parse(source.anisotropy);
+        }
+        if (source.brdf) {
+            material.brdf.parse(source.brdf);
+        }
         return material;
     }
 }

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

@@ -90,6 +90,8 @@ export class PBRMetallicRoughnessMaterial extends PBRBaseSimpleMaterial {
         clone.name = name;
 
         this.clearCoat.copyTo(clone.clearCoat);
+        this.anisotropy.copyTo(clone.anisotropy);
+        this.brdf.copyTo(clone.brdf);
 
         return clone;
     }
@@ -102,6 +104,8 @@ export class PBRMetallicRoughnessMaterial extends PBRBaseSimpleMaterial {
         serializationObject.customType = "BABYLON.PBRMetallicRoughnessMaterial";
 
         serializationObject.clearCoat = this.clearCoat.serialize();
+        serializationObject.anisotropy = this.anisotropy.serialize();
+        serializationObject.brdf = this.brdf.serialize();
 
         return serializationObject;
     }
@@ -114,6 +118,12 @@ export class PBRMetallicRoughnessMaterial extends PBRBaseSimpleMaterial {
         if (source.clearCoat) {
             material.clearCoat.parse(source.clearCoat);
         }
+        if (source.anisotropy) {
+            material.anisotropy.parse(source.anisotropy);
+        }
+        if (source.brdf) {
+            material.brdf.parse(source.brdf);
+        }
         return material;
     }
 }

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

@@ -80,6 +80,8 @@ export class PBRSpecularGlossinessMaterial extends PBRBaseSimpleMaterial {
         clone.name = name;
 
         this.clearCoat.copyTo(clone.clearCoat);
+        this.anisotropy.copyTo(clone.anisotropy);
+        this.brdf.copyTo(clone.brdf);
 
         return clone;
     }
@@ -92,6 +94,8 @@ export class PBRSpecularGlossinessMaterial extends PBRBaseSimpleMaterial {
         serializationObject.customType = "BABYLON.PBRSpecularGlossinessMaterial";
 
         serializationObject.clearCoat = this.clearCoat.serialize();
+        serializationObject.anisotropy = this.anisotropy.serialize();
+        serializationObject.brdf = this.brdf.serialize();
 
         return serializationObject;
     }
@@ -104,6 +108,12 @@ export class PBRSpecularGlossinessMaterial extends PBRBaseSimpleMaterial {
         if (source.clearCoat) {
             material.clearCoat.parse(source.clearCoat);
         }
+        if (source.anisotropy) {
+            material.anisotropy.parse(source.anisotropy);
+        }
+        if (source.brdf) {
+            material.brdf.parse(source.brdf);
+        }
         return material;
     }
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 3 - 3
src/Misc/textureTools.ts


+ 82 - 18
src/Shaders/ShadersInclude/pbrFunctions.fx

@@ -1,5 +1,6 @@
 // Constants
 #define RECIPROCAL_PI2 0.15915494
+#define RECIPROCAL_PI 0.31830988618
 #define FRESNEL_MAXIMUM_ON_ROUGH 0.25
 
 // AlphaG epsilon to avoid numerical issues
@@ -33,6 +34,34 @@ vec2 getAARoughnessFactors(vec3 normalVector) {
     #endif
 }
 
+
+#ifdef MS_BRDF_ENERGY_CONSERVATION
+    // http://www.jcgt.org/published/0008/01/03/
+    // http://advances.realtimerendering.com/s2018/Siggraph%202018%20HDRP%20talk_with%20notes.pdf
+    vec3 getEnergyConservationFactor(const vec3 specularEnvironmentR0, vec2 environmentBrdf) {
+        return 1.0 + specularEnvironmentR0 * (1.0 / environmentBrdf.y - 1.0);
+    }
+#endif
+
+vec2 getBRDFLookup(float NdotV, float perceptualRoughness, sampler2D brdfSampler) {
+    // Indexed on cos(theta) and roughness
+    vec2 UV = vec2(NdotV, perceptualRoughness);
+    
+    // We can find the scale and offset to apply to the specular value.
+    vec2 brdfLookup = texture2D(brdfSampler, UV).xy;
+
+    return brdfLookup;
+}
+
+vec3 getReflectanceFromBRDFLookup(const vec3 specularEnvironmentR0, vec2 environmentBrdf) {
+    #ifdef BRDF_V_HEIGHT_CORRELATED
+        vec3 reflectance = mix(environmentBrdf.xxx, environmentBrdf.yyy, specularEnvironmentR0);
+    #else
+        vec3 reflectance = specularEnvironmentR0 * environmentBrdf.x + environmentBrdf.y;
+    #endif
+    return reflectance;
+}
+
 // Schlick's approximation for R0 (Fresnel Reflectance Values)
 // Keep for references
 // vec3 getR0fromAirToSurfaceIOR(vec3 ior1) {
@@ -57,24 +86,24 @@ vec2 getAARoughnessFactors(vec3 normalVector) {
 // }
 
 #ifdef CLEARCOAT
-// Knowing ior clear coat is fix for the material
-// Solving iorbase = 1 + sqrt(fo) / (1 - sqrt(fo)) and f0base = square((iorbase - iorclearcoat) / (iorbase - iorclearcoat))
-// provide f0base = square(A + B * sqrt(fo)) / (B + A * sqrt(fo))
-// where A = 1 - iorclearcoat
-// and   B = 1 + iorclearcoat
-vec3 getR0RemappedForClearCoat(vec3 f0) {
-    #ifdef CLEARCOAT_DEFAULTIOR
-        #ifdef MOBILE
-            return clamp(f0 * (f0 * 0.526868 + 0.529324) - 0.0482256, 0., 1.);
+    // Knowing ior clear coat is fix for the material
+    // Solving iorbase = 1 + sqrt(fo) / (1 - sqrt(fo)) and f0base = square((iorbase - iorclearcoat) / (iorbase - iorclearcoat))
+    // provide f0base = square(A + B * sqrt(fo)) / (B + A * sqrt(fo))
+    // where A = 1 - iorclearcoat
+    // and   B = 1 + iorclearcoat
+    vec3 getR0RemappedForClearCoat(vec3 f0) {
+        #ifdef CLEARCOAT_DEFAULTIOR
+            #ifdef MOBILE
+                return clamp(f0 * (f0 * 0.526868 + 0.529324) - 0.0482256, 0., 1.);
+            #else
+                return clamp(f0 * (f0 * (0.941892 - 0.263008 * f0) + 0.346479) - 0.0285998, 0., 1.);
+            #endif
         #else
-            return clamp(f0 * (f0 * (0.941892 - 0.263008 * f0) + 0.346479) - 0.0285998, 0., 1.);
+            vec3 s = sqrt(f0);
+            vec3 t = (vClearCoatRefractionParams.z + vClearCoatRefractionParams.w * s) / (vClearCoatRefractionParams.w + vClearCoatRefractionParams.z * s);
+            return t * t;
         #endif
-    #else
-        vec3 s = sqrt(f0);
-        vec3 t = (vClearCoatRefractionParams.z + vClearCoatRefractionParams.w * s) / (vClearCoatRefractionParams.w + vClearCoatRefractionParams.z * s);
-        return t * t;
-    #endif
-}
+    }
 #endif
 
 // From Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007
@@ -139,6 +168,18 @@ vec2 getAnisotropicRoughness(float alphaG, float anisotropy) {
     return vec2(alphaT, alphaB);
 }
 
+// Aniso Bent Normals
+// Mc Alley https://www.gdcvault.com/play/1022235/Rendering-the-World-of-Far 
+vec3 getAnisotropicBentNormals(const mat3 TBN, const vec3 V, const vec3 N, float anisotropy) {
+    vec3 anisotropicFrameDirection = anisotropy >= 0.0 ? TBN[1] : TBN[0];
+    vec3 anisotropicFrameTangent = cross(normalize(anisotropicFrameDirection), V);
+    vec3 anisotropicFrameNormal = cross(anisotropicFrameTangent, anisotropicFrameDirection);
+    vec3 anisotropicNormal = normalize(mix(N, anisotropicFrameNormal, abs(anisotropy)));
+    return anisotropicNormal;
+
+    // should we also do http://advances.realtimerendering.com/s2018/Siggraph%202018%20HDRP%20talk_with%20notes.pdf page 80 ?
+}
+
 // GGX Distribution Anisotropic
 // https://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf Addenda
 float normalDistributionFunction_BurleyGGX_Anisotropic(float NdotH, float TdotH, float BdotH, const vec2 alphaTB) {
@@ -146,7 +187,24 @@ float normalDistributionFunction_BurleyGGX_Anisotropic(float NdotH, float TdotH,
     vec3 v = vec3(alphaTB.y * TdotH, alphaTB.x  * BdotH, a2 * NdotH);
     float v2 = dot(v, v);
     float w2 = a2 / v2;
-    return a2 * w2 * w2 * (1.0 / PI);
+    return a2 * w2 * w2 * RECIPROCAL_PI;
+}
+
+// GGX Mask/Shadowing Isotropic 
+// Heitz http://jcgt.org/published/0003/02/03/paper.pdf
+// https://twvideo01.ubm-us.net/o1/vault/gdc2017/Presentations/Hammon_Earl_PBR_Diffuse_Lighting.pdf
+float smithVisibility_GGXCorrelated(float NdotV, float NdotL, float alphaG) {
+    #ifdef MOBILE
+        // Appply simplification as all squared root terms are below 1 and squared
+        float GGXV = NdotL * (NdotV * (1.0 - alphaG) + alphaG);
+        float GGXL = NdotV * (NdotL * (1.0 - alphaG) + alphaG);
+        return 0.5 / (GGXV + GGXL);
+    #else
+        float a2 = alphaG * alphaG;
+        float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
+        float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
+        return 0.5 / (GGXV + GGXL);
+    #endif
 }
 
 // GGX Mask/Shadowing Anisotropic 
@@ -209,7 +267,13 @@ vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, flo
     float alphaG = convertRoughnessToAverageSlope(roughness);
 
     float distribution = normalDistributionFunction_TrowbridgeReitzGGX(NdotH, alphaG);
-    float visibility = smithVisibility_TrowbridgeReitzGGXFast(NdotL, NdotV, alphaG);
+
+    #ifdef BRDF_V_HEIGHT_CORRELATED
+        float visibility = smithVisibility_GGXCorrelated(NdotL, NdotV, alphaG);
+    #else
+        float visibility = smithVisibility_TrowbridgeReitzGGXFast(NdotL, NdotV, alphaG);
+    #endif
+
     float specTerm = max(0., visibility * distribution);
 
     vec3 fresnel = fresnelSchlickGGX(VdotH, reflectance0, reflectance90);

+ 28 - 22
src/Shaders/pbr.fragment.fx

@@ -485,10 +485,7 @@ void main(void) {
     #endif
 
     #ifdef ANISOTROPIC
-        vec3 anisotropicFrameDirection = anisotropy >= 0.0 ? TBN[1] : TBN[0];
-        vec3 anisotropicFrameTangent = cross(normalize(anisotropicFrameDirection), viewDirectionW);
-        vec3 anisotropicFrameNormal = cross(anisotropicFrameTangent, anisotropicFrameDirection);
-        vec3 anisotropicNormal = normalize(mix(normalW, anisotropicFrameNormal, abs(anisotropy)));
+        vec3 anisotropicNormal = getAnisotropicBentNormals(TBN, viewDirectionW, normalW, anisotropy);
     #endif
 
     // _____________________________ Refraction Info _______________________________________
@@ -751,8 +748,10 @@ void main(void) {
             clearCoatNormalW = gl_FrontFacing ? clearCoatNormalW : -clearCoatNormalW;
         #endif
 
-        // Clear Coat AA
-        vec2 clearCoatAARoughnessFactors = getAARoughnessFactors(clearCoatNormalW.xyz);
+        #ifdef SPECULARAA
+            // Clear Coat AA
+            vec2 clearCoatAARoughnessFactors = getAARoughnessFactors(clearCoatNormalW.xyz);
+        #endif
 
         // Compute N dot V.
         float clearCoatNdotVUnclamped = dot(clearCoatNormalW, viewDirectionW);
@@ -826,10 +825,9 @@ void main(void) {
             #endif
 
             #ifdef CLEARCOAT_TINT
+                // Used later on in the light fragment and ibl.
                 vec3 clearCoatVRefract = -refract(vPositionW, clearCoatNormalW, vClearCoatRefractionParams.y);
                 float clearCoatNdotVRefract = clamp(dot(clearCoatNormalW, clearCoatVRefract), 0.00000000001, 1.0);
-
-                // Used later on in the light fragment and ibl.
                 vec3 absorption = vec3(0.);
             #endif
 
@@ -839,6 +837,15 @@ void main(void) {
         #endif
     #endif
 
+    #if defined(ENVIRONMENTBRDF)
+        // BRDF Lookup
+        vec2 environmentBrdf = getBRDFLookup(NdotV, roughness, environmentBrdfSampler);
+
+        #ifdef MS_BRDF_ENERGY_CONSERVATION
+            vec3 energyConservationFactor = getEnergyConservationFactor(specularEnvironmentR0, environmentBrdf);
+        #endif
+    #endif
+
     // ____________________________________________________________________________________
     // _____________________________ Direct Lighting Info __________________________________
     vec3 diffuseBase = vec3(0., 0., 0.);
@@ -866,13 +873,7 @@ void main(void) {
 
     // _________________________ Specular Environment Oclusion __________________________
     #if defined(ENVIRONMENTBRDF) && !defined(REFLECTIONMAP_SKYBOX)
-        // Indexed on cos(theta) and roughness
-        vec2 brdfSamplerUV = vec2(NdotV, roughness);
-        
-        // We can find the scale and offset to apply to the specular value.
-        vec4 environmentBrdf = texture2D(environmentBrdfSampler, brdfSamplerUV);
-
-        vec3 specularEnvironmentReflectance = specularEnvironmentR0 * environmentBrdf.x + environmentBrdf.y;
+        vec3 specularEnvironmentReflectance = getReflectanceFromBRDFLookup(specularEnvironmentR0, environmentBrdf);
 
         #ifdef RADIANCEOCCLUSION
             #ifdef AMBIENTINGRAYSCALE
@@ -901,13 +902,9 @@ void main(void) {
     // _________________________ 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(vClearCoatRefractionParams.x * environmentClearCoatBrdf.x + environmentClearCoatBrdf.y);
+            // BRDF Lookup
+            vec2 environmentClearCoatBrdf = getBRDFLookup(clearCoatNdotV, clearCoatRoughness, environmentBrdfSampler);
+            vec3 clearCoatEnvironmentReflectance = getReflectanceFromBRDFLookup(vec3(vClearCoatRefractionParams.x), environmentClearCoatBrdf);
 
             #ifdef RADIANCEOCCLUSION
                 float clearCoatSeo = environmentRadianceOcclusion(ambientMonochrome, clearCoatNdotVUnclamped);
@@ -1008,6 +1005,9 @@ void main(void) {
 
         // Full value needed for alpha.
         vec3 finalSpecularScaled = finalSpecular * vLightingIntensity.x * vLightingIntensity.w;
+        #ifdef MS_BRDF_ENERGY_CONSERVATION
+            finalSpecularScaled *= energyConservationFactor;
+        #endif
     #endif
 
     // _____________________________ Radiance ________________________________________
@@ -1017,6 +1017,9 @@ void main(void) {
 
         // Full value needed for alpha. 
         vec3 finalRadianceScaled = finalRadiance * vLightingIntensity.z;
+        #ifdef MS_BRDF_ENERGY_CONSERVATION
+            finalRadianceScaled *= energyConservationFactor;
+        #endif
     #endif
 
     // _____________________________ Refraction ______________________________________
@@ -1032,6 +1035,9 @@ void main(void) {
 
         // Full value needed for alpha.
         vec3 finalClearCoatScaled = finalClearCoat * vLightingIntensity.x * vLightingIntensity.w;
+        #ifdef MS_BRDF_ENERGY_CONSERVATION
+            finalClearCoatScaled *= energyConservationFactor;
+        #endif
 
     // ____________________________ Clear Coat Radiance _______________________________
         #ifdef REFLECTION