Просмотр исходного кода

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js into nme-particle

Popov72 5 лет назад
Родитель
Сommit
7608746d20
36 измененных файлов с 1181 добавлено и 139 удалено
  1. 10 1
      Tools/Gulp/helpers/gulp-processConstants.js
  2. 17 6
      Tools/Gulp/tasks/gulpTasks-localRun.js
  3. 10 1
      dist/preview release/what's new.md
  4. 4 4
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx
  5. 79 0
      loaders/src/glTF/2.0/Extensions/KHR_materials_ior.ts
  6. 14 5
      loaders/src/glTF/2.0/Extensions/KHR_materials_specular.ts
  7. 1 0
      loaders/src/glTF/2.0/Extensions/index.ts
  8. 3 1
      src/Engines/Processors/shaderProcessor.ts
  9. 12 0
      src/Engines/constants.ts
  10. 2 11
      src/Materials/Node/Blocks/PBR/reflectivityBlock.ts
  11. 108 57
      src/Materials/PBR/pbrBaseMaterial.ts
  12. 33 13
      src/Materials/PBR/pbrMaterial.ts
  13. 7 3
      src/Materials/PBR/pbrSubSurfaceConfiguration.ts
  14. 209 0
      src/Materials/Textures/Filtering/hdrFiltering.ts
  15. 30 0
      src/Materials/Textures/baseTexture.ts
  16. 14 3
      src/Materials/Textures/hdrCubeTexture.ts
  17. 4 3
      src/Materials/Textures/index.ts
  18. 54 6
      src/Materials/effectRenderer.ts
  19. 13 1
      src/Probes/reflectionProbe.ts
  20. 224 0
      src/Shaders/ShadersInclude/hdrFilteringFunctions.fx
  21. 48 0
      src/Shaders/ShadersInclude/helperFunctions.fx
  22. 155 0
      src/Shaders/ShadersInclude/importanceSampling.fx
  23. 19 6
      src/Shaders/ShadersInclude/pbrBRDFFunctions.fx
  24. 1 1
      src/Shaders/ShadersInclude/pbrBlockReflectance.fx
  25. 12 2
      src/Shaders/ShadersInclude/pbrBlockReflectance0.fx
  26. 6 3
      src/Shaders/ShadersInclude/pbrBlockReflection.fx
  27. 4 8
      src/Shaders/ShadersInclude/pbrBlockReflectivity.fx
  28. 1 0
      src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx
  29. 16 1
      src/Shaders/ShadersInclude/pbrFragmentSamplersDeclaration.fx
  30. 4 0
      src/Shaders/ShadersInclude/pbrUboDeclaration.fx
  31. 5 0
      src/Shaders/ShadersInclude/pbrVertexDeclaration.fx
  32. 15 0
      src/Shaders/hdrFiltering.fragment.fx
  33. 16 0
      src/Shaders/hdrFiltering.vertex.fx
  34. 13 0
      src/Shaders/pbr.fragment.fx
  35. 15 0
      src/Shaders/pbr.vertex.fx
  36. 3 3
      tests/validation/config.json

+ 10 - 1
Tools/Gulp/helpers/gulp-processConstants.js

@@ -2,13 +2,22 @@
 var through = require('through2');
 var PluginError = require('plugin-error');
 const fs = require('fs');
-const babylonConstants = require(__dirname + '/../../../dist/preview release/babylon.max').Constants;
+const constantModule = __dirname + '/../../../dist/preview release/babylon.max';
 
+let _babylonConstants = undefined;
+function getBabylonConstants() {
+    if (!_babylonConstants) {
+        _babylonConstants = require(constantModule).Constants;
+    }
+    return _babylonConstants;
+}
 
 /**
  * Replace all constants by their inlined values.
  */
 function processConstants(sourceCode) {
+    const babylonConstants = getBabylonConstants();
+
     var regexImport = /import { Constants } from .*;/g;
     sourceCode = sourceCode.replace(regexImport, "");
 

+ 17 - 6
Tools/Gulp/tasks/gulpTasks-localRun.js

@@ -28,13 +28,23 @@ gulp.task("webserver", function () {
         middleware: function (connect, opt) {
             return [function (req, res, next) {
                 const baseUrl =  (req.url.indexOf('dist') !== -1 || req.url.indexOf('Tools') !== -1  || req.url.indexOf('temp/') !== -1);
-                if (!baseUrl && req.headers['referer'] && req.headers['referer'].indexOf('/Playground/') !== -1 && req.url.indexOf('/Playground/') === -1) {
-                    req.url = "/Playground/" + req.url;
-                    res.writeHead(301, {
-                        'Location': req.url
-                    });
-                    return res.end();
+                let referer = req.headers['referer'];
+                if (!baseUrl && referer) {
+                    referer = referer.toLowerCase();
+                    if (referer.indexOf('/playground/') !== -1 && req.url.indexOf('/Playground/') === -1) {
+                        req.url = "/Playground/" + req.url;
+                        res.writeHead(301, {
+                            'Location': req.url
+                        });
+                        return res.end();
+                    }
+                    if (referer.indexOf('/localdev/') !== -1 && referer.indexOf(req.originalUrl) === -1) {
+                        if (!fs.existsSync(rootRelativePath + req.originalUrl)) {
+                            req.url = "/Playground/" + req.url.replace(/localDev/ig, "");
+                        }
+                    }
                 }
+
                 const pgMath = req.url.match(/\/Playground\/pg\/(.*)/);
                 if (pgMath) {
                     const isAFile = req.url.indexOf('.') !== -1;
@@ -61,6 +71,7 @@ gulp.task("webserver", function () {
                         req.url += ".js";
                     }
                 }
+
                 next();
             }]
         }

+ 10 - 1
dist/preview release/what's new.md

@@ -8,6 +8,7 @@
 - Added Babylon.js Texture [tools](https://www.babylonjs.com/tools/ibl) to prefilter HDR files ([Sebavan](https://github.com/sebavan/))
 - Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
 - Added Curve editor to view selected entity's animations in the Inspector ([pixelspace](https://github.com/devpixelspace))
+- Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 
 ## Updates
 
@@ -21,7 +22,6 @@
 - Added support for `material.disableColorWrite` ([Deltakosh](https://github.com/deltakosh))
 - The Mesh Asset Task also accepts File as sceneInput ([RaananW](https://github.com/RaananW))
 - Added support preserving vert colors for CSG objects ([PirateJC](https://github.com/PirateJC))
-- Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 - Added `boundingBoxRenderer.onBeforeBoxRenderingObservable` and `boundingBoxRenderer.onAfterBoxRenderingObservable` ([Deltakosh](https://github.com/deltakosh))
 
 ### Engine
@@ -71,6 +71,8 @@
 - Get the list of cameras retrieved from a gLTF file when loaded through the asset container ([Popov72](https://github.com/Popov72))
 - Fixed SceneLoader.ImportAnimations. Now targets nodes based on "targetProperty" ([#7931](https://github.com/BabylonJS/Babylon.js/issues/7931)) ([phenry20](https://github.com/phenry20))
 - Renamed KHR_mesh_instancing extension to EXT_mesh_gpu_instancing ([#7945](https://github.com/BabylonJS/Babylon.js/issues/7945)) ([drigax](https://github.com/Drigax))
+- Added support for KHR_materials_ior for glTF loader. ([Sebavan](https://github.com/sebavan/))
+- Added support for KHR_materials_specular for glTF loader. ([Sebavan](https://github.com/sebavan/))
 
 ### Navigation
 
@@ -86,6 +88,7 @@
 - `setTexture` and `setTextureArray` from `ShaderMaterial` take now a `BaseTexture` as input instead of a `Texture`, allowing to pass a `CubeTexture` ([Popov72](https://github.com/Popov72))
 - Allow parenthesis usage in `#if` expressions in shader code ([Popov72](https://github.com/Popov72))
 - Added to `StandardMaterial` RGBD ReflectionTexture, RefractionTexture and LightmapTexture support. ([MackeyK24](https://github.com/MackeyK24))
+- Allow using the single comment syntax `// comment` in a `#if` construct in shader code ([Popov72](https://github.com/Popov72))
 
 ### WebXR
 
@@ -120,6 +123,11 @@
 - Added local space support for GPU particles ([CraigFeldpsar](https://github.com/craigfeldspar))
 - Added ability to update also colors and uvs of solid particle vertices ([jerome](https://github.com/jbousquie))
 
+### Textures
+
+- .HDR environment files will now give accurate PBR reflections ([CraigFeldpsar](https://github.com/craigfeldspar))
+- Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar))
+
 ### Build
 
 - Fixed an issue with gulp webpack, webpack stream and the viewer ([RaananW](https://github.com/RaananW))
@@ -181,3 +189,4 @@
 - `EffectRenderer.render` now takes a `RenderTargetTexture` or an `InternalTexture` as the output texture and only a single `EffectWrapper` for its first argument ([Popov72](https://github.com/Popov72))
 - Sound's `updateOptions` takes `options.length` and `options.offset` as seconds and not milliseconds ([RaananW](https://github.com/RaananW))
 - HDRCubeTexture default rotation is now similar to the industry one. You might need to add a rotation on y of 90 degrees if you scene changes ([Sebavan](https://github.com/sebavan/))
+- PBRMaterial index of refraction is now defined as index of refraction and not the inverse of it ([Sebavan](https://github.com/sebavan/))

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

@@ -151,11 +151,11 @@ export class PBRMaterialPropertyGridComponent extends React.Component<IPBRMateri
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="METALLIC WORKFLOW">
                     <SliderLineComponent label="Metallic" target={material} propertyName="metallic" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent label="Metallic F0" target={material} propertyName="metallicF0Factor" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <CheckBoxLineComponent label="Metallic F0 From Map" target={material} propertyName="useMetallicF0FactorFromMetallicTexture"
-                        onValueChanged={() => this.forceUpdate()}
-                        onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <SliderLineComponent label="Roughness" target={material} propertyName="roughness" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="Index of Refraction" target={material} propertyName="indexOfRefraction" minimum={1} maximum={2} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent label="F0 Factor" target={material} propertyName="metallicF0Factor" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <Color3LineComponent label="Reflectance Color" target={material} propertyName="metallicReflectanceColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} isLinear={true} />
+                    <TextureLinkLineComponent label="Reflectance Texture" texture={material.metallicReflectanceTexture} onTextureCreated={(texture) => material.metallicReflectanceTexture = texture} onTextureRemoved={() => material.metallicReflectanceTexture = null} material={material} onSelectionChangedObservable={this.props.onSelectionChangedObservable} onDebugSelectionChangeObservable={this._onDebugSelectionChangeObservable} />
                 </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="CLEAR COAT">
                     <CheckBoxLineComponent label="Enabled" target={material.clearCoat} propertyName="isEnabled"

+ 79 - 0
loaders/src/glTF/2.0/Extensions/KHR_materials_ior.ts

@@ -0,0 +1,79 @@
+import { Nullable } from "babylonjs/types";
+import { PBRMaterial } from "babylonjs/Materials/PBR/pbrMaterial";
+import { Material } from "babylonjs/Materials/material";
+
+import { IMaterial } from "../glTFLoaderInterfaces";
+import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
+import { GLTFLoader } from "../glTFLoader";
+
+const NAME = "KHR_materials_ior";
+
+interface IKHR_materials_ior {
+    ior: number;
+}
+
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1718)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+export class KHR_materials_ior implements IGLTFLoaderExtension {
+    /**
+     * Default ior Value from the spec.
+     */
+    private static readonly _DEFAULT_IOR = 1.5;
+
+    /**
+     * The name of this extension.
+     */
+    public readonly name = NAME;
+
+    /**
+     * Defines whether this extension is enabled.
+     */
+    public enabled: boolean;
+
+    /**
+     * Defines a number that determines the order the extensions are applied.
+     */
+    public order = 180;
+
+    private _loader: GLTFLoader;
+
+    /** @hidden */
+    constructor(loader: GLTFLoader) {
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+
+    /** @hidden */
+    public dispose() {
+        delete this._loader;
+    }
+
+    /** @hidden */
+    public loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
+        return GLTFLoader.LoadExtensionAsync<IKHR_materials_ior>(context, material, this.name, (extensionContext, extension) => {
+            const promises = new Array<Promise<any>>();
+            promises.push(this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial));
+            promises.push(this._loadIorPropertiesAsync(extensionContext, extension, babylonMaterial));
+            return Promise.all(promises).then(() => { });
+        });
+    }
+
+    private _loadIorPropertiesAsync(context: string, properties: IKHR_materials_ior, babylonMaterial: Material): Promise<void> {
+        if (!(babylonMaterial instanceof PBRMaterial)) {
+            throw new Error(`${context}: Material type not supported`);
+        }
+
+        if (properties.ior !== undefined) {
+            babylonMaterial.indexOfRefraction = properties.ior;
+        }
+        else {
+            babylonMaterial.indexOfRefraction = KHR_materials_ior._DEFAULT_IOR;
+        }
+
+        return Promise.resolve();
+    }
+}
+
+GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_ior(loader));

+ 14 - 5
loaders/src/glTF/2.0/Extensions/KHR_materials_specular.ts

@@ -5,17 +5,18 @@ import { Material } from "babylonjs/Materials/material";
 import { IMaterial, ITextureInfo } from "../glTFLoaderInterfaces";
 import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
 import { GLTFLoader } from "../glTFLoader";
+import { Color3 } from 'babylonjs/Maths/math.color';
 
 const NAME = "KHR_materials_specular";
 
 interface IKHR_materials_specular {
     specularFactor: number;
+    specularColorFactor: number[];
     specularTexture: ITextureInfo;
 }
 
 /**
- * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1677)
- * [Playground Sample](https://www.babylonjs-playground.com/frame.html#BNIZX6#4)
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1719)
  * !!! Experimental Extension Subject to Changes !!!
  */
 export class KHR_materials_specular implements IGLTFLoaderExtension {
@@ -62,16 +63,24 @@ export class KHR_materials_specular implements IGLTFLoaderExtension {
             throw new Error(`${context}: Material type not supported`);
         }
 
+        const promises = new Array<Promise<any>>();
+
         if (properties.specularFactor !== undefined) {
             babylonMaterial.metallicF0Factor = properties.specularFactor;
         }
 
+        if (properties.specularColorFactor !== undefined) {
+            babylonMaterial.metallicReflectanceColor = Color3.FromArray(properties.specularColorFactor);
+        }
+
         if (properties.specularTexture) {
-            // This does not allow a separate sampler for it at the moment but is currently under discussion.
-            babylonMaterial.useMetallicF0FactorFromMetallicTexture = true;
+            promises.push(this._loader.loadTextureInfoAsync(`${context}/specularTexture`, properties.specularTexture, (texture) => {
+                texture.name = `${babylonMaterial.name} (Specular F0 Color)`;
+                babylonMaterial.metallicReflectanceTexture = texture;
+            }));
         }
 
-        return Promise.resolve();
+        return Promise.all(promises).then(() => { });
     }
 }
 

+ 1 - 0
loaders/src/glTF/2.0/Extensions/index.ts

@@ -7,6 +7,7 @@ export * from "./KHR_materials_unlit";
 export * from "./KHR_materials_clearcoat";
 export * from "./KHR_materials_sheen";
 export * from "./KHR_materials_specular";
+export * from "./KHR_materials_ior";
 export * from "./KHR_mesh_quantization";
 export * from "./KHR_texture_basisu";
 export * from "./KHR_texture_transform";

+ 3 - 1
src/Engines/Processors/shaderProcessor.ts

@@ -123,7 +123,9 @@ export class ShaderProcessor {
     private static _BuildExpression(line: string, start: number): ShaderCodeTestNode {
         let node = new ShaderCodeTestNode();
         let command = line.substring(0, start);
-        let expression = line.substring(start).trim();
+        let expression = line.substring(start);
+
+        expression = expression.substring(0, ((expression.indexOf("//") + 1) || (expression.length + 1)) - 1).trim();
 
         if (command === "#ifdef") {
             node.testExpression = new ShaderDefineIsDefinedOperator(expression);

+ 12 - 0
src/Engines/constants.ts

@@ -241,6 +241,18 @@ export class Constants {
     /** Equirectangular Fixed Mirrored coordinates mode */
     public static readonly TEXTURE_FIXED_EQUIRECTANGULAR_MIRRORED_MODE = 9;
 
+    /** Offline (baking) quality for texture filtering */
+    public static readonly TEXTURE_FILTERING_QUALITY_OFFLINE = 4096;
+
+    /** High quality for texture filtering */
+    public static readonly TEXTURE_FILTERING_QUALITY_HIGH = 64;
+
+    /** Medium quality for texture filtering */
+    public static readonly TEXTURE_FILTERING_QUALITY_MEDIUM = 16;
+
+    /** Low quality for texture filtering */
+    public static readonly TEXTURE_FILTERING_QUALITY_LOW = 8;
+
     // Texture rescaling mode
     /** Defines that texture rescaling will use a floor to find the closer power of 2 size */
     public static readonly SCALEMODE_FLOOR = 1;

+ 2 - 11
src/Materials/Node/Blocks/PBR/reflectivityBlock.ts

@@ -40,12 +40,6 @@ export class ReflectivityBlock extends NodeMaterialBlock {
     public useRoughnessFromMetallicTextureGreen: boolean = true;
 
     /**
-     * Specifies whether the F0 factor can be fetched from the mettalic texture.
-     */
-    @editableInPropertyPage("Metallic F0 from alpha channel", PropertyTypeForEdition.Boolean, "METALLIC WORKFLOW", { "notifiers": { "update": true }})
-    public useMetallicF0FactorFromMetallicTexture: boolean = false;
-
-    /**
      * Create a new ReflectivityBlock
      * @param name defines the block name
      */
@@ -122,9 +116,10 @@ export class ReflectivityBlock extends NodeMaterialBlock {
             reflectivityOutParams reflectivityOut;
 
             reflectivityBlock(
-                vec4(${this.metallic.associatedVariableName}, ${this.roughness.associatedVariableName}, 0., 0.04),
+                vec4(${this.metallic.associatedVariableName}, ${this.roughness.associatedVariableName}, 0., 0.),
             #ifdef METALLICWORKFLOW
                 surfaceAlbedo,
+                vec4(1.),
             #endif
             #ifdef REFLECTIVITY
                 vec3(0., 0., ${aoIntensityVarName}),
@@ -160,7 +155,6 @@ export class ReflectivityBlock extends NodeMaterialBlock {
         defines.setValue("METALLNESSSTOREINMETALMAPBLUE", this.useMetallnessFromMetallicTextureBlue, true);
         defines.setValue("ROUGHNESSSTOREINMETALMAPALPHA", this.useRoughnessFromMetallicTextureAlpha, true);
         defines.setValue("ROUGHNESSSTOREINMETALMAPGREEN",  !this.useRoughnessFromMetallicTextureAlpha && this.useRoughnessFromMetallicTextureGreen, true);
-        defines.setValue("METALLICF0FACTORFROMMETALLICMAP", this.useMetallicF0FactorFromMetallicTexture, true);
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {
@@ -178,7 +172,6 @@ export class ReflectivityBlock extends NodeMaterialBlock {
         codeString += `${this._codeVariableName}.useMetallnessFromMetallicTextureBlue = ${this.useMetallnessFromMetallicTextureBlue};\r\n`;
         codeString += `${this._codeVariableName}.useRoughnessFromMetallicTextureAlpha = ${this.useRoughnessFromMetallicTextureAlpha};\r\n`;
         codeString += `${this._codeVariableName}.useRoughnessFromMetallicTextureGreen = ${this.useRoughnessFromMetallicTextureGreen};\r\n`;
-        codeString += `${this._codeVariableName}.useMetallicF0FactorFromMetallicTexture = ${this.useMetallicF0FactorFromMetallicTexture};\r\n`;
 
         return codeString;
     }
@@ -190,7 +183,6 @@ export class ReflectivityBlock extends NodeMaterialBlock {
         serializationObject.useMetallnessFromMetallicTextureBlue = this.useMetallnessFromMetallicTextureBlue;
         serializationObject.useRoughnessFromMetallicTextureAlpha = this.useRoughnessFromMetallicTextureAlpha;
         serializationObject.useRoughnessFromMetallicTextureGreen = this.useRoughnessFromMetallicTextureGreen;
-        serializationObject.useMetallicF0FactorFromMetallicTexture = this.useMetallicF0FactorFromMetallicTexture;
 
         return serializationObject;
     }
@@ -202,7 +194,6 @@ export class ReflectivityBlock extends NodeMaterialBlock {
         this.useMetallnessFromMetallicTextureBlue = serializationObject.useMetallnessFromMetallicTextureBlue;
         this.useRoughnessFromMetallicTextureAlpha = serializationObject.useRoughnessFromMetallicTextureAlpha;
         this.useRoughnessFromMetallicTextureGreen = serializationObject.useRoughnessFromMetallicTextureGreen;
-        this.useMetallicF0FactorFromMetallicTexture = serializationObject.useMetallicF0FactorFromMetallicTexture;
     }
 }
 

+ 108 - 57
src/Materials/PBR/pbrBaseMaterial.ts

@@ -17,6 +17,7 @@ import { IMaterialBRDFDefines, PBRBRDFConfiguration } from "./pbrBRDFConfigurati
 import { IMaterialSheenDefines, PBRSheenConfiguration } from "./pbrSheenConfiguration";
 import { IMaterialSubSurfaceDefines, PBRSubSurfaceConfiguration } from "./pbrSubSurfaceConfiguration";
 import { Color3, TmpColors } from '../../Maths/math.color';
+import { Scalar } from "../../Maths/math.scalar";
 
 import { ImageProcessingConfiguration, IImageProcessingConfigurationDefines } from "../../Materials/imageProcessingConfiguration";
 import { Effect, IEffectCreationOptions } from "../../Materials/effect";
@@ -37,6 +38,7 @@ import { IAnimatable } from '../../Animations/animatable.interface';
 import "../../Materials/Textures/baseTexture.polynomial";
 import "../../Shaders/pbr.fragment";
 import "../../Shaders/pbr.vertex";
+
 import { EffectFallbacks } from '../effectFallbacks';
 
 const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
@@ -54,6 +56,9 @@ export class PBRMaterialDefines extends MaterialDefines
     IMaterialSubSurfaceDefines {
     public PBR = true;
 
+    public NUM_SAMPLES = "0u";
+    public REALTIME_FILTERING = false;
+
     public MAINUV1 = false;
     public MAINUV2 = false;
     public UV1 = false;
@@ -101,7 +106,8 @@ export class PBRMaterialDefines extends MaterialDefines
     public ROUGHNESSSTOREINMETALMAPGREEN = false;
     public METALLNESSSTOREINMETALMAPBLUE = false;
     public AOSTOREINMETALMAPRED = false;
-    public METALLICF0FACTORFROMMETALLICMAP = false;
+    public METALLIC_REFLECTANCE = false;
+    public METALLIC_REFLECTANCEDIRECTUV = 0;
 
     public ENVIRONMENTBRDF = false;
     public ENVIRONMENTBRDF_RGBD = false;
@@ -406,19 +412,32 @@ export abstract class PBRBaseMaterial extends PushMaterial {
     protected _roughness: Nullable<number> = null;
 
     /**
-     * Specifies the an F0 factor to help configuring the material F0.
-     * Instead of the default 4%, 8% * factor will be used. As the factor is defaulting
-     * to 0.5 the previously hard coded value stays the same.
-     * Can also be used to scale the F0 values of the metallic texture.
+     * In metallic workflow, specifies an F0 factor to help configuring the material F0.
+     * By default the indexOfrefraction is used to compute F0;
+     *
+     * This is used as a factor against the default reflectance at normal incidence to tweak it.
+     *
+     * F0 = defaultF0 * metallicF0Factor * metallicReflectanceColor;
+     * F90 = metallicReflectanceColor;
      */
-    protected _metallicF0Factor = 0.5;
+    protected _metallicF0Factor = 1;
 
     /**
-     * Specifies whether the F0 factor can be fetched from the mettalic texture.
-     * If set to true, please adapt the metallicF0Factor to ensure it fits with
-     * your expectation as it multiplies with the texture data.
+     * In metallic workflow, specifies an F90 color to help configuring the material F90.
+     * By default the F90 is always 1;
+     *
+     * Please note that this factor is also used as a factor against the default reflectance at normal incidence.
+     *
+     * F0 = defaultF0 * metallicF0Factor * metallicReflectanceColor
+     * F90 = metallicReflectanceColor;
      */
-    protected _useMetallicF0FactorFromMetallicTexture = false;
+    protected _metallicReflectanceColor = Color3.White();
+
+    /**
+     * Defines to store metallicReflectanceColor in RGB and metallicF0Factor in A
+     * This is multiply against the scalar values defined in the material.
+     */
+    protected _metallicReflectanceTexture: Nullable<BaseTexture> = null;
 
     /**
      * Used to enable roughness/glossiness fetch from a separate channel depending on the current mode.
@@ -938,6 +957,12 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                         }
                     }
 
+                    if (this._metallicReflectanceTexture) {
+                        if (!this._metallicReflectanceTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
                     if (this._microSurfaceTexture) {
                         if (!this._microSurfaceTexture.isReadyOrNotBlocking()) {
                             return false;
@@ -1033,6 +1058,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
     private _prepareEffect(mesh: AbstractMesh, defines: PBRMaterialDefines, onCompiled: Nullable<(effect: Effect) => void> = null, onError: Nullable<(effect: Effect, errors: string) => void> = null, useInstances: Nullable<boolean> = null, useClipPlane: Nullable<boolean> = null): Nullable<Effect> {
         this._prepareDefines(mesh, defines, useInstances, useClipPlane);
+
         if (!defines.isDirty) {
             return null;
         }
@@ -1156,12 +1182,12 @@ export abstract class PBRBaseMaterial extends PushMaterial {
 
         var shaderName = "pbr";
 
-        var uniforms = ["world", "view", "viewProjection", "vEyePosition", "vLightsType", "vAmbientColor", "vAlbedoColor", "vReflectivityColor", "vEmissiveColor", "visibility", "vReflectionColor",
+        var uniforms = ["world", "view", "viewProjection", "vEyePosition", "vLightsType", "vAmbientColor", "vAlbedoColor", "vReflectivityColor", "vMetallicReflectanceFactors", "vEmissiveColor", "visibility", "vReflectionColor",
             "vFogInfos", "vFogColor", "pointSize",
-            "vAlbedoInfos", "vAmbientInfos", "vOpacityInfos", "vReflectionInfos", "vReflectionPosition", "vReflectionSize", "vEmissiveInfos", "vReflectivityInfos",
+            "vAlbedoInfos", "vAmbientInfos", "vOpacityInfos", "vReflectionInfos", "vReflectionPosition", "vReflectionSize", "vEmissiveInfos", "vReflectivityInfos", "vMetallicReflectanceInfos",
             "vMicroSurfaceSamplerInfos", "vBumpInfos", "vLightmapInfos",
             "mBones",
-            "vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "vClipPlane5", "vClipPlane6", "albedoMatrix", "ambientMatrix", "opacityMatrix", "reflectionMatrix", "emissiveMatrix", "reflectivityMatrix", "normalMatrix", "microSurfaceSamplerMatrix", "bumpMatrix", "lightmapMatrix",
+            "vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "vClipPlane5", "vClipPlane6", "albedoMatrix", "ambientMatrix", "opacityMatrix", "reflectionMatrix", "emissiveMatrix", "reflectivityMatrix", "normalMatrix", "microSurfaceSamplerMatrix", "bumpMatrix", "lightmapMatrix", "metallicReflectanceMatrix",
             "vLightingIntensity",
             "logarithmicDepthConstant",
             "vSphericalX", "vSphericalY", "vSphericalZ",
@@ -1172,13 +1198,14 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             "vSphericalL2_2", "vSphericalL2_1", "vSphericalL20", "vSphericalL21", "vSphericalL22",
             "vReflectionMicrosurfaceInfos",
             "vTangentSpaceParams", "boneTextureWidth",
-            "vDebugMode"
+            "vDebugMode",
+            "vFilteringInfo", "linearRoughness"
         ];
 
         var samplers = ["albedoSampler", "reflectivitySampler", "ambientSampler", "emissiveSampler",
             "bumpSampler", "lightmapSampler", "opacitySampler",
             "reflectionSampler", "reflectionSamplerLow", "reflectionSamplerHigh", "irradianceSampler",
-            "microSurfaceSampler", "environmentBrdfSampler", "boneSampler"];
+            "microSurfaceSampler", "environmentBrdfSampler", "boneSampler", "metallicReflectanceSampler"];
 
         var uniformBuffers = ["Material", "Scene"];
 
@@ -1275,6 +1302,13 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                     defines.LODINREFLECTIONALPHA = reflectionTexture.lodLevelInAlpha;
                     defines.LINEARSPECULARREFLECTION = reflectionTexture.linearSpecularLOD;
 
+                    if (reflectionTexture.realTimeFiltering && reflectionTexture.realTimeFilteringQuality > 0) {
+                        defines.NUM_SAMPLES = reflectionTexture.realTimeFilteringQuality + "u";
+                        defines.REALTIME_FILTERING = true;
+                    } else {
+                        defines.REALTIME_FILTERING = false;
+                    }
+
                     if (reflectionTexture.coordinatesMode === Texture.INVCUBIC_MODE) {
                         defines.INVERTCUBICMAP = true;
                     }
@@ -1333,7 +1367,7 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                         else if (reflectionTexture.isCube) {
                             defines.USESPHERICALFROMREFLECTIONMAP = true;
                             defines.USEIRRADIANCEMAP = false;
-                            if (this._forceIrradianceInFragment || scene.getEngine().getCaps().maxVaryingVectors <= 8) {
+                            if (this._forceIrradianceInFragment || reflectionTexture.realTimeFiltering || scene.getEngine().getCaps().maxVaryingVectors <= 8) {
                                 defines.USESPHERICALINVERTEX = false;
                             }
                             else {
@@ -1387,7 +1421,6 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                         defines.ROUGHNESSSTOREINMETALMAPGREEN = !this._useRoughnessFromMetallicTextureAlpha && this._useRoughnessFromMetallicTextureGreen;
                         defines.METALLNESSSTOREINMETALMAPBLUE = this._useMetallnessFromMetallicTextureBlue;
                         defines.AOSTOREINMETALMAPRED = this._useAmbientOcclusionFromMetallicTextureRed;
-                        defines.METALLICF0FACTORFROMMETALLICMAP = this._useMetallicF0FactorFromMetallicTexture;
                     }
                     else if (this._reflectivityTexture) {
                         MaterialHelper.PrepareDefinesForMergedUV(this._reflectivityTexture, defines, "REFLECTIVITY");
@@ -1397,6 +1430,12 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                         defines.REFLECTIVITY = false;
                     }
 
+                    if (this._metallicReflectanceTexture) {
+                        MaterialHelper.PrepareDefinesForMergedUV(this._metallicReflectanceTexture, defines, "METALLIC_REFLECTANCE");
+                    } else {
+                        defines.METALLIC_REFLECTANCE = false;
+                    }
+
                     if (this._microSurfaceTexture) {
                         MaterialHelper.PrepareDefinesForMergedUV(this._microSurfaceTexture, defines, "MICROSURFACEMAP");
                     } else {
@@ -1572,6 +1611,9 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         ubo.addUniform("vReflectivityColor", 4);
         ubo.addUniform("vEmissiveColor", 3);
         ubo.addUniform("visibility", 1);
+        ubo.addUniform("vMetallicReflectanceFactors", 4);
+        ubo.addUniform("vMetallicReflectanceInfos", 2);
+        ubo.addUniform("metallicReflectanceMatrix", 16);
 
         PBRClearCoatConfiguration.PrepareUniformBuffer(ubo);
         PBRAnisotropicConfiguration.PrepareUniformBuffer(ubo);
@@ -1681,6 +1723,14 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                             ubo.updateVector3("vReflectionSize", cubeTexture.boundingBoxSize);
                         }
 
+                        if (reflectionTexture.realTimeFiltering) {
+                            const width = reflectionTexture.getSize().width;
+                            const alpha = this._roughness! * this._roughness!;
+
+                            this._activeEffect.setFloat2("vFilteringInfo", width, Scalar.Log2(width));
+                            this._activeEffect.setFloat("linearRoughness", alpha);
+                        }
+
                         if (!defines.USEIRRADIANCEMAP) {
                             var polynomials = reflectionTexture.sphericalPolynomial;
                             if (defines.USESPHERICALFROMREFLECTIONMAP && polynomials) {
@@ -1740,6 +1790,11 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                             MaterialHelper.BindTextureMatrix(this._reflectivityTexture, ubo, "reflectivity");
                         }
 
+                        if (this._metallicReflectanceTexture) {
+                            ubo.updateFloat2("vMetallicReflectanceInfos", this._metallicReflectanceTexture.coordinatesIndex, this._metallicReflectanceTexture.level);
+                            MaterialHelper.BindTextureMatrix(this._metallicReflectanceTexture, ubo, "metallicReflectance");
+                        }
+
                         if (this._microSurfaceTexture) {
                             ubo.updateFloat2("vMicroSurfaceSamplerInfos", this._microSurfaceTexture.coordinatesIndex, this._microSurfaceTexture.level);
                             MaterialHelper.BindTextureMatrix(this._microSurfaceTexture, ubo, "microSurfaceSampler");
@@ -1767,13 +1822,21 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                 if (defines.METALLICWORKFLOW) {
                     TmpColors.Color3[0].r = (this._metallic === undefined || this._metallic === null) ? 1 : this._metallic;
                     TmpColors.Color3[0].g = (this._roughness === undefined || this._roughness === null) ? 1 : this._roughness;
+                    ubo.updateColor4("vReflectivityColor", TmpColors.Color3[0], 1);
+
+                    const ior = this.subSurface.indexOfRefraction;
+                    const outside_ior = 1; // consider air as clear coat and other layaers would remap in the shader.
 
                     // We are here deriving our default reflectance from a common value for none metallic surface.
-                    // Default specular reflectance at normal incidence.
-                    // 4% corresponds to index of refraction (IOR) of 1.50, approximately equal to glass.
-                    // We then use 8% combined with a factor of 0.5 to allow some variations around the 0.04 default value.
-                    const metallicF0 = 0.08 * this._metallicF0Factor;
-                    ubo.updateColor4("vReflectivityColor", TmpColors.Color3[0], metallicF0);
+                    // Based of the schlick fresnel approximation model
+                    // for dielectrics.
+                    const f0 = Math.pow((ior - outside_ior) / (ior + outside_ior), 2);
+
+                    // Tweak the default F0 and F90 based on our given setup
+                    this._metallicReflectanceColor.scaleToRef(f0 * this._metallicF0Factor, TmpColors.Color3[0]);
+                    const metallicF90 = this._metallicF0Factor;
+
+                    ubo.updateColor4("vMetallicReflectanceFactors", TmpColors.Color3[0], metallicF90);
                 }
                 else {
                     ubo.updateColor4("vReflectivityColor", this._reflectivityColor, this._microSurface);
@@ -1849,6 +1912,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
                         ubo.setTexture("reflectivitySampler", this._reflectivityTexture);
                     }
 
+                    if (this._metallicReflectanceTexture) {
+                        ubo.setTexture("metallicReflectanceSampler", this._metallicReflectanceTexture);
+                    }
+
                     if (this._microSurfaceTexture) {
                         ubo.setTexture("microSurfaceSampler", this._microSurfaceTexture);
                     }
@@ -2010,6 +2077,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             activeTextures.push(this._metallicTexture);
         }
 
+        if (this._metallicReflectanceTexture) {
+            activeTextures.push(this._metallicReflectanceTexture);
+        }
+
         if (this._microSurfaceTexture) {
             activeTextures.push(this._microSurfaceTexture);
         }
@@ -2064,6 +2135,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             return true;
         }
 
+        if (this._metallicReflectanceTexture === texture) {
+            return true;
+        }
+
         if (this._microSurfaceTexture === texture) {
             return true;
         }
@@ -2089,45 +2164,21 @@ export abstract class PBRBaseMaterial extends PushMaterial {
      */
     public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void {
         if (forceDisposeTextures) {
-            if (this._albedoTexture) {
-                this._albedoTexture.dispose();
-            }
-
-            if (this._ambientTexture) {
-                this._ambientTexture.dispose();
-            }
-
-            if (this._opacityTexture) {
-                this._opacityTexture.dispose();
-            }
-
-            if (this._reflectionTexture) {
-                this._reflectionTexture.dispose();
-            }
-
             if (this._environmentBRDFTexture && this.getScene().environmentBRDFTexture !== this._environmentBRDFTexture) {
                 this._environmentBRDFTexture.dispose();
             }
 
-            if (this._emissiveTexture) {
-                this._emissiveTexture.dispose();
-            }
-
-            if (this._metallicTexture) {
-                this._metallicTexture.dispose();
-            }
-
-            if (this._reflectivityTexture) {
-                this._reflectivityTexture.dispose();
-            }
-
-            if (this._bumpTexture) {
-                this._bumpTexture.dispose();
-            }
-
-            if (this._lightmapTexture) {
-                this._lightmapTexture.dispose();
-            }
+            this._albedoTexture?.dispose();
+            this._ambientTexture?.dispose();
+            this._opacityTexture?.dispose();
+            this._reflectionTexture?.dispose();
+            this._emissiveTexture?.dispose();
+            this._metallicTexture?.dispose();
+            this._reflectivityTexture?.dispose();
+            this._bumpTexture?.dispose();
+            this._lightmapTexture?.dispose();
+            this._metallicReflectanceTexture?.dispose();
+            this._microSurfaceTexture?.dispose();
         }
 
         this.subSurface.dispose(forceDisposeTextures);

+ 33 - 13
src/Materials/PBR/pbrMaterial.ts

@@ -165,23 +165,38 @@ export class PBRMaterial extends PBRBaseMaterial {
     public roughness: Nullable<number>;
 
     /**
-     * Specifies the an F0 factor to help configuring the material F0.
-     * Instead of the default 4%, 8% * factor will be used. As the factor is defaulting
-     * to 0.5 the previously hard coded value stays the same.
-     * Can also be used to scale the F0 values of the metallic texture.
+     * In metallic workflow, specifies an F0 factor to help configuring the material F0.
+     * By default the indexOfrefraction is used to compute F0;
+     *
+     * This is used as a factor against the default reflectance at normal incidence to tweak it.
+     *
+     * F0 = defaultF0 * metallicF0Factor * metallicReflectanceColor;
+     * F90 = metallicReflectanceColor;
      */
     @serialize()
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
-    public metallicF0Factor = 0.5;
+    public metallicF0Factor = 1;
 
     /**
-     * Specifies whether the F0 factor can be fetched from the mettalic texture.
-     * If set to true, please adapt the metallicF0Factor to ensure it fits with
-     * your expectation as it multiplies with the texture data.
+     * In metallic workflow, specifies an F90 color to help configuring the material F90.
+     * By default the F90 is always 1;
+     *
+     * Please note that this factor is also used as a factor against the default reflectance at normal incidence.
+     *
+     * F0 = defaultF0 * metallicF0Factor * metallicReflectanceColor
+     * F90 = metallicReflectanceColor;
      */
-    @serialize()
+    @serializeAsColor3()
+    @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+    public metallicReflectanceColor = Color3.White();
+
+    /**
+     * Defines to store metallicReflectanceColor in RGB and metallicF0Factor in A
+     * This is multiply against the scalar values defined in the material.
+     */
+    @serializeAsTexture()
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
-    public useMetallicF0FactorFromMetallicTexture = false;
+    public metallicReflectanceTexture: Nullable<BaseTexture>;
 
     /**
      * Used to enable roughness/glossiness fetch from a separate channel depending on the current mode.
@@ -264,13 +279,18 @@ export class PBRMaterial extends PBRBaseMaterial {
     public microSurface = 1.0;
 
     /**
-     * source material index of refraction (IOR)' / 'destination material IOR.
+     * Index of refraction of the material base layer.
+     * https://en.wikipedia.org/wiki/List_of_refractive_indices
+     *
+     * This does not only impact refraction but also the Base F0 of Dielectric Materials.
+     *
+     * From dielectric fresnel rules: F0 = square((iorT - iorI) / (iorT + iorI))
      */
     public get indexOfRefraction(): number {
-        return 1 / this.subSurface.indexOfRefraction;
+        return this.subSurface.indexOfRefraction;
     }
     public set indexOfRefraction(value: number) {
-        this.subSurface.indexOfRefraction = 1 / value;
+        this.subSurface.indexOfRefraction = value;
     }
 
     /**

+ 7 - 3
src/Materials/PBR/pbrSubSurfaceConfiguration.ts

@@ -113,14 +113,18 @@ export class PBRSubSurfaceConfiguration {
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
     public refractionTexture: Nullable<BaseTexture> = null;
 
-    private _indexOfRefraction = 1;
+    private _indexOfRefraction = 1.5;
     /**
-     * Defines the index of refraction used in the material.
+     * Index of refraction of the material base layer.
      * https://en.wikipedia.org/wiki/List_of_refractive_indices
+     *
+     * This does not only impact refraction but also the Base F0 of Dielectric Materials.
+     *
+     * From dielectric fresnel rules: F0 = square((iorT - iorI) / (iorT + iorI))
      */
     @serialize()
     @expandToProperty("_markAllSubMeshesAsTexturesDirty")
-    public indexOfRefraction = 1;
+    public indexOfRefraction = 1.5;
 
     private _invertRefractionY = false;
     /**

+ 209 - 0
src/Materials/Textures/Filtering/hdrFiltering.ts

@@ -0,0 +1,209 @@
+import { Vector3 } from "../../../Maths/math";
+import { Scalar } from "../../../Maths/math.scalar";
+import { InternalTexture } from "../internalTexture";
+import { BaseTexture } from "../baseTexture";
+import { ThinEngine } from "../../../Engines/thinEngine";
+import { Effect } from "../../../Materials/effect";
+import { Constants } from "../../../Engines/constants";
+import { EffectWrapper, EffectRenderer } from "../../../Materials/effectRenderer";
+import { Nullable } from '../../../types';
+
+import "../../../Shaders/hdrFiltering.vertex";
+import "../../../Shaders/hdrFiltering.fragment";
+
+/**
+ * Options for texture filtering
+ */
+interface IHDRFilteringOptions {
+    /**
+     * Scales pixel intensity for the input HDR map.
+     */
+    hdrScale?: number;
+
+    /**
+     * Quality of the filter. Should be `Constants.TEXTURE_FILTERING_QUALITY_OFFLINE` for prefiltering
+     */
+    quality?: number;
+}
+
+/**
+ * Filters HDR maps to get correct renderings of PBR reflections
+ */
+export class HDRFiltering {
+
+    private _engine: ThinEngine;
+    private _effectRenderer: EffectRenderer;
+    private _effectWrapper: EffectWrapper;
+
+    private _lodGenerationOffset: number = 0;
+    private _lodGenerationScale: number = 0.8;
+
+    /**
+     * Quality switch for prefiltering. Should be set to `Constants.TEXTURE_FILTERING_QUALITY_OFFLINE` unless
+     * you care about baking speed.
+     */
+    public quality: number = Constants.TEXTURE_FILTERING_QUALITY_OFFLINE;
+
+    /**
+     * Scales pixel intensity for the input HDR map.
+     */
+    public hdrScale: number = 1;
+
+    /**
+     * Instantiates HDR filter for reflection maps
+     *
+     * @param engine Thin engine
+     * @param options Options
+     */
+    constructor(engine: ThinEngine, options: IHDRFilteringOptions = {}) {
+        // pass
+        this._engine = engine;
+        this.hdrScale = options.hdrScale || this.hdrScale;
+        this.quality = options.hdrScale || this.quality;
+    }
+
+    private _createRenderTarget(size: number): InternalTexture {
+        let textureType = Constants.TEXTURETYPE_UNSIGNED_BYTE;
+        if (this._engine.getCaps().textureHalfFloatRender) {
+            textureType = Constants.TEXTURETYPE_HALF_FLOAT;
+        }
+        else if (this._engine.getCaps().textureFloatRender) {
+            textureType = Constants.TEXTURETYPE_FLOAT;
+        }
+
+        const texture = this._engine.createRenderTargetCubeTexture(size, {
+            format: Constants.TEXTUREFORMAT_RGBA,
+            type: textureType,
+            generateMipMaps: false,
+            generateDepthBuffer: false,
+            generateStencilBuffer: false,
+            samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE
+        });
+        this._engine.updateTextureWrappingMode(texture,
+            Constants.TEXTURE_CLAMP_ADDRESSMODE,
+            Constants.TEXTURE_CLAMP_ADDRESSMODE,
+            Constants.TEXTURE_CLAMP_ADDRESSMODE);
+
+        this._engine.updateTextureSamplingMode(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, texture, true);
+
+        return texture;
+    }
+
+    private _prefilterInternal(texture: BaseTexture): BaseTexture {
+        const width = texture.getSize().width;
+        const mipmapsCount = Math.round(Scalar.Log2(width)) + 1;
+
+        const effect = this._effectWrapper.effect;
+        const outputTexture = this._createRenderTarget(width);
+        this._effectRenderer.setViewport();
+
+        const intTexture = texture.getInternalTexture();
+        if (intTexture) {
+            // Just in case generate fresh clean mips.
+            this._engine.updateTextureSamplingMode(Constants.TEXTURE_TRILINEAR_SAMPLINGMODE, intTexture, true);
+        }
+
+        this._effectRenderer.applyEffectWrapper(this._effectWrapper);
+
+        const directions = [
+            [new Vector3(0, 0, -1), new Vector3(0, -1, 0), new Vector3(1, 0, 0)], // PositiveX
+            [new Vector3(0, 0, 1), new Vector3(0, -1, 0), new Vector3(-1, 0, 0)], // NegativeX
+            [new Vector3(1, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 1, 0)], // PositiveY
+            [new Vector3(1, 0, 0), new Vector3(0, 0, -1), new Vector3(0, -1, 0)], // NegativeY
+            [new Vector3(1, 0, 0), new Vector3(0, -1, 0), new Vector3(0, 0, 1)], // PositiveZ
+            [new Vector3(-1, 0, 0), new Vector3(0, -1, 0), new Vector3(0, 0, -1)], // NegativeZ
+        ];
+
+        effect.setFloat("hdrScale", this.hdrScale);
+        effect.setFloat2("vFilteringInfo", texture.getSize().width, mipmapsCount);
+        effect.setTexture("inputTexture", texture);
+
+        for (let face = 0; face < 6; face++) {
+            effect.setVector3("up", directions[face][0]);
+            effect.setVector3("right", directions[face][1]);
+            effect.setVector3("front", directions[face][2]);
+
+            for (let lod = 0; lod < mipmapsCount; lod++) {
+
+                this._engine.bindFramebuffer(outputTexture, face, undefined, undefined, true, lod);
+                this._effectRenderer.applyEffectWrapper(this._effectWrapper);
+
+                let alpha = Math.pow(2, (lod - this._lodGenerationOffset) / this._lodGenerationScale) / width;
+                if (lod === 0) {
+                    alpha = 0;
+                }
+
+                effect.setFloat("linearRoughness", alpha);
+
+                this._effectRenderer.draw();
+            }
+        }
+
+        // Cleanup
+        this._effectRenderer.restoreStates();
+        this._engine.restoreDefaultFramebuffer();
+        this._engine._releaseFramebufferObjects(outputTexture);
+        this._engine._releaseTexture(texture._texture!);
+
+        // Internal Swap
+        outputTexture._swapAndDie(texture._texture!);
+        return texture;
+    }
+
+    private _createEffect(texture: BaseTexture, onCompiled?: Nullable<(effect: Effect) => void>): EffectWrapper {
+        const defines = [];
+        if (texture.gammaSpace) {
+            defines.push("#define GAMMA_INPUT");
+        }
+
+        defines.push("#define NUM_SAMPLES " + this.quality + "u"); // unsigned int
+
+        const effectWrapper = new EffectWrapper({
+            engine: this._engine,
+            name: "hdrFiltering",
+            vertexShader: "hdrFiltering",
+            fragmentShader: "hdrFiltering",
+            samplerNames: ["inputTexture"],
+            uniformNames: ["vSampleDirections", "vWeights", "up", "right", "front", "vFilteringInfo", "hdrScale", "linearRoughness"],
+            useShaderStore: true,
+            defines,
+            onCompiled: onCompiled
+        });
+
+        return effectWrapper;
+    }
+
+    /**
+     * Get a value indicating if the filter is ready to be used
+     * @param texture Texture to filter
+     * @returns true if the filter is ready
+     */
+    public isReady(texture: BaseTexture) {
+        return (texture.isReady() && this._effectWrapper.effect.isReady());
+    }
+
+    /**
+      * Prefilters a cube texture to have mipmap levels representing roughness values.
+      * Prefiltering will be invoked at the end of next rendering pass.
+      * This has to be done once the map is loaded, and has not been prefiltered by a third party software.
+      * See http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf for more information
+      * @param texture Texture to filter
+      * @param onFinished Callback when filtering is done
+      * @return Promise called when prefiltering is done
+      */
+    public prefilter(texture: BaseTexture, onFinished: Nullable<() => void> = null) {
+        return new Promise((resolve) => {
+            this._effectRenderer = new EffectRenderer(this._engine);
+            this._effectWrapper = this._createEffect(texture);
+            this._effectWrapper.effect.executeWhenCompiled(() => {
+                this._prefilterInternal(texture);
+                this._effectRenderer.dispose();
+                this._effectWrapper.dispose();
+                resolve();
+                if (onFinished) {
+                    onFinished();
+                }
+            });
+        });
+    }
+}

+ 30 - 0
src/Materials/Textures/baseTexture.ts

@@ -345,6 +345,36 @@ export class BaseTexture implements IAnimatable {
         return "BaseTexture";
     }
 
+    private _realTimeFiltering: boolean = false;
+    /**
+     * Enables realtime filtering on the texture.
+     */
+    public get realTimeFiltering() {
+        return this._realTimeFiltering;
+    }
+    public set realTimeFiltering(b: boolean) {
+        this._realTimeFiltering = b;
+        let scene = this.getScene();
+        if (scene) {
+            scene.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+        }
+    }
+
+    private _realTimeFilteringQuality: number = Constants.TEXTURE_FILTERING_QUALITY_LOW;
+    /**
+     * Quality switch for realtime filtering
+     */
+    public get realTimeFilteringQuality() : number {
+        return this._realTimeFilteringQuality;
+    }
+    public set realTimeFilteringQuality(n: number) {
+        this._realTimeFilteringQuality = n;
+        let scene = this.getScene();
+        if (scene) {
+            scene.markAllMaterialsAsDirty(Constants.MATERIAL_TextureDirtyFlag);
+        }
+    }
+
     /**
      * Define the list of animation attached to the texture.
      */

+ 14 - 3
src/Materials/Textures/hdrCubeTexture.ts

@@ -10,7 +10,7 @@ import { _TypeStore } from '../../Misc/typeStore';
 import { Tools } from '../../Misc/tools';
 import { ToGammaSpace } from '../../Maths/math.constants';
 import { ThinEngine } from '../../Engines/thinEngine';
-
+import { HDRFiltering } from "../../Materials/Textures/Filtering/hdrFiltering";
 import "../../Engines/Extensions/engine.rawTexture";
 import "../../Materials/Textures/baseTexture.polynomial";
 
@@ -33,6 +33,7 @@ export class HDRCubeTexture extends BaseTexture {
 
     private _generateHarmonics = true;
     private _noMipmap: boolean;
+    private _prefilterOnLoad: boolean;
     private _textureMatrix: Matrix;
     private _size: number;
     private _onLoad: Nullable<() => void> = null;
@@ -114,9 +115,9 @@ export class HDRCubeTexture extends BaseTexture {
      * @param noMipmap Forces to not generate the mipmap if true
      * @param generateHarmonics Specifies whether you want to extract the polynomial harmonics during the generation process
      * @param gammaSpace Specifies if the texture will be use in gamma or linear space (the PBR material requires those texture in linear space, but the standard material would require them in Gamma space)
-     * @param reserved Reserved flag for internal use.
+     * @param prefilterOnLoad Prefilters HDR texture to allow use of this texture as a PBR reflection texture.
      */
-    constructor(url: string, sceneOrEngine: Scene | ThinEngine, size: number, noMipmap = false, generateHarmonics = true, gammaSpace = false, reserved = false, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null) {
+    constructor(url: string, sceneOrEngine: Scene | ThinEngine, size: number, noMipmap = false, generateHarmonics = true, gammaSpace = false, prefilterOnLoad = false, onLoad: Nullable<() => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null) {
         super(sceneOrEngine);
 
         if (!url) {
@@ -128,6 +129,7 @@ export class HDRCubeTexture extends BaseTexture {
         this.hasAlpha = false;
         this.isCube = true;
         this._textureMatrix = Matrix.Identity();
+        this._prefilterOnLoad = prefilterOnLoad;
         this._onLoad = onLoad;
         this._onError = onError;
         this.gammaSpace = gammaSpace;
@@ -235,9 +237,18 @@ export class HDRCubeTexture extends BaseTexture {
                     results.push(dataFace);
                 }
             }
+
             return results;
         };
 
+        if (this._getEngine()!.webGLVersion >= 2 && this._prefilterOnLoad) {
+            const previousOnLoad = this._onLoad;
+            const hdrFiltering = new HDRFiltering(engine);
+            this._onLoad = () => {
+                hdrFiltering.prefilter(this, previousOnLoad);
+            };
+        }
+
         this._texture = engine.createRawCubeTextureFromUrl(this.url, this.getScene(), this._size,
             Constants.TEXTUREFORMAT_RGB,
             engine.getCaps().textureFloat ? Constants.TEXTURETYPE_FLOAT : Constants.TEXTURETYPE_UNSIGNED_INT,

+ 4 - 3
src/Materials/Textures/index.ts

@@ -4,20 +4,21 @@ export * from "./colorGradingTexture";
 export * from "./cubeTexture";
 export * from "./dynamicTexture";
 export * from "./equiRectangularCubeTexture";
+export * from "./Filtering/hdrFiltering";
 export * from "./hdrCubeTexture";
+export * from "./htmlElementTexture";
 export * from "./internalTexture";
 export * from "./internalTextureLoader";
 export * from "./Loaders/index";
 export * from "./mirrorTexture";
 export * from "./multiRenderTarget";
+export * from "./Packer/index";
 export * from "./Procedurals/index";
 export * from "./rawCubeTexture";
 export * from "./rawTexture";
-export * from "./rawTexture3D";
 export * from "./rawTexture2DArray";
+export * from "./rawTexture3D";
 export * from "./refractionTexture";
 export * from "./renderTargetTexture";
 export * from "./texture";
 export * from "./videoTexture";
-export * from "./htmlElementTexture";
-export * from "./Packer/index";

+ 54 - 6
src/Materials/effectRenderer.ts

@@ -90,6 +90,14 @@ export class EffectRenderer {
     }
 
     /**
+     * Restores engine states
+     */
+    public restoreStates(): void {
+        this.engine.depthCullingState.depthTest = true;
+        this.engine.stencilState.stencilTest = true;
+    }
+
+    /**
      * Draws a full screen quad.
      */
     public draw(): void {
@@ -108,7 +116,7 @@ export class EffectRenderer {
     public render(effectWrapper: EffectWrapper, outputTexture: Nullable<InternalTexture | RenderTargetTexture> = null) {
         // Ensure effect is ready
         if (!effectWrapper.effect.isReady()) {
-            return ;
+            return;
         }
 
         // Reset state
@@ -127,6 +135,8 @@ export class EffectRenderer {
         if (out) {
             this.engine.unBindFramebuffer(out);
         }
+
+        this.restoreStates();
     }
 
     /**
@@ -158,6 +168,10 @@ interface EffectWrapperCreationOptions {
      */
     fragmentShader: string;
     /**
+     * Use the shader store instead of direct source code
+     */
+    useShaderStore?: boolean;
+    /**
      * Vertex shader for the effect
      */
     vertexShader?: string;
@@ -174,6 +188,14 @@ interface EffectWrapperCreationOptions {
      */
     samplerNames?: Array<string>;
     /**
+      * Defines to use in the shader
+      */
+    defines?: Array<string>;
+    /**
+      * Callback when effect is compiled
+      */
+    onCompiled?: Nullable<(effect: Effect) => void>;
+    /**
      * The friendly name of the effect displayed in Spector.
      */
     name?: string;
@@ -199,6 +221,7 @@ export class EffectWrapper {
     constructor(creationOptions: EffectWrapperCreationOptions) {
         let effectCreationOptions: any;
         const uniformNames = creationOptions.uniformNames || [];
+
         if (creationOptions.vertexShader) {
             effectCreationOptions = {
                 fragmentSource: creationOptions.fragmentShader,
@@ -222,11 +245,36 @@ export class EffectWrapper {
             });
         }
 
-        this.effect = new Effect(effectCreationOptions,
-            creationOptions.attributeNames || ["position"],
-            uniformNames,
-            creationOptions.samplerNames,
-            creationOptions.engine);
+        const defines = creationOptions.defines ? creationOptions.defines.join("\n") : "";
+
+        if (creationOptions.useShaderStore) {
+            effectCreationOptions.fragment = effectCreationOptions.fragmentSource;
+            if (!effectCreationOptions.vertex) {
+                effectCreationOptions.vertex = effectCreationOptions.vertexSource;
+            }
+
+            delete effectCreationOptions.fragmentSource;
+            delete effectCreationOptions.vertexSource;
+
+            this.effect = creationOptions.engine.createEffect(effectCreationOptions.spectorName,
+                creationOptions.attributeNames || ["position"],
+                uniformNames,
+                creationOptions.samplerNames,
+                defines,
+                undefined,
+                creationOptions.onCompiled
+            );
+        } else {
+            this.effect = new Effect(effectCreationOptions,
+                creationOptions.attributeNames || ["position"],
+                uniformNames,
+                creationOptions.samplerNames,
+                creationOptions.engine,
+                defines,
+                undefined,
+                creationOptions.onCompiled,
+            );
+        }
     }
 
     /**

+ 13 - 1
src/Probes/reflectionProbe.ts

@@ -91,7 +91,19 @@ export class ReflectionProbe {
         }
         this._scene.reflectionProbes.push(this);
 
-        this._renderTargetTexture = new RenderTargetTexture(name, size, scene, generateMipMaps, true, useFloat ? Constants.TEXTURETYPE_FLOAT : Constants.TEXTURETYPE_UNSIGNED_INT, true);
+        let textureType = Constants.TEXTURETYPE_UNSIGNED_BYTE;
+        if (useFloat) {
+            const caps = this._scene.getEngine().getCaps();
+            if (caps.textureHalfFloatRender) {
+                textureType = Constants.TEXTURETYPE_HALF_FLOAT;
+            }
+            else if (caps.textureFloatRender) {
+                textureType = Constants.TEXTURETYPE_FLOAT;
+            }
+        }
+        this._renderTargetTexture = new RenderTargetTexture(name, size, scene, generateMipMaps, true, textureType, true);
+
+        this._renderTargetTexture.realTimeFiltering = true;
 
         this._renderTargetTexture.onBeforeRenderObservable.add((faceIndex: number) => {
             switch (faceIndex) {

+ 224 - 0
src/Shaders/ShadersInclude/hdrFilteringFunctions.fx

@@ -0,0 +1,224 @@
+#if defined(WEBGL2) && defined(NUM_SAMPLES)
+	#if NUM_SAMPLES > 0u
+		uniform vec2 vFilteringInfo;
+		uniform float linearRoughness;
+
+		const float NUM_SAMPLES_FLOAT = float(NUM_SAMPLES);
+		const float NUM_SAMPLES_FLOAT_INVERSED = 1. / NUM_SAMPLES_FLOAT;
+
+		const float K = 4.;
+
+		//
+		//
+		// Importance sampling GGX - Trowbridge-Reitz
+		// ------------------------------------------
+		//
+		// Important samples are chosen to integrate Dggx() * cos(theta) over the hemisphere.
+		//
+		// All calculations are made in tangent space, with n = [0 0 1]
+		//
+		//             l        h (important sample)
+		//             .\      /.
+		//             . \    / .
+		//             .  \  /  .
+		//             .   \/   .
+		//         ----+---o----+-------> n [0 0 1]
+		//     cos(2*theta)     cos(theta)
+		//        = n•l            = n•h
+		//
+		//  v = n
+		//  f0 = f90 = 1
+		//  V = 1
+		//
+		//  h is micro facet's normal
+		//
+		//  l is the reflection of v (i.e.: n) around h  ==>  n•h = l•h = v•h
+		//
+		//  h = important_sample_ggx()
+		//
+		//  n•h = [0 0 1]•h = h.z
+		//
+		//  l = reflect(-n, h)
+		//    = 2 * (n•h) * h - n;
+		//
+		//  n•l = cos(2 * theta)
+		//      = cos(theta)^2 - sin(theta)^2
+		//      = (n•h)^2 - (1 - (n•h)^2)
+		//      = 2(n•h)^2 - 1
+		//
+		//
+		//  pdf() = D(h) <n•h> |J(h)|
+		//
+		//               1
+		//  |J(h)| = ----------
+		//            4 <v•h>
+		//
+		//    v = n -> <v•h>/<n•h> = 1
+		//
+		//  pdf() = D(h) / 4
+		//
+		//
+		// Pre-filtered importance sampling
+		// --------------------------------
+		//
+		//  see: "Real-time Shading with Filtered Importance Sampling", Jaroslav Krivanek
+		//  see: "GPU-Based Importance Sampling, GPU Gems 3", Mark Colbert
+		//
+		//
+		//                   Ωs
+		//     lod = log4(K ----)
+		//                   Ωp
+		//
+		//     log4(K) = 1, works well for box filters
+		//     K = 4
+		//
+		//             1
+		//     Ωs = ---------, solid-angle of an important sample
+		//           N * pdf
+		//
+		//              4 PI
+		//     Ωp ~ --------------, solid-angle of a sample in the base cubemap
+		//           texel_count
+		//
+		//
+		// Evaluating the integral
+		// -----------------------
+		//
+		//                    K     fr(h)
+		//            Er() = --- ∑ ------- L(h) <n•l>
+		//                    N  h   pdf
+		//
+		// with:
+		//
+		//            fr() = D(h)
+		//
+		//                       N
+		//            K = -----------------
+		//                    fr(h)
+		//                 ∑ ------- <n•l>
+		//                 h   pdf
+		//
+		//
+		//  It results that:
+		//
+		//            K           4 <v•h>
+		//    Er() = --- ∑ D(h) ------------ L(h) <n•l>
+		//            N  h        D(h) <n•h>
+		//
+		//    v = n -> <v•h>/<n•h> = 1
+		//
+		//              K
+		//    Er() = 4 --- ∑ L(h) <n•l>
+		//              N  h
+		//
+		//                  N       4
+		//    Er() = ------------- --- ∑ V(v) <n•l>
+		//             4 ∑ <n•l>    N
+		//
+		//
+		//  +------------------------------+
+		//  |          ∑ <n•l> L(h)        |
+		//  |  Er() = --------------       |
+		//  |            ∑ <n•l>           |
+		//  +------------------------------+
+		//
+		//
+
+		vec3 irradiance(samplerCube inputTexture, vec3 n) {
+		    vec3 result = vec3(0.0);
+			vec3 tangent = abs(n.z) < 0.999 ? vec3(0., 0., 1.) : vec3(1., 0., 0.);
+			tangent = normalize(cross(tangent, n));
+			vec3 bitangent = cross(n, tangent);
+			mat3 tbn = mat3(tangent, bitangent, n);
+
+		    float maxLevel = vFilteringInfo.y;
+		    float dim0 = vFilteringInfo.x;
+		    float omegaP = (4. * PI) / (6. * dim0 * dim0);
+
+		    for(uint i = 0u; i < NUM_SAMPLES; ++i)
+		    {
+		        vec2 Xi = hammersley(i, NUM_SAMPLES);
+		        vec3 Ls = hemisphereCosSample(Xi);
+
+		        Ls = normalize(Ls);
+
+		        vec3 Ns = vec3(0., 0., 1.);
+
+		        float NoL = dot(Ns, Ls);
+
+		        if (NoL > 0.) {
+		            float pdf_inversed = PI / NoL;
+
+		            float omegaS = NUM_SAMPLES_FLOAT_INVERSED * pdf_inversed;
+		            float l = log4(omegaS) - log4(omegaP) + log4(K);
+		            float mipLevel = clamp(l, 0.0, maxLevel);
+
+		            vec3 c = textureCubeLodEXT(inputTexture, tbn * Ls, mipLevel).rgb;
+		            #ifdef GAMMA_INPUT
+		                c = toLinearSpace(c);
+		            #endif
+		            result += c;
+		        }
+		    }
+
+		    result = result * NUM_SAMPLES_FLOAT_INVERSED;
+
+		    return result;
+		}
+
+		vec3 radiance(samplerCube inputTexture, vec3 n) {
+			if (linearRoughness == 0.) {
+				vec3 c = textureCube(inputTexture, n).rgb;
+				#ifdef GAMMA_INPUT
+				    c = toLinearSpace(c);
+				#endif
+				return c;
+			}
+
+			vec3 result = vec3(0.);
+			vec3 tangent = abs(n.z) < 0.999 ? vec3(0., 0., 1.) : vec3(1., 0., 0.);
+			tangent = normalize(cross(tangent, n));
+			vec3 bitangent = cross(n, tangent);
+			mat3 tbn = mat3(tangent, bitangent, n);
+
+			float maxLevel = vFilteringInfo.y;
+			float dim0 = vFilteringInfo.x;
+			float omegaP = (4. * PI) / (6. * dim0 * dim0);
+
+			float weight = 0.;
+			for(uint i = 0u; i < NUM_SAMPLES; ++i)
+			{
+			    vec2 Xi = hammersley(i, NUM_SAMPLES);
+			    vec3 H = hemisphereImportanceSampleDggx(Xi, linearRoughness);
+
+			    float NoV = 1.;
+			    float NoH = H.z;
+			    float NoH2 = H.z * H.z;
+			    float NoL = 2. * NoH2 - 1.;
+			    vec3 L = vec3(2. * NoH * H.x, 2. * NoH * H.y, NoL);
+			    L = normalize(L);
+
+			    if (NoL > 0.) {
+			        float pdf_inversed = 4. / normalDistributionFunction_TrowbridgeReitzGGX(NoH, linearRoughness);
+
+			        float omegaS = NUM_SAMPLES_FLOAT_INVERSED * pdf_inversed;
+			        float l = log4(omegaS) - log4(omegaP) + log4(K);
+			        float mipLevel = clamp(float(l), 0.0, maxLevel);
+
+			        weight += NoL;
+
+			        vec3 c = textureCubeLodEXT(inputTexture, tbn * L, mipLevel).rgb;
+			        #ifdef GAMMA_INPUT
+			            c = toLinearSpace(c);
+			        #endif
+			        result += c * NoL;
+			    }
+			}
+
+			result = result / weight;
+
+			return result;
+		}
+
+	#endif
+#endif

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

@@ -73,6 +73,54 @@ float square(float value)
     return value * value;
 }
 
+#ifdef WEBGL2
+    // https://learnopengl.com/PBR/IBL/Specular-IBL
+    // Hammersley
+    float radicalInverse_VdC(uint bits) 
+    {
+        bits = (bits << 16u) | (bits >> 16u);
+        bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+        bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+        bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+        bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+        return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+    }
+
+    vec2 hammersley(uint i, uint N)
+    {
+        return vec2(float(i)/float(N), radicalInverse_VdC(i));
+    }
+#else
+    // float vanDerCorpus(uint n, uint base)
+    // {
+    //     float invBase = 1.0 / float(base);
+    //     float denom   = 1.0;
+    //     float result  = 0.0;
+
+    //     for(uint i = 0u; i < 32u; ++i)
+    //     {
+    //         if(n > 0u)
+    //         {
+    //             denom   = mod(float(n), 2.0);
+    //             result += denom * invBase;
+    //             invBase = invBase / 2.0;
+    //             n       = uint(float(n) / 2.0);
+    //         }
+    //     }
+
+    //     return result;
+    // }
+
+    // vec2 hammersley(uint i, uint N)
+    // {
+    //     return vec2(float(i)/float(N), vanDerCorpus(i, 2u));
+    // }
+#endif
+
+float log4(float x) {
+    return log2(x) / 2.;
+}
+
 float pow5(float value) {
     float sq = value * value;
     return sq * sq * value;

+ 155 - 0
src/Shaders/ShadersInclude/importanceSampling.fx

@@ -0,0 +1,155 @@
+// https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html
+//
+// Importance sampling
+// -------------------
+//
+// Important samples are chosen to integrate cos(theta) over the hemisphere.
+//
+// All calculations are made in tangent space, with n = [0 0 1]
+//
+//                      l (important sample)
+//                     /.
+//                    / .
+//                   /  .
+//                  /   .
+//         --------o----+-------> n (direction)
+//                   cos(theta)
+//                    = n•l
+//
+//
+//  'direction' is given as an input parameter, and serves as tge z direction of the tangent space.
+//
+//  l = important_sample_cos()
+//
+//  n•l = [0 0 1] • l = l.z
+//
+//           n•l
+//  pdf() = -----
+//           PI
+//
+vec3 hemisphereCosSample(vec2 u) {
+    // pdf = cosTheta / M_PI;
+    float phi = 2. * PI * u.x;
+
+    float cosTheta2 = 1. - u.y;
+    float cosTheta = sqrt(cosTheta2);
+    float sinTheta = sqrt(1. - cosTheta2);
+
+    return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+}
+
+// https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html
+//
+//
+// Importance sampling GGX - Trowbridge-Reitz
+// ------------------------------------------
+//
+// Important samples are chosen to integrate Dggx() * cos(theta) over the hemisphere.
+//
+// All calculations are made in tangent space, with n = [0 0 1]
+//
+//                      h (important sample)
+//                     /.
+//                    / .
+//                   /  .
+//                  /   .
+//         --------o----+-------> n
+//                   cos(theta)
+//                    = n•h
+//
+//  h is micro facet's normal
+//  l is the reflection of v around h, l = reflect(-v, h)  ==>  v•h = l•h
+//
+//  n•v is given as an input parameter at runtime
+//
+//  Since n = [0 0 1], we also have v.z = n•v
+//
+//  Since we need to compute v•h, we chose v as below. This choice only affects the
+//  computation of v•h (and therefore the fresnel term too), but doesn't affect
+//  n•l, which only relies on l.z (which itself only relies on v.z, i.e.: n•v)
+//
+//      | sqrt(1 - (n•v)^2)     (sin)
+//  v = | 0
+//      | n•v                   (cos)
+//
+//
+//  h = important_sample_ggx()
+//
+vec3 hemisphereImportanceSampleDggx(vec2 u, float a) {
+    // pdf = D(a) * cosTheta
+    float phi = 2. * PI * u.x;
+
+    // NOTE: (aa-1) == (a-1)(a+1) produces better fp accuracy
+    float cosTheta2 = (1. - u.y) / (1. + (a + 1.) * ((a - 1.) * u.y));
+    float cosTheta = sqrt(cosTheta2);
+    float sinTheta = sqrt(1. - cosTheta2);
+
+    return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+}
+
+//
+//
+// Importance sampling Charlie
+// ---------------------------
+//
+// In order to pick the most significative samples and increase the convergence rate, we chose to
+// rely on Charlie's distribution function for the pdf as we do in hemisphereImportanceSampleDggx.
+//
+// To determine the direction we then need to resolve the cdf associated to the chosen pdf for random inputs.
+//
+// Knowing pdf() = DCharlie(h) <n•h>
+//
+// We need to find the cdf:
+//
+// / 2pi     / pi/2
+// |         |  (2 + (1 / a)) * sin(theta) ^ (1 / a) * cos(theta) * sin(theta)
+// / phi=0   / theta=0
+//
+// We sample theta and phi independently.
+//
+// 1. as in all the other isotropic cases phi = 2 * pi * epsilon
+//    (https://www.tobias-franke.eu/log/2014/03/30/notes_on_importance_sampling.html)
+//
+// 2. we need to solve the integral on theta:
+//
+//             / sTheta
+// P(sTheta) = |  (2 + (1 / a)) * sin(theta) ^ (1 / a + 1) * cos(theta) * dtheta
+//             / theta=0
+//
+// By subsitution of u = sin(theta) and du = cos(theta) * dtheta
+//
+// /
+// |  (2 + (1 / a)) * u ^ (1 / a + 1) * du
+// /
+//
+// = (2 + (1 / a)) * u ^ (1 / a + 2) / (1 / a + 2)
+//
+// = u ^ (1 / a + 2)
+//
+// = sin(theta) ^ (1 / a + 2)
+//
+//             +-                          -+ sTheta
+// P(sTheta) = |  sin(theta) ^ (1 / a + 2)  |
+//             +-                          -+ 0
+//
+// P(sTheta) = sin(sTheta) ^ (1 / a + 2)
+//
+// We now need to resolve the cdf for an epsilon value:
+//
+// epsilon = sin(theta) ^ (a / ( 2 * a + 1))
+//
+//  +--------------------------------------------+
+//  |                                            |
+//  |  sin(theta) = epsilon ^ (a / ( 2 * a + 1)) |
+//  |                                            |
+//  +--------------------------------------------+
+//
+vec3 hemisphereImportanceSampleDCharlie(vec2 u, float a) { 
+    // pdf = DistributionCharlie() * cosTheta
+    float phi = 2. * PI * u.x;
+
+    float sinTheta = pow(u.y, a / (2. * a + 1.));
+    float cosTheta = sqrt(1. - sinTheta * sinTheta);
+
+    return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
+}

+ 19 - 6
src/Shaders/ShadersInclude/pbrBRDFFunctions.fx

@@ -29,6 +29,16 @@
         return brdfLookup.rgb;
     }
 
+    vec3 getReflectanceFromBRDFLookup(const vec3 specularEnvironmentR0, const vec3 specularEnvironmentR90, const vec3 environmentBrdf) {
+        #ifdef BRDF_V_HEIGHT_CORRELATED
+            vec3 reflectance = (specularEnvironmentR90 - specularEnvironmentR0) * environmentBrdf.x + specularEnvironmentR0 * environmentBrdf.y;
+            // Simplification if F90 = 1 vec3 reflectance = (specularEnvironmentR90 - specularEnvironmentR0) * environmentBrdf.xxx + specularEnvironmentR0 * environmentBrdf.yyy;
+        #else
+            vec3 reflectance = specularEnvironmentR0 * environmentBrdf.x + specularEnvironmentR90 * environmentBrdf.y;
+        #endif
+        return reflectance;
+    }
+
     vec3 getReflectanceFromBRDFLookup(const vec3 specularEnvironmentR0, const vec3 environmentBrdf) {
         #ifdef BRDF_V_HEIGHT_CORRELATED
             vec3 reflectance = mix(environmentBrdf.xxx, environmentBrdf.yyy, specularEnvironmentR0);
@@ -76,18 +86,21 @@ float getBRDFLookupCharlieSheen(float NdotV, float perceptualRoughness)
 //                              Schlick/Fresnel
 // ______________________________________________________________________
 
+// iorI incident iorT transmitted
+
 // Schlick's approximation for R0 (Fresnel Reflectance Values)
 // Keep for references
-// vec3 getR0fromAirToSurfaceIOR(vec3 ior1) {
-//     return getR0fromIOR(ior1, vec3(1.0));
-// }
 
-// vec3 getR0fromIOR(vec3 ior1, vec3 ior2) {
-//     vec3 t = (ior1 - ior2) / (ior1 + ior2);
+// vec3 getR0fromIORs(vec3 iorT, vec3 iorI) { 
+//     vec3 t = (iorT - iorI) / (iorT + iorI);
 //     return t * t;
 // }
 
-// vec3 getIORfromAirToSurfaceR0(vec3 f0) {
+// vec3 getR0fromAirToSurfaceIORT(vec3 iorT) {
+//     return getR0fromIOR(iorT, vec3(1.0));
+// }
+
+// vec3 getIORTfromAirToSurfaceR0(vec3 f0) {
 //     vec3 s = sqrt(f0);
 //     return (1.0 + s) / (1.0 - s);
 // }

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

@@ -1,5 +1,5 @@
 #if defined(ENVIRONMENTBRDF) && !defined(REFLECTIONMAP_SKYBOX)
-    vec3 specularEnvironmentReflectance = getReflectanceFromBRDFLookup(clearcoatOut.specularEnvironmentR0, environmentBrdf);
+    vec3 specularEnvironmentReflectance = getReflectanceFromBRDFLookup(clearcoatOut.specularEnvironmentR0, specularEnvironmentR90, environmentBrdf);
 
     #ifdef RADIANCEOCCLUSION
         specularEnvironmentReflectance *= seo;

+ 12 - 2
src/Shaders/ShadersInclude/pbrBlockReflectance0.fx

@@ -1,4 +1,14 @@
 float reflectance = max(max(reflectivityOut.surfaceReflectivityColor.r, reflectivityOut.surfaceReflectivityColor.g), reflectivityOut.surfaceReflectivityColor.b);
-float reflectance90 = fresnelGrazingReflectance(reflectance);
 vec3 specularEnvironmentR0 = reflectivityOut.surfaceReflectivityColor.rgb;
-vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90;
+
+#ifdef METALLICWORKFLOW
+    vec3 specularEnvironmentR90 = vec3(metallicReflectanceFactors.a);
+#else 
+    vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0);
+#endif
+
+// Back Compat
+#ifdef ALPHAFRESNEL
+    float reflectance90 = fresnelGrazingReflectance(reflectance);
+    specularEnvironmentR90 = specularEnvironmentR90 * reflectance90;
+#endif

+ 6 - 3
src/Shaders/ShadersInclude/pbrBlockReflection.fx

@@ -254,8 +254,12 @@
                     irradianceVector.z *= -1.0;
                 #endif
 
-                environmentIrradiance = computeEnvironmentIrradiance(irradianceVector);
-
+                #if defined(WEBGL2) && defined(REALTIME_FILTERING)
+                    environmentIrradiance = irradiance(reflectionSampler, irradianceVector);
+                #else
+                    environmentIrradiance = computeEnvironmentIrradiance(irradianceVector);
+                #endif
+                
                 #ifdef SS_TRANSLUCENCY
                     outParams.irradianceVector = irradianceVector;
                 #endif
@@ -273,7 +277,6 @@
         #endif
 
         environmentIrradiance *= vReflectionColor.rgb;
-
         outParams.environmentRadiance = environmentRadiance;
         outParams.environmentIrradiance = environmentIrradiance;
         outParams.reflectionCoords = reflectionCoords;

+ 4 - 8
src/Shaders/ShadersInclude/pbrBlockReflectivity.fx

@@ -21,6 +21,7 @@ void reflectivityBlock(
     const in vec4 vReflectivityColor,
 #ifdef METALLICWORKFLOW
     const in vec3 surfaceAlbedo,
+    const in vec4 metallicReflectanceFactors,
 #endif
 #ifdef REFLECTIVITY
     const in vec3 vReflectivityInfos,
@@ -82,7 +83,7 @@ void reflectivityBlock(
         // Diffuse is used as the base of the reflectivity.
         vec3 baseColor = surfaceAlbedo;
 
-        #ifdef REFLECTANCE
+        #ifdef FROSTBITE_REFLECTANCE
             // *** NOT USED ANYMORE ***
             // Following Frostbite Remapping,
             // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf page 115
@@ -95,19 +96,14 @@ void reflectivityBlock(
             // Compute the converted reflectivity.
             surfaceReflectivityColor = mix(0.16 * reflectance * reflectance, baseColor, metallicRoughness.r);
         #else
-            vec3 metallicF0 = vec3(vReflectivityColor.a, vReflectivityColor.a, vReflectivityColor.a);
-            #ifdef METALLICF0FACTORFROMMETALLICMAP
-                #ifdef REFLECTIVITY
-                    metallicF0 *= surfaceMetallicOrReflectivityColorMap.a;
-                #endif
-            #endif
+            vec3 metallicF0 = metallicReflectanceFactors.rgb;
 
             #if DEBUGMODE > 0
                 outParams.metallicF0 = metallicF0;
             #endif
 
             // Compute the converted diffuse.
-            outParams.surfaceAlbedo = mix(baseColor.rgb * (1.0 - metallicF0.r), vec3(0., 0., 0.), metallicRoughness.r);
+            outParams.surfaceAlbedo = mix(baseColor.rgb * (1.0 - metallicF0), vec3(0., 0., 0.), metallicRoughness.r);
 
             // Compute the converted reflectivity.
             surfaceReflectivityColor = mix(metallicF0, baseColor, metallicRoughness.r);

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

@@ -5,6 +5,7 @@ uniform vec4 vAlbedoColor;
 uniform vec4 vLightingIntensity;
 
 uniform vec4 vReflectivityColor;
+uniform vec4 vMetallicReflectanceFactors;
 uniform vec3 vEmissiveColor;
 
 uniform float visibility;

+ 16 - 1
src/Shaders/ShadersInclude/pbrFragmentSamplersDeclaration.fx

@@ -75,6 +75,17 @@
     uniform sampler2D microSurfaceSampler;
 #endif
 
+#ifdef METALLIC_REFLECTANCE
+    #if METALLIC_REFLECTANCEDIRECTUV == 1
+        #define vMetallicReflectanceUV vMainUV1
+    #elif METALLIC_REFLECTANCEDIRECTUV == 2
+        #define vMetallicReflectanceUV vMainUV2
+    #else
+        varying vec2 vMetallicReflectanceUV;
+    #endif
+    uniform sampler2D metallicReflectanceSampler;
+#endif
+
 #ifdef CLEARCOAT
     #ifdef CLEARCOAT_TEXTURE
         #if CLEARCOAT_TEXTUREDIRECTUV == 1
@@ -144,7 +155,11 @@
         uniform samplerCube reflectionSampler;
         
         #ifdef LODBASEDMICROSFURACE
-            #define sampleReflectionLod(s, c, l) textureCubeLodEXT(s, c, l)
+            #if defined(WEBGL2) && defined(REALTIME_FILTERING)
+                #define sampleReflectionLod(s, c, l) vec4(radiance(s, c), 1.0)
+            #else
+                #define sampleReflectionLod(s, c, l) textureCubeLodEXT(s, c, l)
+            #endif
         #else
             uniform samplerCube reflectionSamplerLow;
             uniform samplerCube reflectionSamplerHigh;

+ 4 - 0
src/Shaders/ShadersInclude/pbrUboDeclaration.fx

@@ -33,6 +33,10 @@ uniform Material
 
     uniform float visibility;
 
+    uniform vec4 vMetallicReflectanceFactors;
+    uniform vec2 vMetallicReflectanceInfos;
+    uniform mat4 metallicReflectanceMatrix;
+
     uniform vec2 vClearCoatParams;
     uniform vec4 vClearCoatRefractionParams;
     uniform vec2 vClearCoatInfos;

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

@@ -31,6 +31,11 @@ uniform vec3 vReflectivityInfos;
 uniform mat4 reflectivityMatrix;
 #endif
 
+#ifdef METALLIC_REFLECTANCE
+uniform vec2 vMetallicReflectanceInfos;
+uniform mat4 metallicReflectanceMatrix;
+#endif
+
 #ifdef MICROSURFACEMAP
 uniform vec2 vMicroSurfaceSamplerInfos;
 uniform mat4 microSurfaceSamplerMatrix;

+ 15 - 0
src/Shaders/hdrFiltering.fragment.fx

@@ -0,0 +1,15 @@
+#include<helperFunctions>
+#include<importanceSampling>
+#include<pbrBRDFFunctions>
+#include<hdrFilteringFunctions>
+
+uniform samplerCube inputTexture;
+uniform float hdrScale;
+
+varying vec3 direction;
+
+void main() {
+    vec3 color = radiance(inputTexture, direction);
+
+    gl_FragColor = vec4(color * hdrScale, 1.0);
+}

+ 16 - 0
src/Shaders/hdrFiltering.vertex.fx

@@ -0,0 +1,16 @@
+// Attributes
+attribute vec2 position;
+
+// Output
+varying vec3 direction;
+
+// Uniforms
+uniform vec3 up;
+uniform vec3 right;
+uniform vec3 front;
+
+void main(void) {	
+	mat3 view = mat3(up, right, front);
+	direction = view * vec3(position, 1.0);
+	gl_Position = vec4(position, 0.0, 1.0);
+}

+ 13 - 0
src/Shaders/pbr.fragment.fx

@@ -31,6 +31,7 @@ precision highp float;
 
 // Helper Functions
 #include<helperFunctions>
+#include<importanceSampling>
 #include<pbrHelperFunctions>
 #include<imageProcessingFunctions>
 #include<shadowsFragmentFunctions>
@@ -38,6 +39,7 @@ precision highp float;
 #include<pbrDirectLightingSetupFunctions>
 #include<pbrDirectLightingFalloffFunctions>
 #include<pbrBRDFFunctions>
+#include<hdrFilteringFunctions>
 #include<pbrDirectLightingFunctions>
 #include<pbrIBLFunctions>
 #include<bumpFragmentMainFunctions>
@@ -142,10 +144,21 @@ void main(void) {
     vec4 microSurfaceTexel = texture2D(microSurfaceSampler, vMicroSurfaceSamplerUV + uvOffset) * vMicroSurfaceSamplerInfos.y;
 #endif
 
+#ifdef METALLICWORKFLOW
+    vec4 metallicReflectanceFactors = vMetallicReflectanceFactors;
+    #ifdef METALLIC_REFLECTANCE
+        vec4 metallicReflectanceFactorsMap = texture2D(metallicReflectanceSampler, vMetallicReflectanceUV + uvOffset);
+        metallicReflectanceFactorsMap = toLinearSpace(metallicReflectanceFactorsMap);
+
+        metallicReflectanceFactors *= metallicReflectanceFactorsMap;
+    #endif
+#endif
+
     reflectivityBlock(
         vReflectivityColor,
     #ifdef METALLICWORKFLOW
         surfaceAlbedo,
+        metallicReflectanceFactors,
     #endif
     #ifdef REFLECTIVITY
         vReflectivityInfos,

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

@@ -62,6 +62,10 @@ varying vec2 vReflectivityUV;
 varying vec2 vMicroSurfaceSamplerUV;
 #endif
 
+#if defined(METALLIC_REFLECTANCE) && METALLIC_REFLECTANCEDIRECTUV == 0
+varying vec2 vMetallicReflectanceUV;
+#endif
+
 #if defined(BUMP) && BUMPDIRECTUV == 0
 varying vec2 vBumpUV;
 #endif
@@ -297,6 +301,17 @@ void main(void) {
     }
 #endif
 
+#if defined(METALLIC_REFLECTANCE) && METALLIC_REFLECTANCEDIRECTUV == 0 
+    if (vMetallicReflectanceInfos.x == 0.)
+    {
+        vMetallicReflectanceUV = vec2(metallicReflectanceMatrix * vec4(uvUpdated, 1.0, 0.0));
+    }
+    else
+    {
+        vMetallicReflectanceUV = vec2(metallicReflectanceMatrix * vec4(uv2, 1.0, 0.0));
+    }
+#endif
+
 #if defined(BUMP) && BUMPDIRECTUV == 0 
     if (vBumpInfos.x == 0.)
     {

+ 3 - 3
tests/validation/config.json

@@ -144,7 +144,7 @@
         },
         {
             "title": "Simulate pointer",
-            "playgroundId": "#8MGKWK#196",
+            "playgroundId": "#8MGKWK#269",
             "referenceImage": "simulatePointer.png"
         },
         {
@@ -677,12 +677,12 @@
         },
         {
             "title": "PBR",
-            "playgroundId": "#LCA0Q4#0",
+            "playgroundId": "#LCA0Q4#27",
             "referenceImage": "pbr.png"
         },
         {
             "title": "PBR refraction",
-            "playgroundId": "#LCA0Q4#25",
+            "playgroundId": "#LCA0Q4#26",
             "referenceImage": "pbr_refraction.png"
         },
         {