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

Merge pull request #8139 from BabylonJS/master

Nightly
mergify[bot] 5 лет назад
Родитель
Сommit
d2e79b6112
100 измененных файлов с 11146 добавлено и 4282 удалено
  1. 8 1
      Playground/js/main.js
  2. 10 1
      Tools/Gulp/helpers/gulp-processConstants.js
  3. 17 6
      Tools/Gulp/tasks/gulpTasks-localRun.js
  4. 949 509
      dist/preview release/babylon.d.ts
  5. 1 1
      dist/preview release/babylon.js
  6. 1740 457
      dist/preview release/babylon.max.js
  7. 1 1
      dist/preview release/babylon.max.js.map
  8. 1976 1055
      dist/preview release/babylon.module.d.ts
  9. 979 508
      dist/preview release/documentation.d.ts
  10. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js
  11. 5 3
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  12. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  13. 107 23
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  14. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.js.map
  15. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  16. 107 23
      dist/preview release/loaders/babylon.glTFFileLoader.js
  17. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.js.map
  18. 2 2
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  19. 33 2
      dist/preview release/loaders/babylonjs.loaders.d.ts
  20. 107 23
      dist/preview release/loaders/babylonjs.loaders.js
  21. 1 1
      dist/preview release/loaders/babylonjs.loaders.js.map
  22. 2 2
      dist/preview release/loaders/babylonjs.loaders.min.js
  23. 72 4
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  24. 19 9
      dist/preview release/nodeEditor/babylon.nodeEditor.d.ts
  25. 5 5
      dist/preview release/nodeEditor/babylon.nodeEditor.js
  26. 350 125
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  27. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  28. 46 21
      dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts
  29. 1 1
      dist/preview release/packagesSizeBaseLine.json
  30. 1976 1055
      dist/preview release/viewer/babylon.module.d.ts
  31. 97 57
      dist/preview release/viewer/babylon.viewer.js
  32. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  33. 72 4
      dist/preview release/viewer/babylonjs.loaders.module.d.ts
  34. 19 3
      dist/preview release/what's new.md
  35. 4 4
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx
  36. 79 0
      loaders/src/glTF/2.0/Extensions/KHR_materials_ior.ts
  37. 14 5
      loaders/src/glTF/2.0/Extensions/KHR_materials_specular.ts
  38. 1 0
      loaders/src/glTF/2.0/Extensions/index.ts
  39. 13 1
      nodeEditor/public/index.js
  40. 26 1
      nodeEditor/src/blockTools.ts
  41. 15 0
      nodeEditor/src/components/nodeList/nodeListComponent.tsx
  42. 1 1
      nodeEditor/src/components/preview/previewAreaComponent.tsx
  43. 199 71
      nodeEditor/src/components/preview/previewManager.ts
  44. 40 21
      nodeEditor/src/components/preview/previewMeshControlComponent.tsx
  45. 0 9
      nodeEditor/src/components/preview/previewMeshType.ts
  46. 19 0
      nodeEditor/src/components/preview/previewType.ts
  47. 20 3
      nodeEditor/src/components/propertyTab/propertyTabComponent.tsx
  48. 24 10
      nodeEditor/src/diagram/display/inputDisplayManager.ts
  49. 4 3
      nodeEditor/src/diagram/display/textureDisplayManager.ts
  50. 1 0
      nodeEditor/src/diagram/displayLedger.ts
  51. 3 2
      nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx
  52. 1 0
      nodeEditor/src/diagram/propertyLedger.ts
  53. 7 6
      nodeEditor/src/globalState.ts
  54. 4 3
      nodeEditor/src/nodeEditor.ts
  55. 1 1
      nodeEditor/src/sharedComponents/color3LineComponent.tsx
  56. 1 1
      nodeEditor/src/sharedComponents/color4LineComponent.tsx
  57. 9 2
      nodeEditor/src/sharedComponents/colorPickerComponent.tsx
  58. 18 8
      src/Audio/sound.ts
  59. 3 1
      src/Engines/Processors/shaderProcessor.ts
  60. 12 0
      src/Engines/constants.ts
  61. 13 6
      src/Materials/Node/Blocks/Dual/textureBlock.ts
  62. 50 8
      src/Materials/Node/Blocks/Input/inputBlock.ts
  63. 2 11
      src/Materials/Node/Blocks/PBR/reflectivityBlock.ts
  64. 4 0
      src/Materials/Node/Blocks/Particle/index.ts
  65. 95 0
      src/Materials/Node/Blocks/Particle/particleBlendMultiplyBlock.ts
  66. 97 0
      src/Materials/Node/Blocks/Particle/particleRampGradientBlock.ts
  67. 223 0
      src/Materials/Node/Blocks/Particle/particleTextureBlock.ts
  68. 1 0
      src/Materials/Node/Blocks/index.ts
  69. 4 1
      src/Materials/Node/Blocks/lerpBlock.ts
  70. 2 0
      src/Materials/Node/Enums/nodeMaterialModes.ts
  71. 160 7
      src/Materials/Node/nodeMaterial.ts
  72. 1 1
      src/Materials/Node/nodeMaterialBlock.ts
  73. 8 2
      src/Materials/Node/nodeMaterialBuildStateSharedData.ts
  74. 108 57
      src/Materials/PBR/pbrBaseMaterial.ts
  75. 33 13
      src/Materials/PBR/pbrMaterial.ts
  76. 7 3
      src/Materials/PBR/pbrSubSurfaceConfiguration.ts
  77. 209 0
      src/Materials/Textures/Filtering/hdrFiltering.ts
  78. 30 0
      src/Materials/Textures/baseTexture.ts
  79. 14 3
      src/Materials/Textures/hdrCubeTexture.ts
  80. 4 3
      src/Materials/Textures/index.ts
  81. 54 6
      src/Materials/effectRenderer.ts
  82. 40 0
      src/Particles/IParticleSystem.ts
  83. 135 43
      src/Particles/gpuParticleSystem.ts
  84. 1 1
      src/Particles/particleHelper.ts
  85. 97 25
      src/Particles/particleSystem.ts
  86. 17 7
      src/Particles/particleSystemComponent.ts
  87. 13 1
      src/Probes/reflectionProbe.ts
  88. 224 0
      src/Shaders/ShadersInclude/hdrFilteringFunctions.fx
  89. 48 0
      src/Shaders/ShadersInclude/helperFunctions.fx
  90. 155 0
      src/Shaders/ShadersInclude/importanceSampling.fx
  91. 19 6
      src/Shaders/ShadersInclude/pbrBRDFFunctions.fx
  92. 1 1
      src/Shaders/ShadersInclude/pbrBlockReflectance.fx
  93. 12 2
      src/Shaders/ShadersInclude/pbrBlockReflectance0.fx
  94. 6 3
      src/Shaders/ShadersInclude/pbrBlockReflection.fx
  95. 4 8
      src/Shaders/ShadersInclude/pbrBlockReflectivity.fx
  96. 1 0
      src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx
  97. 16 1
      src/Shaders/ShadersInclude/pbrFragmentSamplersDeclaration.fx
  98. 4 0
      src/Shaders/ShadersInclude/pbrUboDeclaration.fx
  99. 5 0
      src/Shaders/ShadersInclude/pbrVertexDeclaration.fx
  100. 0 0
      src/Shaders/gpuRenderParticles.fragment.fx

+ 8 - 1
Playground/js/main.js

@@ -101,7 +101,14 @@ compileAndRun = function (parent, fpsLabel) {
 
                 parent.zipTool.ZipCode = zipVariables + defaultEngineZip + "var engine = createDefaultEngine();" + ";\r\nvar scene = new BABYLON.Scene(engine);\r\n\r\n" + code;
             } else {
-                code += "\r\n\r\nengine = " + createEngineFunction + "();";
+                code += `
+var engine;
+try {
+    engine = ${createEngineFunction}();
+} catch(e) {
+    console.log("the available createEngine function failed. Creating the default engine instead");
+    engine = createDefaultEngine();
+}`;
                 code += "\r\nif (!engine) throw 'engine should not be null.';";
 
                 if (parent.settingsPG.ScriptLanguage == "JS") {

+ 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();
             }]
         }

Разница между файлами не показана из-за своего большого размера
+ 949 - 509
dist/preview release/babylon.d.ts


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/babylon.js


Разница между файлами не показана из-за своего большого размера
+ 1740 - 457
dist/preview release/babylon.max.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/babylon.max.js.map


Разница между файлами не показана из-за своего большого размера
+ 1976 - 1055
dist/preview release/babylon.module.d.ts


Разница между файлами не показана из-за своего большого размера
+ 979 - 508
dist/preview release/documentation.d.ts


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.js


Разница между файлами не показана из-за своего большого размера
+ 5 - 3
dist/preview release/inspector/babylon.inspector.bundle.max.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.max.js.map


+ 107 - 23
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -724,6 +724,78 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 
 /***/ }),
 
+/***/ "./glTF/2.0/Extensions/KHR_materials_ior.ts":
+/*!**************************************************!*\
+  !*** ./glTF/2.0/Extensions/KHR_materials_ior.ts ***!
+  \**************************************************/
+/*! exports provided: KHR_materials_ior */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return KHR_materials_ior; });
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
+
+
+var NAME = "KHR_materials_ior";
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1718)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+var KHR_materials_ior = /** @class */ (function () {
+    /** @hidden */
+    function KHR_materials_ior(loader) {
+        /**
+         * The name of this extension.
+         */
+        this.name = NAME;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        this.order = 180;
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+    /** @hidden */
+    KHR_materials_ior.prototype.dispose = function () {
+        delete this._loader;
+    };
+    /** @hidden */
+    KHR_materials_ior.prototype.loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
+        var _this = this;
+        return _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].LoadExtensionAsync(context, material, this.name, function (extensionContext, extension) {
+            var promises = new Array();
+            promises.push(_this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial));
+            promises.push(_this._loadIorPropertiesAsync(extensionContext, extension, babylonMaterial));
+            return Promise.all(promises).then(function () { });
+        });
+    };
+    KHR_materials_ior.prototype._loadIorPropertiesAsync = function (context, properties, babylonMaterial) {
+        if (!(babylonMaterial instanceof babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["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();
+    };
+    /**
+     * Default ior Value from the spec.
+     */
+    KHR_materials_ior._DEFAULT_IOR = 1.5;
+    return KHR_materials_ior;
+}());
+
+_glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, function (loader) { return new KHR_materials_ior(loader); });
+
+
+/***/ }),
+
 /***/ "./glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts":
 /*!********************************************************************!*\
   !*** ./glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts ***!
@@ -916,10 +988,10 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
 
+
 var NAME = "KHR_materials_specular";
 /**
- * [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 !!!
  */
 var KHR_materials_specular = /** @class */ (function () {
@@ -954,14 +1026,20 @@ var KHR_materials_specular = /** @class */ (function () {
         if (!(babylonMaterial instanceof babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["PBRMaterial"])) {
             throw new Error(context + ": Material type not supported");
         }
+        var promises = new Array();
         if (properties.specularFactor !== undefined) {
             babylonMaterial.metallicF0Factor = properties.specularFactor;
         }
+        if (properties.specularColorFactor !== undefined) {
+            babylonMaterial.metallicReflectanceColor = babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["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, function (texture) {
+                texture.name = babylonMaterial.name + " (Specular F0 Color)";
+                babylonMaterial.metallicReflectanceTexture = texture;
+            }));
         }
-        return Promise.resolve();
+        return Promise.all(promises).then(function () { });
     };
     return KHR_materials_specular;
 }());
@@ -1892,7 +1970,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 /*!**************************************!*\
   !*** ./glTF/2.0/Extensions/index.ts ***!
   \**************************************/
-/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -1924,30 +2002,34 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _KHR_materials_specular__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./KHR_materials_specular */ "./glTF/2.0/Extensions/KHR_materials_specular.ts");
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return _KHR_materials_specular__WEBPACK_IMPORTED_MODULE_8__["KHR_materials_specular"]; });
 
-/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_9__["KHR_mesh_quantization"]; });
+/* harmony import */ var _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_materials_ior */ "./glTF/2.0/Extensions/KHR_materials_ior.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__["KHR_materials_ior"]; });
 
-/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_10__["KHR_texture_basisu"]; });
+/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__["KHR_mesh_quantization"]; });
 
-/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_transform"]; });
+/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_basisu"]; });
 
-/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_12__["MSFT_audio_emitter"]; });
+/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_transform"]; });
 
-/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_13__["MSFT_lod"]; });
+/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_13__["MSFT_audio_emitter"]; });
 
-/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_14__["MSFT_minecraftMesh"]; });
+/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_14__["MSFT_lod"]; });
 
-/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_15__["MSFT_sRGBFactors"]; });
+/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_15__["MSFT_minecraftMesh"]; });
 
-/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_16__["ExtrasAsMetadata"]; });
+/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_16__["MSFT_sRGBFactors"]; });
 
+/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_17__["ExtrasAsMetadata"]; });
+
+
 
 
 
@@ -4128,7 +4210,7 @@ _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFFileLoader"]._CreateGLTF2Loade
 /*!***************************!*\
   !*** ./glTF/2.0/index.ts ***!
   \***************************/
-/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -4157,6 +4239,8 @@ __webpack_require__.r(__webpack_exports__);
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_specular"]; });
 
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_ior"]; });
+
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_mesh_quantization"]; });
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_texture_basisu"]; });

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.js.map


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 107 - 23
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -3304,6 +3304,78 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 
 /***/ }),
 
+/***/ "./glTF/2.0/Extensions/KHR_materials_ior.ts":
+/*!**************************************************!*\
+  !*** ./glTF/2.0/Extensions/KHR_materials_ior.ts ***!
+  \**************************************************/
+/*! exports provided: KHR_materials_ior */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return KHR_materials_ior; });
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
+
+
+var NAME = "KHR_materials_ior";
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1718)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+var KHR_materials_ior = /** @class */ (function () {
+    /** @hidden */
+    function KHR_materials_ior(loader) {
+        /**
+         * The name of this extension.
+         */
+        this.name = NAME;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        this.order = 180;
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+    /** @hidden */
+    KHR_materials_ior.prototype.dispose = function () {
+        delete this._loader;
+    };
+    /** @hidden */
+    KHR_materials_ior.prototype.loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
+        var _this = this;
+        return _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].LoadExtensionAsync(context, material, this.name, function (extensionContext, extension) {
+            var promises = new Array();
+            promises.push(_this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial));
+            promises.push(_this._loadIorPropertiesAsync(extensionContext, extension, babylonMaterial));
+            return Promise.all(promises).then(function () { });
+        });
+    };
+    KHR_materials_ior.prototype._loadIorPropertiesAsync = function (context, properties, babylonMaterial) {
+        if (!(babylonMaterial instanceof babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["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();
+    };
+    /**
+     * Default ior Value from the spec.
+     */
+    KHR_materials_ior._DEFAULT_IOR = 1.5;
+    return KHR_materials_ior;
+}());
+
+_glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, function (loader) { return new KHR_materials_ior(loader); });
+
+
+/***/ }),
+
 /***/ "./glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts":
 /*!********************************************************************!*\
   !*** ./glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts ***!
@@ -3496,10 +3568,10 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
 
+
 var NAME = "KHR_materials_specular";
 /**
- * [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 !!!
  */
 var KHR_materials_specular = /** @class */ (function () {
@@ -3534,14 +3606,20 @@ var KHR_materials_specular = /** @class */ (function () {
         if (!(babylonMaterial instanceof babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["PBRMaterial"])) {
             throw new Error(context + ": Material type not supported");
         }
+        var promises = new Array();
         if (properties.specularFactor !== undefined) {
             babylonMaterial.metallicF0Factor = properties.specularFactor;
         }
+        if (properties.specularColorFactor !== undefined) {
+            babylonMaterial.metallicReflectanceColor = babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["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, function (texture) {
+                texture.name = babylonMaterial.name + " (Specular F0 Color)";
+                babylonMaterial.metallicReflectanceTexture = texture;
+            }));
         }
-        return Promise.resolve();
+        return Promise.all(promises).then(function () { });
     };
     return KHR_materials_specular;
 }());
@@ -4472,7 +4550,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 /*!**************************************!*\
   !*** ./glTF/2.0/Extensions/index.ts ***!
   \**************************************/
-/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -4504,30 +4582,34 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _KHR_materials_specular__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./KHR_materials_specular */ "./glTF/2.0/Extensions/KHR_materials_specular.ts");
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return _KHR_materials_specular__WEBPACK_IMPORTED_MODULE_8__["KHR_materials_specular"]; });
 
-/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_9__["KHR_mesh_quantization"]; });
+/* harmony import */ var _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_materials_ior */ "./glTF/2.0/Extensions/KHR_materials_ior.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__["KHR_materials_ior"]; });
 
-/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_10__["KHR_texture_basisu"]; });
+/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__["KHR_mesh_quantization"]; });
 
-/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_transform"]; });
+/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_basisu"]; });
 
-/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_12__["MSFT_audio_emitter"]; });
+/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_transform"]; });
 
-/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_13__["MSFT_lod"]; });
+/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_13__["MSFT_audio_emitter"]; });
 
-/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_14__["MSFT_minecraftMesh"]; });
+/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_14__["MSFT_lod"]; });
 
-/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_15__["MSFT_sRGBFactors"]; });
+/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_15__["MSFT_minecraftMesh"]; });
 
-/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_16__["ExtrasAsMetadata"]; });
+/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_16__["MSFT_sRGBFactors"]; });
 
+/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_17__["ExtrasAsMetadata"]; });
+
+
 
 
 
@@ -6708,7 +6790,7 @@ _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFFileLoader"]._CreateGLTF2Loade
 /*!***************************!*\
   !*** ./glTF/2.0/index.ts ***!
   \***************************/
-/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -6737,6 +6819,8 @@ __webpack_require__.r(__webpack_exports__);
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_specular"]; });
 
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_ior"]; });
+
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_mesh_quantization"]; });
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_texture_basisu"]; });

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.js.map


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 33 - 2
dist/preview release/loaders/babylonjs.loaders.d.ts

@@ -1850,8 +1850,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
-     * [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 {
@@ -1879,6 +1878,38 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
+     * [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;
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        order: number;
+        private _loader;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /** @hidden */
+        loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        private _loadIorPropertiesAsync;
+    }
+}
+declare module BABYLON.GLTF2.Loader.Extensions {
+    /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization)
      */
     export class KHR_mesh_quantization implements IGLTFLoaderExtension {

+ 107 - 23
dist/preview release/loaders/babylonjs.loaders.js

@@ -4684,6 +4684,78 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 
 /***/ }),
 
+/***/ "./glTF/2.0/Extensions/KHR_materials_ior.ts":
+/*!**************************************************!*\
+  !*** ./glTF/2.0/Extensions/KHR_materials_ior.ts ***!
+  \**************************************************/
+/*! exports provided: KHR_materials_ior */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return KHR_materials_ior; });
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/PBR/pbrMaterial */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__);
+/* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
+
+
+var NAME = "KHR_materials_ior";
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1718)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+var KHR_materials_ior = /** @class */ (function () {
+    /** @hidden */
+    function KHR_materials_ior(loader) {
+        /**
+         * The name of this extension.
+         */
+        this.name = NAME;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        this.order = 180;
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+    /** @hidden */
+    KHR_materials_ior.prototype.dispose = function () {
+        delete this._loader;
+    };
+    /** @hidden */
+    KHR_materials_ior.prototype.loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
+        var _this = this;
+        return _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].LoadExtensionAsync(context, material, this.name, function (extensionContext, extension) {
+            var promises = new Array();
+            promises.push(_this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial));
+            promises.push(_this._loadIorPropertiesAsync(extensionContext, extension, babylonMaterial));
+            return Promise.all(promises).then(function () { });
+        });
+    };
+    KHR_materials_ior.prototype._loadIorPropertiesAsync = function (context, properties, babylonMaterial) {
+        if (!(babylonMaterial instanceof babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["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();
+    };
+    /**
+     * Default ior Value from the spec.
+     */
+    KHR_materials_ior._DEFAULT_IOR = 1.5;
+    return KHR_materials_ior;
+}());
+
+_glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, function (loader) { return new KHR_materials_ior(loader); });
+
+
+/***/ }),
+
 /***/ "./glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts":
 /*!********************************************************************!*\
   !*** ./glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts ***!
@@ -4876,10 +4948,10 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
 
 
+
 var NAME = "KHR_materials_specular";
 /**
- * [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 !!!
  */
 var KHR_materials_specular = /** @class */ (function () {
@@ -4914,14 +4986,20 @@ var KHR_materials_specular = /** @class */ (function () {
         if (!(babylonMaterial instanceof babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["PBRMaterial"])) {
             throw new Error(context + ": Material type not supported");
         }
+        var promises = new Array();
         if (properties.specularFactor !== undefined) {
             babylonMaterial.metallicF0Factor = properties.specularFactor;
         }
+        if (properties.specularColorFactor !== undefined) {
+            babylonMaterial.metallicReflectanceColor = babylonjs_Materials_PBR_pbrMaterial__WEBPACK_IMPORTED_MODULE_0__["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, function (texture) {
+                texture.name = babylonMaterial.name + " (Specular F0 Color)";
+                babylonMaterial.metallicReflectanceTexture = texture;
+            }));
         }
-        return Promise.resolve();
+        return Promise.all(promises).then(function () { });
     };
     return KHR_materials_specular;
 }());
@@ -5852,7 +5930,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 /*!**************************************!*\
   !*** ./glTF/2.0/Extensions/index.ts ***!
   \**************************************/
-/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -5884,30 +5962,34 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _KHR_materials_specular__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./KHR_materials_specular */ "./glTF/2.0/Extensions/KHR_materials_specular.ts");
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return _KHR_materials_specular__WEBPACK_IMPORTED_MODULE_8__["KHR_materials_specular"]; });
 
-/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_9__["KHR_mesh_quantization"]; });
+/* harmony import */ var _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_materials_ior */ "./glTF/2.0/Extensions/KHR_materials_ior.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__["KHR_materials_ior"]; });
 
-/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_10__["KHR_texture_basisu"]; });
+/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__["KHR_mesh_quantization"]; });
 
-/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_transform"]; });
+/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_basisu"]; });
 
-/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_12__["MSFT_audio_emitter"]; });
+/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_transform"]; });
 
-/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_13__["MSFT_lod"]; });
+/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_13__["MSFT_audio_emitter"]; });
 
-/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_14__["MSFT_minecraftMesh"]; });
+/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_14__["MSFT_lod"]; });
 
-/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_15__["MSFT_sRGBFactors"]; });
+/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_15__["MSFT_minecraftMesh"]; });
 
-/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_16__["ExtrasAsMetadata"]; });
+/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_16__["MSFT_sRGBFactors"]; });
 
+/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_17__["ExtrasAsMetadata"]; });
+
+
 
 
 
@@ -8088,7 +8170,7 @@ _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFFileLoader"]._CreateGLTF2Loade
 /*!***************************!*\
   !*** ./glTF/2.0/index.ts ***!
   \***************************/
-/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -8117,6 +8199,8 @@ __webpack_require__.r(__webpack_exports__);
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_specular", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_specular"]; });
 
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_ior"]; });
+
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_mesh_quantization"]; });
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_texture_basisu"]; });

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.js.map


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
dist/preview release/loaders/babylonjs.loaders.min.js


+ 72 - 4
dist/preview release/loaders/babylonjs.loaders.module.d.ts

@@ -1985,8 +1985,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular" {
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
     /**
-     * [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 {
@@ -2012,6 +2011,43 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular" {
         private _loadSpecularPropertiesAsync;
     }
 }
+declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior" {
+    import { Nullable } from "babylonjs/types";
+    import { Material } from "babylonjs/Materials/material";
+    import { IMaterial } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
+    import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
+    /**
+     * [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;
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        order: number;
+        private _loader;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /** @hidden */
+        loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        private _loadIorPropertiesAsync;
+    }
+}
 declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization" {
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
@@ -2276,6 +2312,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/index" {
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_clearcoat";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_sheen";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular";
+    export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_basisu";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_transform";
@@ -4515,8 +4552,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
-     * [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 {
@@ -4544,6 +4580,38 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
+     * [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;
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        order: number;
+        private _loader;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /** @hidden */
+        loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        private _loadIorPropertiesAsync;
+    }
+}
+declare module BABYLON.GLTF2.Loader.Extensions {
+    /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization)
      */
     export class KHR_mesh_quantization implements IGLTFLoaderExtension {

Разница между файлами не показана из-за своего большого размера
+ 19 - 9
dist/preview release/nodeEditor/babylon.nodeEditor.d.ts


Разница между файлами не показана из-за своего большого размера
+ 5 - 5
dist/preview release/nodeEditor/babylon.nodeEditor.js


Разница между файлами не показана из-за своего большого размера
+ 350 - 125
dist/preview release/nodeEditor/babylon.nodeEditor.max.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


Разница между файлами не показана из-за своего большого размера
+ 46 - 21
dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts


+ 1 - 1
dist/preview release/packagesSizeBaseLine.json

@@ -1 +1 @@
-{"thinEngineOnly":115808,"engineOnly":152211,"sceneOnly":511359,"minGridMaterial":644636,"minStandardMaterial":786986}
+{"thinEngineOnly":115859,"engineOnly":152262,"sceneOnly":511410,"minGridMaterial":644687,"minStandardMaterial":788187}

Разница между файлами не показана из-за своего большого размера
+ 1976 - 1055
dist/preview release/viewer/babylon.module.d.ts


Разница между файлами не показана из-за своего большого размера
+ 97 - 57
dist/preview release/viewer/babylon.viewer.js


Разница между файлами не показана из-за своего большого размера
+ 2 - 2
dist/preview release/viewer/babylon.viewer.max.js


+ 72 - 4
dist/preview release/viewer/babylonjs.loaders.module.d.ts

@@ -1985,8 +1985,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular" {
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
     /**
-     * [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 {
@@ -2012,6 +2011,43 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular" {
         private _loadSpecularPropertiesAsync;
     }
 }
+declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior" {
+    import { Nullable } from "babylonjs/types";
+    import { Material } from "babylonjs/Materials/material";
+    import { IMaterial } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
+    import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
+    /**
+     * [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;
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        order: number;
+        private _loader;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /** @hidden */
+        loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        private _loadIorPropertiesAsync;
+    }
+}
 declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization" {
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
@@ -2276,6 +2312,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/index" {
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_clearcoat";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_sheen";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular";
+    export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_basisu";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_transform";
@@ -4515,8 +4552,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
-     * [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 {
@@ -4544,6 +4580,38 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
+     * [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;
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        /**
+         * Defines a number that determines the order the extensions are applied.
+         */
+        order: number;
+        private _loader;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /** @hidden */
+        loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        private _loadIorPropertiesAsync;
+    }
+}
+declare module BABYLON.GLTF2.Loader.Extensions {
+    /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization)
      */
     export class KHR_mesh_quantization implements IGLTFLoaderExtension {

+ 19 - 3
dist/preview release/what's new.md

@@ -6,8 +6,9 @@
 - Added sprite editor to the Inspector ([Deltakosh](https://github.com/deltakosh))
 - Added the `ShadowDepthWrapper` class to support accurate shadow generation for custom as well as node material shaders. [Doc](https://doc.babylonjs.com/babylon101/shadows#custom-shadow-map-shaders) ([Popov72](https://github.com/Popov72))
 - 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 and Post processes 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 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
 
@@ -102,6 +105,7 @@
 - Default (XR-global) rendering group ID can be defined when initializing a default experience ([RaananW](https://github.com/RaananW))
 - Added support for (experimental) haptic actuators ([#8068](https://github.com/BabylonJS/Babylon.js/issues/8068)) ([RaananW](https://github.com/RaananW))
 - It is now possible to enable experimental (AR) features using the options of the default xr helper ([RaananW](https://github.com/RaananW))
+- Full support for right handed systems ([#8132](https://github.com/BabylonJS/Babylon.js/issues/8132)) ([RaananW](https://github.com/RaananW))
 
 ### Collisions
 
@@ -120,6 +124,15 @@
 - 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))
+
+### Audio
+
+- Added support of `metadata` in `Sound` class. ([julien-moreau](https://github.com/julien-moreau))
+
 ### Build
 
 - Fixed an issue with gulp webpack, webpack stream and the viewer ([RaananW](https://github.com/RaananW))
@@ -127,6 +140,7 @@
 ### Playground
 
 - Added support for code templates in the playground ([sailro](http://www.github.com/sailro))
+- If createEngine fails, a default engine will be created ([#8084](https://github.com/BabylonJS/Babylon.js/issues/8084)) ([RaananW](https://github.com/RaananW))
 
 ## Bugs
 
@@ -175,9 +189,11 @@
 - Fixed Path3D `_distances` / length computation ([Poolminer](https://github.com/Poolminer))
 - Make sure bone matrices are up to date when calling `TransformNode.attachToBone` ([Popov72](https://github.com/Popov72))
 - Fix display problem with transparent objects and SSAO2 pipeline (bug in the `GeometryBufferRenderer`) ([Popov72](https://github.com/Popov72))
+- Fixed `Sound` not accepting a `TransformNode` as a source for spatial sound ([Poolminer](https://github.com/Poolminer))
 
 ## Breaking changes
 
 - `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";

+ 13 - 1
nodeEditor/public/index.js

@@ -123,7 +123,19 @@ if (BABYLON.Engine.isSupported()) {
 
     // Set to default
     if (!location.hash) {
-        nodeMaterial.setToDefault();
+        const mode = BABYLON.DataStorage.ReadNumber("Mode", BABYLON.NodeMaterialModes.Material);
+        
+        switch (mode) {
+            case BABYLON.NodeMaterialModes.Material:
+                nodeMaterial.setToDefault();
+                break;
+            case BABYLON.NodeMaterialModes.PostProcess:
+                nodeMaterial.setToDefaultPostProcess();
+                break;
+            case BABYLON.NodeMaterialModes.Particle:
+                nodeMaterial.setToDefaultParticle();
+                break;
+        }
         nodeMaterial.build(true);
         showEditor();
     }

+ 26 - 1
nodeEditor/src/blockTools.ts

@@ -72,6 +72,10 @@ import { ClearCoatBlock } from 'babylonjs/Materials/Node/Blocks/PBR/clearCoatBlo
 import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
 import { SubSurfaceBlock } from 'babylonjs/Materials/Node/Blocks/PBR/subSurfaceBlock';
 import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
+import { ParticleTextureBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleTextureBlock';
+import { ParticleRampGradientBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleRampGradientBlock';
+import { ParticleBlendMultiplyBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleBlendMultiplyBlock';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 
 export class BlockTools {
     public static GetBlockFromString(data: string, scene: Scene, nodeMaterial: NodeMaterial) {
@@ -111,7 +115,7 @@ export class BlockTools {
             case "VectorSplitterBlock":
                 return new VectorSplitterBlock("VectorSplitter");
             case "TextureBlock":
-                return new TextureBlock("Texture");
+                return new TextureBlock("Texture", nodeMaterial.mode === NodeMaterialModes.Particle);
             case "ReflectionTextureBlock":
                 return new ReflectionTextureBlock("Reflection texture");
             case "LightBlock":
@@ -467,6 +471,27 @@ export class BlockTools {
                 return new SubSurfaceBlock("SubSurface");
             case "CurrentScreenBlock":
                 return new CurrentScreenBlock("CurrentScreen");
+            case "ParticleUVBlock": {
+                let uv = new InputBlock("uv");
+                uv.setAsAttribute("particle_uv");
+                return uv;
+            }
+            case "ParticleTextureBlock":
+                return new ParticleTextureBlock("ParticleTexture");
+            case "ParticleColorBlock": {
+                let color = new InputBlock("Color");
+                color.setAsAttribute("particle_color");
+                return color;
+            }
+            case "ParticleTextureMaskBlock": {
+                let u = new InputBlock("TextureMask");
+                u.setAsAttribute("particle_texturemask");
+                return u;
+            }
+            case "ParticleRampGradientBlock":
+                return new ParticleRampGradientBlock("ParticleRampGradient");
+            case "ParticleBlendMultiplyBlock":
+                return new ParticleBlendMultiplyBlock("ParticleBlendMultiply");
         }
 
         return null;

+ 15 - 0
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -133,6 +133,12 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "SubSurfaceBlock": "PBR SubSurface block",
         "Position2DBlock": "A Vector2 representing the position of each vertex of the screen quad",
         "CurrentScreenBlock": "The screen buffer used as input for the post process",
+        "ParticleUVBlock": "The particle uv texture coordinate",
+        "ParticleTextureBlock": "The particle texture",
+        "ParticleColorBlock": "The particle color",
+        "ParticleTextureMaskBlock": "The particle texture mask",
+        "ParticleRampGradientBlock": "The particle ramp gradient block",
+        "ParticleBlendMultiplyBlock": "The particle blend multiply block",
     };
 
     constructor(props: INodeListComponentProps) {
@@ -169,6 +175,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             Mesh: ["InstancesBlock", "PositionBlock", "UVBlock", "ColorBlock", "NormalBlock", "PerturbNormalBlock", "NormalBlendBlock" , "TangentBlock", "MatrixIndicesBlock", "MatrixWeightsBlock", "WorldPositionBlock", "WorldNormalBlock", "WorldTangentBlock", "FrontFacingBlock"],
             Noises: ["RandomNumberBlock", "SimplexPerlin3DBlock", "WorleyNoise3DBlock"],
             Output_Nodes: ["VertexOutputBlock", "FragmentOutputBlock", "DiscardBlock"],
+            Particle: ["ParticleBlendMultiplyBlock", "ParticleColorBlock", "ParticleRampGradientBlock", "ParticleTextureBlock", "ParticleTextureMaskBlock", "ParticleUVBlock"],
             PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
             PostProcess: ["Position2DBlock", "CurrentScreenBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],
@@ -179,10 +186,18 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         switch (this.props.globalState.mode) {
             case NodeMaterialModes.Material:
                 delete allBlocks["PostProcess"];
+                delete allBlocks["Particle"];
                 break;
             case NodeMaterialModes.PostProcess:
                 delete allBlocks["Animation"];
                 delete allBlocks["Mesh"];
+                delete allBlocks["Particle"];
+                break;
+            case NodeMaterialModes.Particle:
+                delete allBlocks["Animation"];
+                delete allBlocks["Mesh"];
+                delete allBlocks["PostProcess"];
+                allBlocks.Output_Nodes.splice(allBlocks.Output_Nodes.indexOf("VertexOutputBlock"), 1);
                 break;
         }
 

+ 1 - 1
nodeEditor/src/components/preview/previewAreaComponent.tsx

@@ -62,7 +62,7 @@ export class PreviewAreaComponent extends React.Component<IPreviewAreaComponentP
                         </div>
                     }
                 </div>
-                { this.props.globalState.mode !== NodeMaterialModes.PostProcess && <>
+                { this.props.globalState.mode === NodeMaterialModes.Material && <>
                     <div id="preview-config-bar">
                         <div
                             title="Render without back face culling"

+ 199 - 71
nodeEditor/src/components/preview/previewManager.ts

@@ -8,7 +8,7 @@ import { Mesh } from 'babylonjs/Meshes/mesh';
 import { Vector3 } from 'babylonjs/Maths/math.vector';
 import { HemisphericLight } from 'babylonjs/Lights/hemisphericLight';
 import { ArcRotateCamera } from 'babylonjs/Cameras/arcRotateCamera';
-import { PreviewMeshType } from './previewMeshType';
+import { PreviewType } from './previewType';
 import { Animation } from 'babylonjs/Animations/animation';
 import { SceneLoader } from 'babylonjs/Loading/sceneLoader';
 import { TransformNode } from 'babylonjs/Meshes/transformNode';
@@ -17,11 +17,17 @@ import { FramingBehavior } from 'babylonjs/Behaviors/Cameras/framingBehavior';
 import { DirectionalLight } from 'babylonjs/Lights/directionalLight';
 import { LogEntry } from '../log/logComponent';
 import { PointerEventTypes } from 'babylonjs/Events/pointerEvents';
-import { Color3 } from 'babylonjs/Maths/math.color';
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
 import { PostProcess } from 'babylonjs/PostProcesses/postProcess';
 import { Constants } from 'babylonjs/Engines/constants';
 import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
 import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
+import { ParticleSystem } from 'babylonjs/Particles/particleSystem';
+import { IParticleSystem } from 'babylonjs/Particles/IParticleSystem';
+import { ParticleHelper } from 'babylonjs/Particles/particleHelper';
+import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { ParticleTextureBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleTextureBlock';
+import { FileTools } from 'babylonjs/Misc/fileTools';
 
 export class PreviewManager {
     private _nodeMaterial: NodeMaterial;
@@ -42,6 +48,7 @@ export class PreviewManager {
     private _currentType: number;
     private _lightParent: TransformNode;
     private _postprocess: Nullable<PostProcess>;
+    private _particleSystem: Nullable<IParticleSystem>;
 
     public constructor(targetCanvas: HTMLCanvasElement, globalState: GlobalState) {
         this._nodeMaterial = globalState.nodeMaterial;
@@ -104,9 +111,7 @@ export class PreviewManager {
             this._scene.render();
         });
 
-   //     let cameraLastRotation = 0;
         let lastOffsetX: number | undefined = undefined;
-     //   const lightRotationParallaxSpeed = 0.5;
         const lightRotationSpeed = 0.01;
 
         this._scene.onPointerObservable.add((evt) => {
@@ -193,35 +198,47 @@ export class PreviewManager {
         }
     }
 
-    private _prepareMeshes() {
-        if (this._globalState.mode !== NodeMaterialModes.PostProcess) {
-            this._prepareLights();
+    private _prepareScene() {
+        this._camera.useFramingBehavior = this._globalState.mode === NodeMaterialModes.Material;
 
-            // Framing
-            this._camera.useFramingBehavior = true;
+        switch (this._globalState.mode) {
+            case NodeMaterialModes.Material: {
+                this._prepareLights();
 
-            var framingBehavior = this._camera.getBehaviorByName("Framing") as FramingBehavior;
+                var framingBehavior = this._camera.getBehaviorByName("Framing") as FramingBehavior;
 
-            setTimeout(() => { // Let the behavior activate first
-                framingBehavior.framingTime = 0;
-                framingBehavior.elevationReturnTime = -1;
+                setTimeout(() => { // Let the behavior activate first
+                    framingBehavior.framingTime = 0;
+                    framingBehavior.elevationReturnTime = -1;
 
-                if (this._scene.meshes.length) {
-                    var worldExtends = this._scene.getWorldExtends();
-                    this._camera.lowerRadiusLimit = null;
-                    this._camera.upperRadiusLimit = null;
-                    framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
-                }
+                    if (this._scene.meshes.length) {
+                        var worldExtends = this._scene.getWorldExtends();
+                        this._camera.lowerRadiusLimit = null;
+                        this._camera.upperRadiusLimit = null;
+                        framingBehavior.zoomOnBoundingInfo(worldExtends.min, worldExtends.max);
+                    }
 
-                this._camera.pinchPrecision = 200 / this._camera.radius;
-                this._camera.upperRadiusLimit = 5 * this._camera.radius;
-            });
+                    this._camera.pinchPrecision = 200 / this._camera.radius;
+                    this._camera.upperRadiusLimit = 5 * this._camera.radius;
+                });
 
-            this._camera.wheelDeltaPercentage = 0.01;
-            this._camera.pinchDeltaPercentage = 0.01;
+                this._camera.wheelDeltaPercentage = 0.01;
+                this._camera.pinchDeltaPercentage = 0.01;
 
-            // Animations
-            this._handleAnimations();
+                // Animations
+                this._handleAnimations();
+                break;
+            }
+            case NodeMaterialModes.PostProcess: {
+                this._camera.radius = 4;
+                this._camera.upperRadiusLimit = 10;
+                break;
+            }
+            case NodeMaterialModes.Particle: {
+                this._camera.radius = this._globalState.previewType === PreviewType.Explosion ? 50 : this._globalState.previewType === PreviewType.DefaultParticleSystem ? 6 : 20;
+                this._camera.upperRadiusLimit = 5000;
+                break;
+            }
         }
 
         // Material
@@ -230,9 +247,8 @@ export class PreviewManager {
     }
 
     private _refreshPreviewMesh() {
-
-        if (this._currentType !== this._globalState.previewMeshType || this._currentType === PreviewMeshType.Custom) {
-            this._currentType = this._globalState.previewMeshType;
+        if (this._currentType !== this._globalState.previewType || this._currentType === PreviewType.Custom) {
+            this._currentType = this._globalState.previewType;
             if (this._meshes && this._meshes.length) {
                 for (var mesh of this._meshes) {
                     mesh.dispose();
@@ -247,55 +263,144 @@ export class PreviewManager {
 
             this._engine.releaseEffects();
 
+            if (this._particleSystem) {
+                this._particleSystem.onBeforeDrawParticlesObservable.clear();
+                this._particleSystem.onDisposeObservable.clear();
+                this._particleSystem.stop();
+                this._particleSystem.dispose();
+                this._particleSystem = null;
+            }
+
             SceneLoader.ShowLoadingScreen = false;
 
             this._globalState.onIsLoadingChanged.notifyObservers(true);
 
-            if (this._globalState.mode !== NodeMaterialModes.PostProcess) {
-                switch (this._globalState.previewMeshType) {
-                    case PreviewMeshType.Box:
+            if (this._globalState.mode === NodeMaterialModes.Material) {
+                switch (this._globalState.previewType) {
+                    case PreviewType.Box:
                         SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCube.glb", this._scene).then(() => {
                             this._meshes.push(...this._scene.meshes);
-                            this._prepareMeshes();
+                            this._prepareScene();
                         });
                         return;
-                    case PreviewMeshType.Sphere:
+                    case PreviewType.Sphere:
                         this._meshes.push(Mesh.CreateSphere("dummy-sphere", 32, 2, this._scene));
                         break;
-                    case PreviewMeshType.Torus:
+                    case PreviewType.Torus:
                         this._meshes.push(Mesh.CreateTorus("dummy-torus", 2, 0.5, 32, this._scene));
                         break;
-                    case PreviewMeshType.Cylinder:
+                    case PreviewType.Cylinder:
                         SceneLoader.AppendAsync("https://models.babylonjs.com/", "roundedCylinder.glb", this._scene).then(() => {
                             this._meshes.push(...this._scene.meshes);
-                            this._prepareMeshes();
+                            this._prepareScene();
                         });
                         return;
-                    case PreviewMeshType.Plane:
+                    case PreviewType.Plane:
                         let plane = Mesh.CreateGround("dummy-plane", 2, 2, 128, this._scene);
                         plane.scaling.y = -1;
                         plane.rotation.x = Math.PI;
                         this._meshes.push(plane);
                         break;
-                    case PreviewMeshType.ShaderBall:
+                    case PreviewType.ShaderBall:
                         SceneLoader.AppendAsync("https://models.babylonjs.com/", "shaderBall.glb", this._scene).then(() => {
                             this._meshes.push(...this._scene.meshes);
-                            this._prepareMeshes();
+                            this._prepareScene();
                         });
                         return;
-                    case PreviewMeshType.Custom:
-                        SceneLoader.AppendAsync("file:", this._globalState.previewMeshFile, this._scene).then(() => {
+                    case PreviewType.Custom:
+                        SceneLoader.AppendAsync("file:", this._globalState.previewFile, this._scene).then(() => {
                             this._meshes.push(...this._scene.meshes);
-                            this._prepareMeshes();
+                            this._prepareScene();
+                        });
+                        return;
+                }
+            } else if (this._globalState.mode === NodeMaterialModes.Particle) {
+                switch (this._globalState.previewType) {
+                    case PreviewType.DefaultParticleSystem:
+                        this._particleSystem = ParticleHelper.CreateDefault(new Vector3(0, 0, 0), 500, this._scene);
+                        this._particleSystem.start();
+                        break;
+                    case PreviewType.Bubbles:
+                        this._particleSystem = new ParticleSystem("particles", 4000, this._scene);
+                        this._particleSystem.particleTexture = new Texture("https://assets.babylonjs.com/particles/textures/explosion/Flare.png", this._scene);
+                        this._particleSystem.minSize = 0.1;
+                        this._particleSystem.maxSize = 1.0;
+                        this._particleSystem.minLifeTime = 0.5;
+                        this._particleSystem.maxLifeTime = 5.0;
+                        this._particleSystem.minEmitPower = 0.5;
+                        this._particleSystem.maxEmitPower = 3.0;
+                        this._particleSystem.createBoxEmitter(new Vector3(-1, 1, -1), new Vector3(1, 1, 1), new Vector3(-0.1, -0.1, -0.1), new Vector3(0.1, 0.1, 0.1));
+                        this._particleSystem.emitRate = 100;
+                        this._particleSystem.blendMode = ParticleSystem.BLENDMODE_ONEONE;
+                        this._particleSystem.color1 = new Color4(1, 1, 0, 1);
+                        this._particleSystem.color2 = new Color4(1, 0.5, 0, 1);
+                        this._particleSystem.gravity = new Vector3(0, -1.0, 0);
+                        this._particleSystem.start();
+                        break;
+                    case PreviewType.Explosion:
+                        this._loadParticleSystem(this._globalState.previewType, 1);
+                        return;
+                    case PreviewType.Fire:
+                    case PreviewType.Rain:
+                    case PreviewType.Smoke:
+                        this._loadParticleSystem(this._globalState.previewType);
+                        return;
+                    case PreviewType.Custom:
+                        FileTools.ReadFile(this._globalState.previewFile, (json) =>  {
+                            this._particleSystem = ParticleSystem.Parse(JSON.parse(json), this._scene, "");
+                            this._particleSystem.start();
+                            this._prepareScene();
+                        }, undefined, false, (error) => {
+                            console.log(error);
                         });
                         return;
                 }
             }
 
-            this._prepareMeshes();
+            this._prepareScene();
         }
     }
 
+    private _loadParticleSystem(particleNumber: number, systemIndex = 0, prepareScene = true) {
+        let name = "";
+
+        switch (particleNumber) {
+            case PreviewType.Explosion:
+                name = "explosion";
+                break;
+            case PreviewType.Fire:
+                name = "fire";
+                break;
+            case PreviewType.Rain:
+                name = "rain";
+                break;
+            case PreviewType.Smoke:
+                name = "smoke";
+                break;
+        }
+
+        ParticleHelper.CreateAsync(name, this._scene).then((set) => {
+            for (let i = 0; i < set.systems.length; ++i) {
+                if (i == systemIndex) {
+                    this._particleSystem = set.systems[i];
+                    this._particleSystem.disposeOnStop = true;
+                    this._particleSystem.onDisposeObservable.add(() => {
+                        this._loadParticleSystem(particleNumber, systemIndex, false);
+                    });
+                    this._particleSystem.start();
+                } else {
+                    set.systems[i].dispose();
+                }
+            }
+            if (prepareScene) {
+                this._prepareScene();
+            } else {
+                let serializationObject = this._nodeMaterial.serialize();
+                this._updatePreview(serializationObject);
+            }
+        });
+    }
+
     private _forceCompilationAsync(material: NodeMaterial, mesh: AbstractMesh): Promise<void> {
         return material.forceCompilationAsync(mesh);
     }
@@ -312,42 +417,65 @@ export class PreviewManager {
                 this._postprocess = null;
             }
 
-            if (this._globalState.mode === NodeMaterialModes.PostProcess) {
-                this._globalState.onIsLoadingChanged.notifyObservers(false);
+            switch (this._globalState.mode) {
+                case NodeMaterialModes.PostProcess: {
+                    this._globalState.onIsLoadingChanged.notifyObservers(false);
 
-                this._postprocess = tempMaterial.createPostProcess(this._camera, 1.0, Constants.TEXTURE_NEAREST_SAMPLINGMODE, this._engine);
+                    this._postprocess = tempMaterial.createPostProcess(this._camera, 1.0, Constants.TEXTURE_NEAREST_SAMPLINGMODE, this._engine);
 
-                const currentScreen = tempMaterial.getBlockByPredicate((block) => block instanceof CurrentScreenBlock);
-                if (currentScreen) {
-                    this._postprocess!.onApplyObservable.add((effect) => {
-                        effect.setTexture("textureSampler", (currentScreen as CurrentScreenBlock).texture);
-                    });
-                }
-
-                if (this._material) {
-                    this._material.dispose();
-                }
-                this._material = tempMaterial;
-            } else if (this._meshes.length) {
-                let tasks = this._meshes.map((m) => this._forceCompilationAsync(tempMaterial, m));
-
-                Promise.all(tasks).then(() => {
-                    for (var mesh of this._meshes) {
-                        mesh.material = tempMaterial;
+                    const currentScreen = tempMaterial.getBlockByPredicate((block) => block instanceof CurrentScreenBlock);
+                    if (currentScreen) {
+                        this._postprocess!.onApplyObservable.add((effect) => {
+                            effect.setTexture("textureSampler", (currentScreen as CurrentScreenBlock).texture);
+                        });
                     }
 
                     if (this._material) {
                         this._material.dispose();
                     }
-
                     this._material = tempMaterial;
+                    break;
+                }
+
+                case NodeMaterialModes.Particle: {
                     this._globalState.onIsLoadingChanged.notifyObservers(false);
-                }).catch((reason) => {
-                    this._globalState.onLogRequiredObservable.notifyObservers(new LogEntry("Shader compilation error:\r\n" + reason, true));
-                    this._globalState.onIsLoadingChanged.notifyObservers(false);
-                });
-            } else {
-                this._material = tempMaterial;
+
+                    this._particleSystem!.onBeforeDrawParticlesObservable.clear();
+
+                    this._particleSystem!.onBeforeDrawParticlesObservable.add((effect) => {
+                        const textureBlock = tempMaterial.getBlockByPredicate((block) => block instanceof ParticleTextureBlock);
+                        if (textureBlock && (textureBlock as ParticleTextureBlock).texture && effect) {
+                            effect.setTexture("diffuseSampler", (textureBlock as ParticleTextureBlock).texture);
+                        }
+                    });
+                    tempMaterial.createEffectForParticles(this._particleSystem!);
+                    break;
+                }
+
+                default: {
+                    if (this._meshes.length) {
+                        let tasks = this._meshes.map((m) => this._forceCompilationAsync(tempMaterial, m));
+
+                        Promise.all(tasks).then(() => {
+                            for (var mesh of this._meshes) {
+                                mesh.material = tempMaterial;
+                            }
+
+                            if (this._material) {
+                                this._material.dispose();
+                            }
+
+                            this._material = tempMaterial;
+                            this._globalState.onIsLoadingChanged.notifyObservers(false);
+                        }).catch((reason) => {
+                            this._globalState.onLogRequiredObservable.notifyObservers(new LogEntry("Shader compilation error:\r\n" + reason, true));
+                            this._globalState.onIsLoadingChanged.notifyObservers(false);
+                        });
+                    } else {
+                        this._material = tempMaterial;
+                    }
+                    break;
+                }
             }
         } catch (err) {
             // Ignore the error

+ 40 - 21
nodeEditor/src/components/preview/previewMeshControlComponent.tsx

@@ -2,7 +2,7 @@
 import * as React from "react";
 import { GlobalState } from '../../globalState';
 import { Color3, Color4 } from 'babylonjs/Maths/math.color';
-import { PreviewMeshType } from './previewMeshType';
+import { PreviewType } from './previewType';
 import { DataStorage } from 'babylonjs/Misc/dataStorage';
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { Observer } from 'babylonjs/Misc/observable';
@@ -38,15 +38,15 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
         this.props.globalState.onResetRequiredObservable.remove(this._onResetRequiredObserver);
     }
 
-    changeMeshType(newOne: PreviewMeshType) {
-        if (this.props.globalState.previewMeshType === newOne) {
+    changeMeshType(newOne: PreviewType) {
+        if (this.props.globalState.previewType === newOne) {
             return;
         }
 
-        this.props.globalState.previewMeshType = newOne;
+        this.props.globalState.previewType = newOne;
         this.props.globalState.onPreviewCommandActivated.notifyObservers(false);
 
-        DataStorage.WriteNumber("PreviewMeshType", newOne);
+        DataStorage.WriteNumber("PreviewType", newOne);
 
         this.forceUpdate();
     }
@@ -56,10 +56,10 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
         if (files && files.length) {
             let file = files[0];
 
-            this.props.globalState.previewMeshFile = file;
-            this.props.globalState.previewMeshType = PreviewMeshType.Custom;
+            this.props.globalState.previewFile = file;
+            this.props.globalState.previewType = PreviewType.Custom;
             this.props.globalState.onPreviewCommandActivated.notifyObservers(false);
-            this.props.globalState.listOfCustomPreviewMeshFiles = [file];
+            this.props.globalState.listOfCustomPreviewFiles = [file];
             this.forceUpdate();
         }
         if (this.filePickerRef.current) {
@@ -96,28 +96,45 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
     render() {
 
         var meshTypeOptions = [
-            { label: "Cube", value: PreviewMeshType.Box },
-            { label: "Cylinder", value: PreviewMeshType.Cylinder },
-            { label: "Plane", value: PreviewMeshType.Plane },
-            { label: "Shader ball", value: PreviewMeshType.ShaderBall },
-            { label: "Sphere", value: PreviewMeshType.Sphere },
-            { label: "Load...", value: PreviewMeshType.Custom + 1 }
+            { label: "Cube", value: PreviewType.Box },
+            { label: "Cylinder", value: PreviewType.Cylinder },
+            { label: "Plane", value: PreviewType.Plane },
+            { label: "Shader ball", value: PreviewType.ShaderBall },
+            { label: "Sphere", value: PreviewType.Sphere },
+            { label: "Load...", value: PreviewType.Custom + 1 }
         ];
 
-        if (this.props.globalState.listOfCustomPreviewMeshFiles.length > 0) {
+        var particleTypeOptions = [
+            { label: "Default", value: PreviewType.DefaultParticleSystem },
+            { label: "Bubbles", value: PreviewType.Bubbles },
+            { label: "Explosion", value: PreviewType.Explosion },
+            { label: "Fire", value: PreviewType.Fire },
+            { label: "Rain", value: PreviewType.Rain },
+            { label: "Smoke", value: PreviewType.Smoke },
+            { label: "Load...", value: PreviewType.Custom + 1 }
+        ];
+
+        if (this.props.globalState.listOfCustomPreviewFiles.length > 0) {
             meshTypeOptions.splice(0, 0, {
-                label: "Custom", value: PreviewMeshType.Custom
+                label: "Custom", value: PreviewType.Custom
+            });
+
+            particleTypeOptions.splice(0, 0, {
+                label: "Custom", value: PreviewType.Custom
             });
         }
 
+        var options = this.props.globalState.mode === NodeMaterialModes.Particle ? particleTypeOptions : meshTypeOptions;
+        var accept = this.props.globalState.mode === NodeMaterialModes.Particle ? ".json" : ".gltf, .glb, .babylon, .obj";
+
         return (
             <div id="preview-mesh-bar">
-                { this.props.globalState.mode !== NodeMaterialModes.PostProcess && <>
-                    <OptionsLineComponent label="" options={meshTypeOptions} target={this.props.globalState}
-                                propertyName="previewMeshType"
+                { (this.props.globalState.mode === NodeMaterialModes.Material || this.props.globalState.mode === NodeMaterialModes.Particle) && <>
+                    <OptionsLineComponent label="" options={options} target={this.props.globalState}
+                                propertyName="previewType"
                                 noDirectUpdate={true}
                                 onSelect={(value: any) => {
-                                    if (value !== PreviewMeshType.Custom + 1) {
+                                    if (value !== PreviewType.Custom + 1) {
                                         this.changeMeshType(value);
                                     } else {
                                         this.filePickerRef.current?.click();
@@ -126,8 +143,10 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
                     <div style={{
                         display: "none"
                     }} title="Preview with a custom mesh" >
-                        <input ref={this.filePickerRef} id="file-picker" type="file" onChange={(evt) => this.useCustomMesh(evt)} accept=".gltf, .glb, .babylon, .obj"/>
+                        <input ref={this.filePickerRef} id="file-picker" type="file" onChange={(evt) => this.useCustomMesh(evt)} accept={accept}/>
                     </div>
+                </> }
+                { this.props.globalState.mode === NodeMaterialModes.Material && <>
                     <div
                         title="Turn-table animation"
                         onClick={() => this.changeAnimation()} className="button" id="play-button">

+ 0 - 9
nodeEditor/src/components/preview/previewMeshType.ts

@@ -1,9 +0,0 @@
-export enum PreviewMeshType {
-    Sphere,
-    Box, 
-    Torus,
-    Cylinder,
-    Plane,
-    ShaderBall,
-    Custom
-}

+ 19 - 0
nodeEditor/src/components/preview/previewType.ts

@@ -0,0 +1,19 @@
+export enum PreviewType {
+    // Meshes
+    Sphere,
+    Box,
+    Torus,
+    Cylinder,
+    Plane,
+    ShaderBall,
+
+    // Particle systems
+    DefaultParticleSystem,
+    Bubbles,
+    Smoke,
+    Rain,
+    Explosion,
+    Fire,
+
+    Custom,
+}

+ 20 - 3
nodeEditor/src/components/propertyTab/propertyTabComponent.tsx

@@ -33,6 +33,7 @@ import { NodePort } from '../../diagram/nodePort';
 import { isFramePortData } from '../../diagram/graphCanvas';
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
+import { PreviewType } from '../preview/previewType';
 require("./propertyTab.scss");
 
 interface IPropertyTabComponentProps {
@@ -263,19 +264,29 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
             this._modeSelect.current?.setValue(value);
         }
 
-        this.props.globalState.mode = value as NodeMaterialModes;
-
         if (loadDefault) {
             switch (value) {
                 case NodeMaterialModes.Material:
+                    this.props.globalState.previewType = PreviewType.Sphere;
                     this.props.globalState.nodeMaterial!.setToDefault();
                     break;
                 case NodeMaterialModes.PostProcess:
                     this.props.globalState.nodeMaterial!.setToDefaultPostProcess();
                     break;
+                case NodeMaterialModes.Particle:
+                    this.props.globalState.previewType = PreviewType.Bubbles;
+                    this.props.globalState.nodeMaterial!.setToDefaultParticle();
+                    break;
             }
+
+            this.props.globalState.listOfCustomPreviewFiles = [];
+            (this.props.globalState.previewFile as any) = undefined;
+
+            DataStorage.WriteNumber("PreviewType", this.props.globalState.previewType);
         }
 
+        this.props.globalState.mode = value as NodeMaterialModes;
+
         this.props.globalState.onResetRequiredObservable.notifyObservers();
     }
 
@@ -314,6 +325,12 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
 
         let gridSize = DataStorage.ReadNumber("GridSize", 20);
 
+        const modeList = [
+            { label: "Material", value: NodeMaterialModes.Material },
+            { label: "Post Process", value: NodeMaterialModes.PostProcess },
+            { label: "Particle", value: NodeMaterialModes.Particle },
+        ];
+
         return (
             <div id="propertyTab">
                 <div id="header">
@@ -324,7 +341,7 @@ export class PropertyTabComponent extends React.Component<IPropertyTabComponentP
                 </div>
                 <div>
                     <LineContainerComponent title="GENERAL">
-                        <OptionsLineComponent ref={this._modeSelect} label="Mode" target={this} getSelection={(target) => this.props.globalState.mode} options={[{ label: "Material", value: NodeMaterialModes.Material }, { label: "Post Process", value: NodeMaterialModes.PostProcess }]} onSelect={(value) => this.changeMode(value)} />
+                        <OptionsLineComponent ref={this._modeSelect} label="Mode" target={this} getSelection={(target) => this.props.globalState.mode} options={modeList} onSelect={(value) => this.changeMode(value)} />
                         <TextLineComponent label="Version" value={Engine.Version}/>
                         <TextLineComponent label="Help" value="doc.babylonjs.com" underline={true} onLink={() => window.open('https://doc.babylonjs.com/how_to/node_material', '_blank')}/>
                         <ButtonLineComponent label="Reset to default" onClick={() => {

+ 24 - 10
nodeEditor/src/diagram/display/inputDisplayManager.ts

@@ -9,16 +9,30 @@ import { Color3 } from 'babylonjs/Maths/math.color';
 import { BlockTools } from '../../blockTools';
 import { StringTools } from '../../stringTools';
 
+const inputNameToAttributeValue: { [name: string] : string } = {
+    "position2d" : "position",
+    "particle_uv" : "uv",
+    "particle_color" : "color",
+    "particle_texturemask": "textureMask",
+};
+
+const inputNameToAttributeName: { [name: string] : string } = {
+    "position2d" : "postprocess",
+    "particle_uv" : "particle",
+    "particle_color" : "particle",
+    "particle_texturemask": "particle",
+};
+
 export class InputDisplayManager implements IDisplayManager {
     public getHeaderClass(block: NodeMaterialBlock) {
         let inputBlock = block as InputBlock;
 
         if (inputBlock.isConstant) {
-            return "constant"
+            return "constant";
         }
 
         if (inputBlock.visibleInInspector) {
-            return "inspector"
+            return "inspector";
         }
 
         return "";
@@ -40,10 +54,10 @@ export class InputDisplayManager implements IDisplayManager {
     }
 
     public getBackgroundColor(block: NodeMaterialBlock): string {
-        let color = "";        
+        let color = "";
         let inputBlock = block as InputBlock;
 
-        switch (inputBlock.type) {                    
+        switch (inputBlock.type) {
             case NodeMaterialBlockConnectionPointTypes.Color3:
             case NodeMaterialBlockConnectionPointTypes.Color4: {
                 if (inputBlock.value) {
@@ -64,8 +78,8 @@ export class InputDisplayManager implements IDisplayManager {
         let inputBlock = block as InputBlock;
 
         if (inputBlock.isAttribute) {
-            const attrVal = inputBlock.name === 'position2d' ? 'position' : inputBlock.name;
-            const attrName = inputBlock.name === 'position2d' ? 'postprocess' : 'mesh';
+            const attrVal = inputNameToAttributeValue[inputBlock.name] ?? inputBlock.name;
+            const attrName = inputNameToAttributeName[inputBlock.name] ?? 'mesh';
             value = attrName + "." + attrVal;
         } else if (inputBlock.isSystemValue) {
             switch (inputBlock.systemValue) {
@@ -107,17 +121,17 @@ export class InputDisplayManager implements IDisplayManager {
                     }
                     break;
                 case NodeMaterialBlockConnectionPointTypes.Vector2:
-                    let vec2Value = inputBlock.value as Vector2
+                    let vec2Value = inputBlock.value as Vector2;
                     value = `(${vec2Value.x.toFixed(2)}, ${vec2Value.y.toFixed(2)})`;
                     break;
                 case NodeMaterialBlockConnectionPointTypes.Vector3:
-                    let vec3Value = inputBlock.value as Vector3
+                    let vec3Value = inputBlock.value as Vector3;
                     value = `(${vec3Value.x.toFixed(2)}, ${vec3Value.y.toFixed(2)}, ${vec3Value.z.toFixed(2)})`;
                     break;
                 case NodeMaterialBlockConnectionPointTypes.Vector4:
-                    let vec4Value = inputBlock.value as Vector4
+                    let vec4Value = inputBlock.value as Vector4;
                     value = `(${vec4Value.x.toFixed(2)}, ${vec4Value.y.toFixed(2)}, ${vec4Value.z.toFixed(2)}, ${vec4Value.w.toFixed(2)})`;
-                    break;                        
+                    break;
             }
         }
 

+ 4 - 3
nodeEditor/src/diagram/display/textureDisplayManager.ts

@@ -5,6 +5,7 @@ import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionB
 import { ReflectionTextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/reflectionTextureBlock';
 import { TextureLineComponent } from '../../sharedComponents/textureLineComponent';
 import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
+import { ParticleTextureBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleTextureBlock';
 
 export class TextureDisplayManager implements IDisplayManager {
     private _previewCanvas: HTMLCanvasElement;
@@ -26,12 +27,12 @@ export class TextureDisplayManager implements IDisplayManager {
         return "#323232";
     }
 
-    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {       
-        const textureBlock = block as TextureBlock | ReflectionTextureBlock | RefractionBlock;
+    public updatePreviewContent(block: NodeMaterialBlock, contentArea: HTMLDivElement): void {
+        const textureBlock = block as TextureBlock | ReflectionTextureBlock | RefractionBlock | CurrentScreenBlock;
 
         if (!this._previewCanvas) {
             contentArea.classList.add("texture-block");
-            if (block instanceof TextureBlock || block instanceof CurrentScreenBlock) {
+            if (block instanceof TextureBlock || block instanceof CurrentScreenBlock || block instanceof ParticleTextureBlock) {
                 contentArea.classList.add("regular-texture-block");
             }
 

+ 1 - 0
nodeEditor/src/diagram/displayLedger.ts

@@ -23,4 +23,5 @@ DisplayLedger.RegisteredControls["ReflectionTextureBlock"] = TextureDisplayManag
 DisplayLedger.RegisteredControls["ReflectionBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["RefractionBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["CurrentScreenBlock"] = TextureDisplayManager;
+DisplayLedger.RegisteredControls["ParticleTextureBlock"] = TextureDisplayManager;
 DisplayLedger.RegisteredControls["DiscardBlock"] = DiscardDisplayManager;

+ 3 - 2
nodeEditor/src/diagram/properties/texturePropertyTabComponent.tsx

@@ -18,12 +18,13 @@ import { ReflectionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/reflectionB
 import { RefractionBlock } from 'babylonjs/Materials/Node/Blocks/PBR/refractionBlock';
 import { TextureBlock } from 'babylonjs/Materials/Node/Blocks/Dual/textureBlock';
 import { CurrentScreenBlock } from 'babylonjs/Materials/Node/Blocks/Dual/currentScreenBlock';
+import { ParticleTextureBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleTextureBlock';
 import { GeneralPropertyTabComponent, GenericPropertyTabComponent } from './genericNodePropertyComponent';
 import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 
 type ReflectionTexture = ReflectionTextureBlock | ReflectionBlock | RefractionBlock;
 
-type AnyTexture = TextureBlock | ReflectionTexture | CurrentScreenBlock;
+type AnyTexture = TextureBlock | ReflectionTexture | CurrentScreenBlock | ParticleTextureBlock;
 
 export class TexturePropertyTabComponent extends React.Component<IPropertyComponentProps, {isEmbedded: boolean, loadAsCubeTexture: boolean}> {
 
@@ -153,7 +154,7 @@ export class TexturePropertyTabComponent extends React.Component<IPropertyCompon
         url = url.replace(/\?nocache=\d+/, "");
 
         let isInReflectionMode = this.textureBlock instanceof ReflectionTextureBlock || this.textureBlock instanceof ReflectionBlock || this.textureBlock instanceof RefractionBlock;
-        let isFrozenTexture = this.textureBlock instanceof CurrentScreenBlock;
+        let isFrozenTexture = this.textureBlock instanceof CurrentScreenBlock || this.textureBlock instanceof ParticleTextureBlock;
 
         var reflectionModeOptions: {label: string, value: number}[] = [
             {

+ 1 - 0
nodeEditor/src/diagram/propertyLedger.ts

@@ -22,4 +22,5 @@ PropertyLedger.RegisteredControls["ReflectionTextureBlock"] = TexturePropertyTab
 PropertyLedger.RegisteredControls["ReflectionBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["RefractionBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["CurrentScreenBlock"] = TexturePropertyTabComponent;
+PropertyLedger.RegisteredControls["ParticleTextureBlock"] = TexturePropertyTabComponent;
 PropertyLedger.RegisteredControls["TrigonometryBlock"] = TrigonometryPropertyTabComponent;

+ 7 - 6
nodeEditor/src/globalState.ts

@@ -3,7 +3,7 @@ import { Nullable } from "babylonjs/types";
 import { Observable } from 'babylonjs/Misc/observable';
 import { LogEntry } from './components/log/logComponent';
 import { NodeMaterialBlock } from 'babylonjs/Materials/Node/nodeMaterialBlock';
-import { PreviewMeshType } from './components/preview/previewMeshType';
+import { PreviewType } from './components/preview/previewType';
 import { DataStorage } from 'babylonjs/Misc/dataStorage';
 import { Color4 } from 'babylonjs/Maths/math.color';
 import { GraphNode } from './diagram/graphNode';
@@ -44,9 +44,9 @@ export class GlobalState {
     onGetNodeFromBlock: (block: NodeMaterialBlock) => GraphNode;
     onGridSizeChanged = new Observable<void>();
     onExposePortOnFrameObservable = new Observable<GraphNode>();
-    previewMeshType: PreviewMeshType;
-    previewMeshFile: File;
-    listOfCustomPreviewMeshFiles: File[] = [];
+    previewType: PreviewType;
+    previewFile: File;
+    listOfCustomPreviewFiles: File[] = [];
     rotatePreview: boolean;
     backgroundColor: Color4;
     backFaceCulling: boolean;
@@ -66,6 +66,7 @@ export class GlobalState {
 
     /** Sets the mode */
     public set mode(m: NodeMaterialModes) {
+        DataStorage.WriteNumber("Mode", m);
         this._mode = m;
         this.onPreviewCommandActivated.notifyObservers(true);
     }
@@ -73,14 +74,14 @@ export class GlobalState {
     customSave?: {label: string, action: (data: string) => Promise<void>};
 
     public constructor() {
-        this.previewMeshType = DataStorage.ReadNumber("PreviewMeshType", PreviewMeshType.Box);
+        this.previewType = DataStorage.ReadNumber("PreviewType", PreviewType.Box);
         this.backFaceCulling = DataStorage.ReadBoolean("BackFaceCulling", true);
         this.depthPrePass = DataStorage.ReadBoolean("DepthPrePass", false);
         this.hemisphericLight = DataStorage.ReadBoolean("HemisphericLight", true);
         this.directionalLight0 = DataStorage.ReadBoolean("DirectionalLight0", false);
         this.directionalLight1 = DataStorage.ReadBoolean("DirectionalLight1", false);
         this.controlCamera = DataStorage.ReadBoolean("ControlCamera", true);
-        this._mode = NodeMaterialModes.Material;
+        this._mode = DataStorage.ReadNumber("Mode", NodeMaterialModes.Material);
 
         let r = DataStorage.ReadNumber("BackgroundColorR", 0.12549019607843137);
         let g = DataStorage.ReadNumber("BackgroundColorG", 0.09803921568627451);

+ 4 - 3
nodeEditor/src/nodeEditor.ts

@@ -6,8 +6,9 @@ import { NodeMaterial } from "babylonjs/Materials/Node/nodeMaterial"
 import { Popup } from "../src/sharedComponents/popup"
 import { SerializationTools } from './serializationTools';
 import { Observable } from 'babylonjs/Misc/observable';
-import { PreviewMeshType } from './components/preview/previewMeshType';
+import { PreviewType } from './components/preview/previewType';
 import { DataStorage } from 'babylonjs/Misc/dataStorage';
+import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 /**
  * Interface used to specify creation options for the node editor
  */
@@ -82,8 +83,8 @@ export class NodeEditor {
             };
         }
         window.addEventListener('beforeunload', () => {
-            if(DataStorage.ReadNumber("PreviewMeshType", PreviewMeshType.Box) === PreviewMeshType.Custom){
-                DataStorage.WriteNumber("PreviewMeshType", PreviewMeshType.Box)
+            if(DataStorage.ReadNumber("PreviewType", PreviewType.Box) === PreviewType.Custom){
+                DataStorage.WriteNumber("PreviewType", globalState.mode === NodeMaterialModes.Material ? PreviewType.Box : PreviewType.Bubbles);
             }
         });
     }

+ 1 - 1
nodeEditor/src/sharedComponents/color3LineComponent.tsx

@@ -148,7 +148,7 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
                         {this.props.label}
                     </div>
                     <div className="color3">
-                        <ColorPickerLineComponent value={this.state.color} disableAlpha={true} onColorChanged={color => {
+                        <ColorPickerLineComponent value={this.state.color} disableAlpha={true} globalState={this.props.globalState} onColorChanged={color => {
                                 this.onChange(color);
                             }} />  
                     </div>

+ 1 - 1
nodeEditor/src/sharedComponents/color4LineComponent.tsx

@@ -159,7 +159,7 @@ export class Color4LineComponent extends React.Component<IColor4LineComponentPro
                         {this.props.label}
                     </div>
                     <div className="color3">
-                        <ColorPickerLineComponent value={this.state.color} onColorChanged={color => {
+                        <ColorPickerLineComponent globalState={this.props.globalState} value={this.state.color} onColorChanged={color => {
                                 this.onChange(color);
                             }} />  
                     </div>

+ 9 - 2
nodeEditor/src/sharedComponents/colorPickerComponent.tsx

@@ -1,10 +1,12 @@
 import * as React from "react";
 import { Color4, Color3 } from 'babylonjs/Maths/math.color';
 import { SketchPicker } from 'react-color';
+import { GlobalState } from '../globalState';
 
 export interface IColorPickerComponentProps {
     value: Color4 | Color3;
     onColorChanged: (newOne: string) => void;
+    globalState: GlobalState;
     disableAlpha?: boolean;
 }
 
@@ -71,6 +73,11 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
         this.syncPositions();
     }
 
+    setPickerState(enabled: boolean) {
+        this.setState({ pickerEnabled: enabled });
+        this.props.globalState.blockKeyboardEvents = enabled;
+    }
+
     render() {
         var color = this.state.color;
 
@@ -78,13 +85,13 @@ export class ColorPickerLineComponent extends React.Component<IColorPickerCompon
             <div className="color-picker">
                 <div className="color-rect"  ref={this._floatHostRef} 
                     style={{background: this.state.hex}} 
-                    onClick={() => this.setState({pickerEnabled: true})}>
+                    onClick={() => this.setPickerState(true)}>
 
                 </div>
                 {
                     this.state.pickerEnabled &&
                     <>
-                        <div className="color-picker-cover" onClick={() => this.setState({pickerEnabled: false})}></div>
+                        <div className="color-picker-cover" onClick={() => this.setPickerState(false)}></div>
                         <div className="color-picker-float" ref={this._floatRef}>
                             <SketchPicker color={color} 
                                 disableAlpha={this.props.disableAlpha}

+ 18 - 8
src/Audio/sound.ts

@@ -140,6 +140,10 @@ export class Sound {
      * Back Compat
      **/
     public onended: () => any;
+    /**
+     * Gets or sets an object used to store user defined information for the sound.
+     */
+    public metadata: any = null;
 
     /**
      * Observable event when the current playing sound finishes.
@@ -937,17 +941,18 @@ export class Sound {
     }
 
     private _onRegisterAfterWorldMatrixUpdate(node: TransformNode): void {
-        if (!(<any>node).getBoundingInfo) {
-            return;
-        }
-        let mesh = node as AbstractMesh;
         if (this._positionInEmitterSpace) {
-            mesh.worldMatrixFromCache.invertToRef(TmpVectors.Matrix[0]);
+            node.worldMatrixFromCache.invertToRef(TmpVectors.Matrix[0]);
             this.setPosition(TmpVectors.Matrix[0].getTranslation());
         }
         else {
-            let boundingInfo = mesh.getBoundingInfo();
-            this.setPosition(boundingInfo.boundingSphere.centerWorld);
+            if (!(<any>node).getBoundingInfo) {
+                this.setPosition(node.absolutePosition);
+            } else {
+                let mesh = node as AbstractMesh;
+                let boundingInfo = mesh.getBoundingInfo();
+                this.setPosition(boundingInfo.boundingSphere.centerWorld);
+            }
         }
         if (Engine.audioEngine.canUseWebAudio && this._isDirectional && this.isPlaying) {
             this._updateDirection();
@@ -1036,7 +1041,8 @@ export class Sound {
             distanceModel: this.distanceModel,
             playbackRate: this._playbackRate,
             panningModel: this._panningModel,
-            soundTrackId: this.soundTrackId
+            soundTrackId: this.soundTrackId,
+            metadata: this.metadata
         };
 
         if (this.spatialSound) {
@@ -1126,6 +1132,10 @@ export class Sound {
             }
         }
 
+        if (parsedSound.metadata) {
+            newSound.metadata = parsedSound.metadata;
+        }
+
         return newSound;
     }
 }

+ 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;

+ 13 - 6
src/Materials/Node/Blocks/Dual/textureBlock.ts

@@ -30,6 +30,7 @@ export class TextureBlock extends NodeMaterialBlock {
     private _textureInfoName: string;
     private _mainUVName: string;
     private _mainUVDefineName: string;
+    private _fragmentOnly: boolean;
 
     /**
      * Gets or sets the texture associated with the node
@@ -50,9 +51,11 @@ export class TextureBlock extends NodeMaterialBlock {
      * Create a new TextureBlock
      * @param name defines the block name
      */
-    public constructor(name: string) {
+    public constructor(name: string, fragmentOnly = false) {
         super(name, NodeMaterialBlockTargets.VertexAndFragment);
 
+        this._fragmentOnly = fragmentOnly;
+
         this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, false, NodeMaterialBlockTargets.VertexAndFragment);
 
         this.registerOutput("rgba", NodeMaterialBlockConnectionPointTypes.Color4, NodeMaterialBlockTargets.Neutral);
@@ -65,7 +68,7 @@ export class TextureBlock extends NodeMaterialBlock {
         this._inputs[0].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Vector3);
         this._inputs[0].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Vector4);
 
-        this._inputs[0]._prioritizeVertex = true;
+        this._inputs[0]._prioritizeVertex = !fragmentOnly;
     }
 
     /**
@@ -173,11 +176,13 @@ export class TextureBlock extends NodeMaterialBlock {
                     uvInput.connectTo(this);
                 }
             } else {
-                let uvInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "uv");
+                const attributeName = material.mode === NodeMaterialModes.Particle ? "particle_uv" : "uv";
+
+                let uvInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === attributeName);
 
                 if (!uvInput) {
                     uvInput = new InputBlock("uv");
-                    uvInput.setAsAttribute();
+                    uvInput.setAsAttribute(attributeName);
                 }
                 uvInput.output.connectTo(this.uv);
             }
@@ -295,7 +300,7 @@ export class TextureBlock extends NodeMaterialBlock {
             return;
         }
 
-        if (this.uv.ownerBlock.target === NodeMaterialBlockTargets.Fragment) {
+        if (this.uv.ownerBlock.target === NodeMaterialBlockTargets.Fragment || this._fragmentOnly) {
             state.compilationString += `vec4 ${this._tempTextureRead} = texture2D(${this._samplerName}, ${uvInput.associatedVariableName});\r\n`;
             return;
         }
@@ -339,7 +344,7 @@ export class TextureBlock extends NodeMaterialBlock {
     protected _buildBlock(state: NodeMaterialBuildState) {
         super._buildBlock(state);
 
-        if (state.target === NodeMaterialBlockTargets.Vertex) {
+        if (state.target === NodeMaterialBlockTargets.Vertex || this._fragmentOnly) {
             this._tempTextureRead = state._getFreeVariableName("tempTextureRead");
         }
 
@@ -418,6 +423,7 @@ export class TextureBlock extends NodeMaterialBlock {
 
         serializationObject.convertToGammaSpace = this.convertToGammaSpace;
         serializationObject.convertToLinearSpace = this.convertToLinearSpace;
+        serializationObject.fragmentOnly = this._fragmentOnly;
         if (this.texture) {
             serializationObject.texture = this.texture.serialize();
         }
@@ -430,6 +436,7 @@ export class TextureBlock extends NodeMaterialBlock {
 
         this.convertToGammaSpace = serializationObject.convertToGammaSpace;
         this.convertToLinearSpace = !!serializationObject.convertToLinearSpace;
+        this._fragmentOnly = !!serializationObject.fragmentOnly;
 
         if (serializationObject.texture && !NodeMaterial.IgnoreTexturesAtLoadTime) {
             rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl;

+ 50 - 8
src/Materials/Node/Blocks/Input/inputBlock.ts

@@ -15,6 +15,23 @@ import { AnimatedInputBlockTypes } from './animatedInputBlockTypes';
 import { Observable } from '../../../../Misc/observable';
 import { MaterialHelper } from '../../../../Materials/materialHelper';
 
+const remapAttributeName: { [name: string]: string }  = {
+    "position2d": "position",
+    "particle_uv": "vUV",
+    "particle_color": "vColor",
+    "particle_texturemask": "textureMask",
+};
+
+const attributeInFragmentOnly: { [name: string]: boolean }  = {
+    "particle_uv": true,
+    "particle_color": true,
+    "particle_texturemask": true,
+};
+
+const attributeAsUniform: { [name: string]: boolean }  = {
+    "particle_texturemask": true,
+};
+
 /**
  * Block used to expose an input value
  */
@@ -96,6 +113,7 @@ export class InputBlock extends NodeMaterialBlock {
                     case "uv":
                     case "uv2":
                     case "position2d":
+                    case "particle_uv":
                         this._type = NodeMaterialBlockConnectionPointTypes.Vector2;
                         return this._type;
                     case "matricesIndices":
@@ -107,6 +125,8 @@ export class InputBlock extends NodeMaterialBlock {
                         this._type = NodeMaterialBlockConnectionPointTypes.Vector4;
                         return this._type;
                     case "color":
+                    case "particle_color":
+                    case "particle_texturemask":
                         this._type = NodeMaterialBlockConnectionPointTypes.Color4;
                         return this._type;
                 }
@@ -393,6 +413,11 @@ export class InputBlock extends NodeMaterialBlock {
         return "";
     }
 
+    /** @hidden */
+    public get _noContextSwitch(): boolean {
+        return attributeInFragmentOnly[this.name];
+    }
+
     private _emit(state: NodeMaterialBuildState, define?: string) {
         // Uniforms
         if (this.isUniform) {
@@ -444,10 +469,18 @@ export class InputBlock extends NodeMaterialBlock {
 
         // Attribute
         if (this.isAttribute) {
-            this.associatedVariableName = this.name === 'position2d' ? 'position' : this.name;
+            this.associatedVariableName = remapAttributeName[this.name] ?? this.name;
 
             if (this.target === NodeMaterialBlockTargets.Vertex && state._vertexState) { // Attribute for fragment need to be carried over by varyings
-                this._emit(state._vertexState, define);
+                if (attributeInFragmentOnly[this.name]) {
+                    if (attributeAsUniform[this.name]) {
+                        state._emitUniformFromString(this.associatedVariableName, state._getGLType(this.type), define);
+                    } else {
+                        state._emitVaryingFromString(this.associatedVariableName, state._getGLType(this.type), define);
+                    }
+                } else {
+                    this._emit(state._vertexState, define);
+                }
                 return;
             }
 
@@ -456,12 +489,21 @@ export class InputBlock extends NodeMaterialBlock {
             }
 
             state.attributes.push(this.associatedVariableName);
-            if (define) {
-                state._attributeDeclaration += this._emitDefine(define);
-            }
-            state._attributeDeclaration += `attribute ${state._getGLType(this.type)} ${this.associatedVariableName};\r\n`;
-            if (define) {
-                state._attributeDeclaration += `#endif\r\n`;
+
+            if (attributeInFragmentOnly[this.name]) {
+                if (attributeAsUniform[this.name]) {
+                    state._emitUniformFromString(this.associatedVariableName, state._getGLType(this.type), define);
+                } else {
+                    state._emitVaryingFromString(this.associatedVariableName, state._getGLType(this.type), define);
+                }
+            } else {
+                if (define) {
+                    state._attributeDeclaration += this._emitDefine(define);
+                }
+                state._attributeDeclaration += `attribute ${state._getGLType(this.type)} ${this.associatedVariableName};\r\n`;
+                if (define) {
+                    state._attributeDeclaration += `#endif\r\n`;
+                }
             }
         }
     }

+ 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;
     }
 }
 

+ 4 - 0
src/Materials/Node/Blocks/Particle/index.ts

@@ -0,0 +1,4 @@
+
+export * from "./particleTextureBlock";
+export * from "./particleRampGradientBlock";
+export * from "./particleBlendMultiplyBlock";

+ 95 - 0
src/Materials/Node/Blocks/Particle/particleBlendMultiplyBlock.ts

@@ -0,0 +1,95 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { _TypeStore } from '../../../../Misc/typeStore';
+
+/**
+  * Block used for the particle blend multiply section
+  */
+export class ParticleBlendMultiplyBlock extends NodeMaterialBlock {
+
+    /**
+     * Create a new ParticleBlendMultiplyBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color4, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("alphaTexture", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+        this.registerInput("alphaColor", NodeMaterialBlockConnectionPointTypes.Float, false, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("blendColor", NodeMaterialBlockConnectionPointTypes.Color4, NodeMaterialBlockTargets.Fragment);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ParticleBlendMultiplyBlock";
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the alphaTexture input component
+     */
+    public get alphaTexture(): NodeMaterialConnectionPoint {
+        return this._inputs[1];
+    }
+
+    /**
+     * Gets the alphaColor input component
+     */
+    public get alphaColor(): NodeMaterialConnectionPoint {
+        return this._inputs[2];
+    }
+
+    /**
+     * Gets the blendColor output component
+     */
+    public get blendColor(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Initialize the block and prepare the context for build
+     * @param state defines the state that will be used for the build
+     */
+    public initialize(state: NodeMaterialBuildState) {
+        state._excludeVariableName("sourceAlpha");
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        if (state.target === NodeMaterialBlockTargets.Vertex) {
+            return;
+        }
+
+        state.compilationString += `
+            #ifdef BLENDMULTIPLYMODE
+                ${this._declareOutput(this.blendColor, state)};
+                float sourceAlpha = ${this.alphaColor.associatedVariableName} * ${this.alphaTexture.associatedVariableName};
+                ${this.blendColor.associatedVariableName}.rgb = ${this.color.associatedVariableName}.rgb * sourceAlpha + vec3(1.0) * (1.0 - sourceAlpha);
+                ${this.blendColor.associatedVariableName}.a = ${this.color.associatedVariableName}.a;
+            #else
+                ${this._declareOutput(this.blendColor, state)} = ${this.color.associatedVariableName};
+            #endif
+        `;
+
+        return this;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.ParticleBlendMultiplyBlock"] = ParticleBlendMultiplyBlock;

+ 97 - 0
src/Materials/Node/Blocks/Particle/particleRampGradientBlock.ts

@@ -0,0 +1,97 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { _TypeStore } from '../../../../Misc/typeStore';
+
+/**
+ * Block used for the particle ramp gradient section
+ */
+export class ParticleRampGradientBlock extends NodeMaterialBlock {
+
+    /**
+     * Create a new ParticleRampGradientBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color4, false, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("rampColor", NodeMaterialBlockConnectionPointTypes.Color4, NodeMaterialBlockTargets.Fragment);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ParticleRampGradientBlock";
+    }
+
+    /**
+     * Gets the color input component
+     */
+    public get color(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the rampColor output component
+     */
+    public get rampColor(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Initialize the block and prepare the context for build
+     * @param state defines the state that will be used for the build
+     */
+    public initialize(state: NodeMaterialBuildState) {
+        state._excludeVariableName("remapRanges");
+        state._excludeVariableName("rampSampler");
+        state._excludeVariableName("baseColor");
+        state._excludeVariableName("alpha");
+        state._excludeVariableName("remappedColorIndex");
+        state._excludeVariableName("rampColor");
+        state._excludeVariableName("finalAlpha");
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        if (state.target === NodeMaterialBlockTargets.Vertex) {
+            return;
+        }
+
+        state._emit2DSampler("rampSampler");
+        state._emitVaryingFromString("remapRanges", "vec4", "RAMPGRADIENT");
+
+        state.compilationString += `
+            #ifdef RAMPGRADIENT
+                vec4 baseColor = ${this.color.associatedVariableName};
+                float alpha = ${this.color.associatedVariableName}.a;
+
+                float remappedColorIndex = clamp((alpha - remapRanges.x) / remapRanges.y, 0.0, 1.0);
+
+                vec4 rampColor = texture2D(rampSampler, vec2(1.0 - remappedColorIndex, 0.));
+                baseColor.rgb *= rampColor.rgb;
+
+                // Remapped alpha
+                float finalAlpha = baseColor.a;
+                baseColor.a = clamp((alpha * rampColor.a - remapRanges.z) / remapRanges.w, 0.0, 1.0);
+
+                ${this._declareOutput(this.rampColor, state)} = baseColor;
+            #else
+                ${this._declareOutput(this.rampColor, state)} = ${this.color.associatedVariableName};
+            #endif
+        `;
+
+        return this;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.ParticleRampGradientBlock"] = ParticleRampGradientBlock;

+ 223 - 0
src/Materials/Node/Blocks/Particle/particleTextureBlock.ts

@@ -0,0 +1,223 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { AbstractMesh } from '../../../../Meshes/abstractMesh';
+import { NodeMaterialDefines } from '../../nodeMaterial';
+import { InputBlock } from '../Input/inputBlock';
+import { BaseTexture } from '../../../Textures/baseTexture';
+import { Nullable } from '../../../../types';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { Texture } from '../../../Textures/texture';
+import { Scene } from '../../../../scene';
+
+declare type NodeMaterial = import("../../nodeMaterial").NodeMaterial;
+
+/**
+ * Base block used for the particle texture
+ */
+export class ParticleTextureBlock extends NodeMaterialBlock {
+
+    private _samplerName = "diffuseSampler";
+    private _linearDefineName: string;
+    private _gammaDefineName: string;
+    private _tempTextureRead: string;
+
+    /**
+     * Gets or sets the texture associated with the node
+     */
+    public texture: Nullable<BaseTexture>;
+
+    /**
+     * Gets or sets a boolean indicating if content needs to be converted to gamma space
+     */
+    public convertToGammaSpace = false;
+
+    /**
+     * Gets or sets a boolean indicating if content needs to be converted to linear space
+     */
+    public convertToLinearSpace = false;
+
+    /**
+     * Create a new ParticleTextureBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this._isUnique = true;
+
+        this.registerInput("uv", NodeMaterialBlockConnectionPointTypes.Vector2, false, NodeMaterialBlockTargets.VertexAndFragment);
+
+        this.registerOutput("rgba", NodeMaterialBlockConnectionPointTypes.Color4, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("rgb", NodeMaterialBlockConnectionPointTypes.Color3, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("r", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("g", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("b", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+        this.registerOutput("a", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Neutral);
+
+        this._inputs[0].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Vector3);
+        this._inputs[0].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Vector4);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ParticleTextureBlock";
+    }
+
+    /**
+     * Gets the uv input component
+     */
+    public get uv(): NodeMaterialConnectionPoint {
+        return this._inputs[0];
+    }
+
+    /**
+     * Gets the rgba output component
+     */
+    public get rgba(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Gets the rgb output component
+     */
+    public get rgb(): NodeMaterialConnectionPoint {
+        return this._outputs[1];
+    }
+
+    /**
+     * Gets the r output component
+     */
+    public get r(): NodeMaterialConnectionPoint {
+        return this._outputs[2];
+    }
+
+    /**
+     * Gets the g output component
+     */
+    public get g(): NodeMaterialConnectionPoint {
+        return this._outputs[3];
+    }
+
+    /**
+     * Gets the b output component
+     */
+    public get b(): NodeMaterialConnectionPoint {
+        return this._outputs[4];
+    }
+
+    /**
+     * Gets the a output component
+     */
+    public get a(): NodeMaterialConnectionPoint {
+        return this._outputs[5];
+    }
+
+    /**
+     * Initialize the block and prepare the context for build
+     * @param state defines the state that will be used for the build
+     */
+    public initialize(state: NodeMaterialBuildState) {
+        state._excludeVariableName("diffuseSampler");
+    }
+
+    public autoConfigure(material: NodeMaterial) {
+        if (!this.uv.isConnected) {
+            let uvInput = material.getInputBlockByPredicate((b) => b.isAttribute && b.name === "particle_uv");
+
+            if (!uvInput) {
+                uvInput = new InputBlock("uv");
+                uvInput.setAsAttribute("particle_uv");
+            }
+            uvInput.output.connectTo(this.uv);
+        }
+    }
+
+    public prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines) {
+        defines.setValue(this._linearDefineName, this.convertToGammaSpace, true);
+        defines.setValue(this._gammaDefineName, this.convertToLinearSpace, true);
+    }
+
+    public isReady() {
+        if (this.texture && !this.texture.isReadyOrNotBlocking()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private _writeOutput(state: NodeMaterialBuildState, output: NodeMaterialConnectionPoint, swizzle: string) {
+        state.compilationString += `${this._declareOutput(output, state)} = ${this._tempTextureRead}.${swizzle};\r\n`;
+
+        state.compilationString += `#ifdef ${this._linearDefineName}\r\n`;
+        state.compilationString += `${output.associatedVariableName} = toGammaSpace(${output.associatedVariableName});\r\n`;
+        state.compilationString += `#endif\r\n`;
+
+        state.compilationString += `#ifdef ${this._gammaDefineName}\r\n`;
+        state.compilationString += `${output.associatedVariableName} = toLinearSpace(${output.associatedVariableName});\r\n`;
+        state.compilationString += `#endif\r\n`;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        if (state.target === NodeMaterialBlockTargets.Vertex) {
+            return;
+        }
+
+        this._tempTextureRead = state._getFreeVariableName("tempTextureRead");
+
+        state._emit2DSampler(this._samplerName);
+
+        state.sharedData.blockingBlocks.push(this);
+        state.sharedData.textureBlocks.push(this);
+        state.sharedData.blocksWithDefines.push(this);
+
+        this._linearDefineName = state._getFreeDefineName("ISLINEAR");
+        this._gammaDefineName = state._getFreeDefineName("ISGAMMA");
+
+        let comments = `//${this.name}`;
+        state._emitFunctionFromInclude("helperFunctions", comments);
+
+        state.compilationString += `vec4 ${this._tempTextureRead} = texture2D(${this._samplerName}, ${this.uv.associatedVariableName});\r\n`;
+
+        for (var output of this._outputs) {
+            if (output.hasEndpoints) {
+                this._writeOutput(state, output, output.name);
+            }
+        }
+
+        return this;
+    }
+
+    public serialize(): any {
+        let serializationObject = super.serialize();
+
+        serializationObject.convertToGammaSpace = this.convertToGammaSpace;
+        serializationObject.convertToLinearSpace = this.convertToLinearSpace;
+        if (this.texture) {
+            serializationObject.texture = this.texture.serialize();
+        }
+
+        return serializationObject;
+    }
+
+    public _deserialize(serializationObject: any, scene: Scene, rootUrl: string) {
+        super._deserialize(serializationObject, scene, rootUrl);
+
+        this.convertToGammaSpace = serializationObject.convertToGammaSpace;
+        this.convertToLinearSpace = !!serializationObject.convertToLinearSpace;
+
+        if (serializationObject.texture) {
+            rootUrl = serializationObject.texture.url.indexOf("data:") === 0 ? "" : rootUrl;
+            this.texture = Texture.Parse(serializationObject.texture, scene, rootUrl) as Texture;
+        }
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.ParticleTextureBlock"] = ParticleTextureBlock;

+ 1 - 0
src/Materials/Node/Blocks/index.ts

@@ -46,3 +46,4 @@ export * from "./reflectBlock";
 export * from "./refractBlock";
 export * from "./desaturateBlock";
 export * from "./PBR/index";
+export * from "./Particle/index";

+ 4 - 1
src/Materials/Node/Blocks/lerpBlock.ts

@@ -17,11 +17,14 @@ export class LerpBlock extends NodeMaterialBlock {
 
         this.registerInput("left", NodeMaterialBlockConnectionPointTypes.AutoDetect);
         this.registerInput("right", NodeMaterialBlockConnectionPointTypes.AutoDetect);
-        this.registerInput("gradient", NodeMaterialBlockConnectionPointTypes.Float);
+        this.registerInput("gradient", NodeMaterialBlockConnectionPointTypes.AutoDetect);
         this.registerOutput("output", NodeMaterialBlockConnectionPointTypes.BasedOnInput);
 
         this._outputs[0]._typeConnectionSource = this._inputs[0];
         this._linkConnectionTypes(0, 1);
+        this._linkConnectionTypes(1, 2);
+
+        this._inputs[2].acceptedConnectionPointTypes.push(NodeMaterialBlockConnectionPointTypes.Float);
     }
 
     /**

+ 2 - 0
src/Materials/Node/Enums/nodeMaterialModes.ts

@@ -6,4 +6,6 @@ export enum NodeMaterialModes {
     Material = 0,
     /** For post process */
     PostProcess = 1,
+    /** For particle system */
+    Particle = 2,
 }

+ 160 - 7
src/Materials/Node/nodeMaterial.ts

@@ -29,6 +29,9 @@ import { TextureBlock } from './Blocks/Dual/textureBlock';
 import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
 import { RefractionBlock } from './Blocks/PBR/refractionBlock';
 import { CurrentScreenBlock } from './Blocks/Dual/currentScreenBlock';
+import { ParticleTextureBlock } from './Blocks/Particle/particleTextureBlock';
+import { ParticleRampGradientBlock } from './Blocks/Particle/particleRampGradientBlock';
+import { ParticleBlendMultiplyBlock } from './Blocks/Particle/particleBlendMultiplyBlock';
 import { EffectFallbacks } from '../effectFallbacks';
 import { WebRequest } from '../../Misc/webRequest';
 import { Effect } from '../effect';
@@ -40,6 +43,9 @@ import { RemapBlock } from './Blocks/remapBlock';
 import { MultiplyBlock } from './Blocks/multiplyBlock';
 import { NodeMaterialModes } from './Enums/nodeMaterialModes';
 import { Texture } from '../Textures/texture';
+import { IParticleSystem } from '../../Particles/IParticleSystem';
+import { BaseParticleSystem } from '../../Particles/baseParticleSystem';
+import { ColorSplitterBlock } from './Blocks/colorSplitterBlock';
 
 const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
 
@@ -597,7 +603,9 @@ export class NodeMaterial extends PushMaterial {
         this._buildWasSuccessful = false;
         var engine = this.getScene().getEngine();
 
-        if (this._vertexOutputNodes.length === 0) {
+        const allowEmptyVertexProgram = this._mode === NodeMaterialModes.Particle;
+
+        if (this._vertexOutputNodes.length === 0 && !allowEmptyVertexProgram) {
             throw "You must define at least one vertexOutputNode";
         }
 
@@ -621,6 +629,7 @@ export class NodeMaterial extends PushMaterial {
         this._sharedData.emitComments = this._options.emitComments;
         this._sharedData.verbose = verbose;
         this._sharedData.scene = this.getScene();
+        this._sharedData.allowEmptyVertexProgram = allowEmptyVertexProgram;
 
         // Initialize blocks
         let vertexNodes: NodeMaterialBlock[] = [];
@@ -749,8 +758,7 @@ export class NodeMaterial extends PushMaterial {
 
         this._processDefines(dummyMesh, defines);
 
-        Effect.ShadersStore[tempName + "VertexShader"] = this._vertexCompilationState._builtCompilationString;
-        Effect.ShadersStore[tempName + "PixelShader"] = this._fragmentCompilationState._builtCompilationString;
+        Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
 
         const postProcess = new PostProcess(
             this.name + "PostProcess", tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers,
@@ -772,8 +780,7 @@ export class NodeMaterial extends PushMaterial {
             const result = this._processDefines(dummyMesh, defines);
 
             if (result) {
-                Effect.ShadersStore[tempName + "VertexShader"] = this._vertexCompilationState._builtCompilationString;
-                Effect.ShadersStore[tempName + "PixelShader"] = this._fragmentCompilationState._builtCompilationString;
+                Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString, this._vertexCompilationState._builtCompilationString);
 
                 postProcess.updateEffect(defines.toString(), this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, { maxSimultaneousLights: this.maxSimultaneousLights }, undefined, undefined, tempName, tempName);
             }
@@ -807,6 +814,110 @@ export class NodeMaterial extends PushMaterial {
         return postProcess;
     }
 
+    private _createEffectForParticles(particleSystem: IParticleSystem, blendMode: number, onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, effect?: Effect, defines?: NodeMaterialDefines, dummyMesh?: Nullable<AbstractMesh>) {
+        let tempName = this.name + this._buildId + "_" + blendMode;
+
+        if (!defines) {
+            defines = new NodeMaterialDefines();
+        }
+
+        if (!dummyMesh) {
+            dummyMesh = this.getScene().getMeshByName(this.name + "Particle");
+            if (!dummyMesh) {
+                dummyMesh = new AbstractMesh(this.name + "Particle", this.getScene());
+            }
+        }
+
+        let buildId = this._buildId;
+
+        let particleSystemDefines: Array<string> = [];
+        let particleSystemDefinesJoined = "";
+
+        if (!effect) {
+            const result = this._processDefines(dummyMesh, defines);
+
+            Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString);
+
+            particleSystem.fillDefines(particleSystemDefines, blendMode);
+
+            particleSystemDefinesJoined = particleSystemDefines.join("\n");
+
+            effect = this.getScene().getEngine().createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines.toString() + "\n" + particleSystemDefinesJoined, result?.fallbacks, onCompiled, onError, particleSystem);
+
+            particleSystem.setCustomEffect(effect, blendMode);
+        }
+
+        effect.onBindObservable.add((effect) => {
+            if (buildId !== this._buildId) {
+                delete Effect.ShadersStore[tempName + "PixelShader"];
+
+                tempName = this.name + this._buildId + "_" + blendMode;
+
+                defines!.markAsUnprocessed();
+
+                buildId = this._buildId;
+            }
+
+            particleSystemDefines.length = 0;
+
+            particleSystem.fillDefines(particleSystemDefines, blendMode);
+
+            const particleSystemDefinesJoinedCurrent = particleSystemDefines.join("\n");
+
+            if (particleSystemDefinesJoinedCurrent !== particleSystemDefinesJoined) {
+                defines!.markAsUnprocessed();
+                particleSystemDefinesJoined = particleSystemDefinesJoinedCurrent;
+            }
+
+            const result = this._processDefines(dummyMesh!, defines!);
+
+            if (result) {
+                Effect.RegisterShader(tempName, this._fragmentCompilationState._builtCompilationString);
+
+                effect = this.getScene().getEngine().createEffectForParticles(tempName, this._fragmentCompilationState.uniforms, this._fragmentCompilationState.samplers, defines!.toString() + "\n" + particleSystemDefinesJoined, result?.fallbacks, onCompiled, onError, particleSystem);
+                particleSystem.setCustomEffect(effect, blendMode);
+                this._createEffectForParticles(particleSystem, blendMode, onCompiled, onError, effect, defines, dummyMesh); // add the effect.onBindObservable observer
+                return;
+            }
+
+            // Animated blocks
+            if (this._sharedData.animatedInputs) {
+                const scene = this.getScene();
+
+                let frameId = scene.getFrameId();
+
+                if (this._animationFrame !== frameId) {
+                    for (var input of this._sharedData.animatedInputs) {
+                        input.animate(scene);
+                    }
+
+                    this._animationFrame = frameId;
+                }
+            }
+
+            // Bindable blocks
+            for (var block of this._sharedData.bindableBlocks) {
+                block.bind(effect, this);
+            }
+
+            // Connection points
+            for (var inputBlock of this._sharedData.inputBlocks) {
+                inputBlock._transmit(effect, this.getScene());
+            }
+        });
+    }
+
+    /**
+     * Create the effect to be used as the custom effect for a particle system
+     * @param particleSystem Particle system to create the effect for
+     * @param onCompiled defines a function to call when the effect creation is successful
+     * @param onError defines a function to call when the effect creation has failed
+     */
+    public createEffectForParticles(particleSystem: IParticleSystem, onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void) {
+        this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_ONEONE, onCompiled, onError);
+        this._createEffectForParticles(particleSystem, BaseParticleSystem.BLENDMODE_MULTIPLY, onCompiled, onError);
+    }
+
     private _processDefines(mesh: AbstractMesh, defines: NodeMaterialDefines, useInstances = false): Nullable<{
         lightDisposed: boolean,
         uniformBuffers: string[],
@@ -1080,7 +1191,7 @@ export class NodeMaterial extends PushMaterial {
      * Gets the list of texture blocks
      * @returns an array of texture blocks
      */
-    public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock | RefractionBlock | CurrentScreenBlock)[] {
+    public getTextureBlocks(): (TextureBlock | ReflectionTextureBaseBlock | RefractionBlock | CurrentScreenBlock | ParticleTextureBlock)[] {
         if (!this._sharedData) {
             return [];
         }
@@ -1268,6 +1379,48 @@ export class NodeMaterial extends PushMaterial {
     }
 
     /**
+     * Clear the current material and set it to a default state for particle
+     */
+    public setToDefaultParticle() {
+        this.clear();
+
+        this.editorData = null;
+
+        // Pixel
+        const uv = new InputBlock("uv");
+        uv.setAsAttribute("particle_uv");
+
+        const texture = new ParticleTextureBlock("ParticleTexture");
+        uv.connectTo(texture);
+
+        const color = new InputBlock("Color");
+        color.setAsAttribute("particle_color");
+
+        const multiply = new MultiplyBlock("Texture * Color");
+        texture.connectTo(multiply);
+        color.connectTo(multiply);
+
+        const rampGradient = new ParticleRampGradientBlock("ParticleRampGradient");
+        multiply.connectTo(rampGradient);
+
+        const cSplitter = new ColorSplitterBlock("ColorSplitter");
+        color.connectTo(cSplitter);
+
+        const blendMultiply = new ParticleBlendMultiplyBlock("ParticleBlendMultiply");
+        rampGradient.connectTo(blendMultiply);
+        texture.connectTo(blendMultiply, { "output": "a" });
+        cSplitter.connectTo(blendMultiply, { "output": "a" });
+
+        const fragmentOutput = new FragmentOutputBlock("FragmentOutput");
+        blendMultiply.connectTo(fragmentOutput);
+
+        // Add to nodes
+        this.addOutputNode(fragmentOutput);
+
+        this._mode = NodeMaterialModes.Particle;
+    }
+
+    /**
      * Loads the current Node Material from a url pointing to a file save by the Node Material Editor
      * @param url defines the url to load from
      * @returns a promise that will fullfil when the material is fully loaded
@@ -1587,7 +1740,7 @@ export class NodeMaterial extends PushMaterial {
                 }
             });
 
-            request.open("GET", this.SnippetUrl + "/" + snippetId.replace("#", "/"));
+            request.open("GET", this.SnippetUrl + "/" + snippetId.replace(/#/g, "/"));
             request.send();
         });
     }

+ 1 - 1
src/Materials/Node/nodeMaterialBlock.ts

@@ -425,7 +425,7 @@ export class NodeMaterialBlock {
             (this.target !== NodeMaterialBlockTargets.VertexAndFragment && otherBlockWasGeneratedInVertexShader)
             )) { // context switch! We need a varying
             if ((!block.isInput && state.target !== block._buildTarget) // block was already emitted by vertex shader
-                || (block.isInput && (block as InputBlock).isAttribute) // block is an attribute
+                || (block.isInput && (block as InputBlock).isAttribute && !(block as InputBlock)._noContextSwitch) // block is an attribute
             ) {
                 let connectedPoint = input.connectedPoint!;
                 if (state._vertexState._emitVaryingFromString("v_" + connectedPoint.associatedVariableName, state._getGLType(connectedPoint.type))) {

+ 8 - 2
src/Materials/Node/nodeMaterialBuildStateSharedData.ts

@@ -5,6 +5,7 @@ import { TextureBlock } from './Blocks/Dual/textureBlock';
 import { ReflectionTextureBaseBlock } from './Blocks/Dual/reflectionTextureBaseBlock';
 import { RefractionBlock } from './Blocks/PBR/refractionBlock';
 import { CurrentScreenBlock } from './Blocks/Dual/currentScreenBlock';
+import { ParticleTextureBlock } from './Blocks/Particle/particleTextureBlock';
 import { Scene } from '../../scene';
 
 /**
@@ -34,7 +35,7 @@ export class NodeMaterialBuildStateSharedData {
     /**
      * Input blocks
      */
-    public textureBlocks = new Array<TextureBlock | ReflectionTextureBaseBlock | RefractionBlock | CurrentScreenBlock>();
+    public textureBlocks = new Array<TextureBlock | ReflectionTextureBaseBlock | RefractionBlock | CurrentScreenBlock | ParticleTextureBlock>();
 
     /**
      * Bindable blocks (Blocks that need to set data to the effect)
@@ -110,6 +111,11 @@ export class NodeMaterialBuildStateSharedData {
         notConnectedNonOptionalInputs: new Array<NodeMaterialConnectionPoint>()
     };
 
+    /**
+     * Is vertex program allowed to be empty?
+     */
+    public allowEmptyVertexProgram: boolean = false;
+
     /** Creates a new shared data */
     public constructor() {
         // Exclude usual attributes from free variable names
@@ -153,7 +159,7 @@ export class NodeMaterialBuildStateSharedData {
     public emitErrors() {
         let errorMessage = "";
 
-        if (!this.checks.emitVertex) {
+        if (!this.checks.emitVertex && !this.allowEmptyVertexProgram) {
             errorMessage += "NodeMaterial does not have a vertex output. You need to at least add a block that generates a glPosition value.\r\n";
         }
         if (!this.checks.emitFragment) {

+ 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,
+            );
+        }
     }
 
     /**

+ 40 - 0
src/Particles/IParticleSystem.ts

@@ -7,6 +7,8 @@ import { Texture } from "../Materials/Textures/texture";
 import { BoxParticleEmitter, IParticleEmitterType, PointParticleEmitter, HemisphericParticleEmitter, SphereParticleEmitter, SphereDirectedParticleEmitter, CylinderParticleEmitter, ConeParticleEmitter } from "../Particles/EmitterTypes/index";
 import { Scene } from "../scene";
 import { ColorGradient, FactorGradient, Color3Gradient } from "../Misc/gradients";
+import { Effect } from "../Materials/effect";
+import { Observable } from "../Misc/observable";
 
 declare type Animation = import("../Animations/animation").Animation;
 
@@ -283,6 +285,10 @@ export interface IParticleSystem {
      */
     dispose(disposeTexture?: boolean): void;
     /**
+    * An event triggered when the system is disposed
+    */
+    onDisposeObservable: Observable<IParticleSystem>;
+    /**
      * Clones the particle system.
      * @param name The name of the cloned object
      * @param newEmitter The new emitter to use
@@ -335,6 +341,40 @@ export interface IParticleSystem {
      * @returns a string containing the class name
      */
     getClassName(): string;
+    /**
+     * Gets the custom effect used to render the particles
+     * @param blendMode Blend mode for which the effect should be retrieved
+     * @returns The effect
+     */
+    getCustomEffect(blendMode: number): Nullable<Effect>;
+    /**
+     * Sets the custom effect used to render the particles
+     * @param effect The effect to set
+     * @param blendMode Blend mode for which the effect should be set
+     */
+    setCustomEffect(effect: Nullable<Effect>, blendMode: number): void;
+
+    /**
+     * Fill the defines array according to the current settings of the particle system
+     * @param defines Array to be updated
+     * @param blendMode blend mode to take into account when updating the array
+     */
+    fillDefines(defines: Array<string>, blendMode: number): void;
+    /**
+     * Fill the uniforms, attributes and samplers arrays according to the current settings of the particle system
+     * @param uniforms Uniforms array to fill
+     * @param attributes Attributes array to fill
+     * @param samplers Samplers array to fill
+     */
+    fillUniformsAttributesAndSamplerNames(uniforms: Array<string>, attributes: Array<string>, samplers: Array<string>): void;
+    /**
+     * Observable that will be called just before the particles are drawn
+     */
+    onBeforeDrawParticlesObservable: Observable<Nullable<Effect>>;
+    /**
+     * Gets the name of the particle vertex shader
+     */
+    vertexShaderName: string;
 
     /**
      * Adds a new color gradient

+ 135 - 43
src/Particles/gpuParticleSystem.ts

@@ -74,6 +74,7 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
 
     private _randomTextureSize: number;
     private _actualFrame = 0;
+    private _customEffect: { [blendMode: number] : Nullable<Effect> };
 
     private readonly _rawTextureWidth = 256;
 
@@ -90,7 +91,7 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
     /**
     * An event triggered when the system is disposed.
     */
-    public onDisposeObservable = new Observable<GPUParticleSystem>();
+    public onDisposeObservable = new Observable<IParticleSystem>();
 
     /**
      * Gets the maximum number of particles active at the same time.
@@ -135,7 +136,7 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
             return false;
         }
 
-        if (!this.emitter || !this._updateEffect.isReady() || !this._imageProcessingConfiguration.isReady() || !this._renderEffect.isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
+        if (!this.emitter || !this._updateEffect.isReady() || !this._imageProcessingConfiguration.isReady() || !this._getEffect().isReady() || !this.particleTexture || !this.particleTexture.isReady()) {
             return false;
         }
 
@@ -223,6 +224,45 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
         return "GPUParticleSystem";
     }
 
+    /**
+     * Gets the custom effect used to render the particles
+     * @param blendMode Blend mode for which the effect should be retrieved
+     * @returns The effect
+     */
+    public getCustomEffect(blendMode: number = 0): Nullable<Effect> {
+        return this._customEffect[blendMode] ?? this._customEffect[0];
+    }
+
+    /**
+     * Sets the custom effect used to render the particles
+     * @param effect The effect to set
+     * @param blendMode Blend mode for which the effect should be set
+     */
+    public setCustomEffect(effect: Nullable<Effect>, blendMode: number = 0) {
+        this._customEffect[blendMode] = effect;
+    }
+
+    /** @hidden */
+    protected _onBeforeDrawParticlesObservable: Nullable<Observable<Nullable<Effect>>> = null;
+
+    /**
+     * Observable that will be called just before the particles are drawn
+     */
+    public get onBeforeDrawParticlesObservable(): Observable<Nullable<Effect>> {
+        if (!this._onBeforeDrawParticlesObservable) {
+            this._onBeforeDrawParticlesObservable = new Observable<Nullable<Effect>>();
+        }
+
+        return this._onBeforeDrawParticlesObservable;
+    }
+
+    /**
+     * Gets the name of the particle vertex shader
+     */
+    public get vertexShaderName(): string {
+        return "gpuRenderParticles";
+    }
+
     private _colorGradientsTexture: RawTexture;
 
     protected _removeGradientAndTexture(gradient: number, gradients: Nullable<IValueGradient[]>, texture: RawTexture): BaseParticleSystem {
@@ -660,16 +700,19 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
      * @param options The options used to create the system
      * @param scene The scene the particle system belongs to
      * @param isAnimationSheetEnabled Must be true if using a spritesheet to animate the particles texture
+     * @param customEffect a custom effect used to change the way particles are rendered by default
      */
     constructor(name: string, options: Partial<{
         capacity: number,
         randomTextureSize: number
-    }>, scene: Scene, isAnimationSheetEnabled: boolean = false) {
+    }>, scene: Scene, isAnimationSheetEnabled: boolean = false, customEffect: Nullable<Effect> = null) {
         super(name);
         this._scene = scene || EngineStore.LastCreatedScene;
 
         this.uniqueId = this._scene.getUniqueId();
 
+        this._customEffect = { 0: customEffect };
+
         // Setup the default processing configuration to the scene.
         this._attachImageProcessingConfiguration(null);
 
@@ -862,7 +905,7 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
         renderVertexBuffers["offset"] = spriteSource.createVertexBuffer("offset", 0, 2);
         renderVertexBuffers["uv"] = spriteSource.createVertexBuffer("uv", 2, 2);
 
-        let vao = this._engine.recordVertexArrayObject(renderVertexBuffers, null, this._renderEffect);
+        let vao = this._engine.recordVertexArrayObject(renderVertexBuffers, null, this._getEffect());
         this._engine.bindArrayBuffer(null);
 
         return vao;
@@ -1105,45 +1148,52 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
         this._updateEffect = new Effect("gpuUpdateParticles", this._updateEffectOptions, this._scene.getEngine());
     }
 
-    /** @hidden */
-    public _recreateRenderEffect() {
-        let defines = "";
+    private _getEffect(): Effect {
+        return this.getCustomEffect() ?? this._renderEffect;
+    }
+
+    /**
+     * Fill the defines array according to the current settings of the particle system
+     * @param defines Array to be updated
+     * @param blendMode blend mode to take into account when updating the array
+     */
+    public fillDefines(defines: Array<string>, blendMode: number = 0) {
         if (this._scene.clipPlane) {
-            defines = "\n#define CLIPPLANE";
+            defines.push("#define CLIPPLANE");
         }
         if (this._scene.clipPlane2) {
-            defines = "\n#define CLIPPLANE2";
+            defines.push("#define CLIPPLANE2");
         }
         if (this._scene.clipPlane3) {
-            defines = "\n#define CLIPPLANE3";
+            defines.push("#define CLIPPLANE3");
         }
         if (this._scene.clipPlane4) {
-            defines = "\n#define CLIPPLANE4";
+            defines.push("#define CLIPPLANE4");
         }
         if (this._scene.clipPlane5) {
-            defines = "\n#define CLIPPLANE5";
+            defines.push("#define CLIPPLANE5");
         }
         if (this._scene.clipPlane6) {
-            defines = "\n#define CLIPPLANE6";
+            defines.push("#define CLIPPLANE6");
         }
 
         if (this.blendMode === ParticleSystem.BLENDMODE_MULTIPLY) {
-            defines = "\n#define BLENDMULTIPLYMODE";
+            defines.push("#define BLENDMULTIPLYMODE");
         }
 
         if (this.isLocal) {
-            defines += "\n#define LOCAL";
+            defines.push("#define LOCAL");
         }
 
         if (this._isBillboardBased) {
-            defines += "\n#define BILLBOARD";
+            defines.push("#define BILLBOARD");
 
             switch (this.billboardMode) {
                 case ParticleSystem.BILLBOARDMODE_Y:
-                    defines += "\n#define BILLBOARDY";
+                    defines.push("#define BILLBOARDY");
                     break;
                 case ParticleSystem.BILLBOARDMODE_STRETCHED:
-                    defines += "\n#define BILLBOARDSTRETCHED";
+                    defines.push("#define BILLBOARDSTRETCHED");
                     break;
                 case ParticleSystem.BILLBOARDMODE_ALL:
                 default:
@@ -1152,34 +1202,68 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
         }
 
         if (this._colorGradientsTexture) {
-            defines += "\n#define COLORGRADIENTS";
+            defines.push("#define COLORGRADIENTS");
         }
 
         if (this.isAnimationSheetEnabled) {
-            defines += "\n#define ANIMATESHEET";
+            defines.push("#define ANIMATESHEET");
         }
 
         if (this._imageProcessingConfiguration) {
             this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
-            defines += "\n" + this._imageProcessingConfigurationDefines.toString();
+            defines.push("" + this._imageProcessingConfigurationDefines.toString());
         }
+    }
 
-        if (this._renderEffect && this._renderEffect.defines === defines) {
-            return;
-        }
+    /**
+     * Fill the uniforms, attributes and samplers arrays according to the current settings of the particle system
+     * @param uniforms Uniforms array to fill
+     * @param attributes Attributes array to fill
+     * @param samplers Samplers array to fill
+     */
+    public fillUniformsAttributesAndSamplerNames(uniforms: Array<string>, attributes: Array<string>, samplers: Array<string>) {
+        attributes.push("position", "age", "life", "size", "color", "offset", "uv", "direction", "initialDirection", "angle", "cellIndex");
+
+        uniforms.push("emitterWM", "worldOffset", "view", "projection", "colorDead", "invView", "vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "vClipPlane5", "vClipPlane6", "sheetInfos", "translationPivot", "eyePosition");
 
-        var uniforms = ["emitterWM", "worldOffset", "view", "projection", "colorDead", "invView", "vClipPlane", "vClipPlane2", "vClipPlane3", "vClipPlane4", "vClipPlane5", "vClipPlane6", "sheetInfos", "translationPivot", "eyePosition"];
-        var samplers = ["textureSampler", "colorGradientSampler"];
+        samplers.push("diffuseSampler", "colorGradientSampler");
 
-        if (ImageProcessingConfiguration) {
+        if (this._imageProcessingConfiguration) {
             ImageProcessingConfiguration.PrepareUniforms(uniforms, this._imageProcessingConfigurationDefines);
             ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
         }
+    }
+
+    /** @hidden */
+    public _recreateRenderEffect(): Effect {
+        const customEffect = this.getCustomEffect();
+
+        if (customEffect) {
+            return customEffect;
+        }
+
+        let defines: Array<string> = [];
+
+        this.fillDefines(defines);
+
+        var join = defines.join("\n");
+
+        if (this._renderEffect && this._renderEffect.defines === join) {
+            return this._renderEffect;
+        }
+
+        var attributes: Array<string> = [];
+        var uniforms: Array<string> = [];
+        var samplers: Array<string> = [];
+
+        this.fillUniformsAttributesAndSamplerNames(uniforms, attributes, samplers);
 
         this._renderEffect = new Effect("gpuRenderParticles",
-            ["position", "age", "life", "size", "color", "offset", "uv", "direction", "initialDirection", "angle", "cellIndex"],
+            attributes,
             uniforms,
-            samplers, this._scene.getEngine(), defines);
+            samplers, this._scene.getEngine(), join);
+
+        return this._renderEffect;
     }
 
     /**
@@ -1397,42 +1481,44 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
 
         if (!preWarm) {
             // Enable render effect
-            this._engine.enableEffect(this._renderEffect);
+            const effect = this._getEffect();
+
+            this._engine.enableEffect(effect);
             let viewMatrix = this._scene.getViewMatrix();
-            this._renderEffect.setMatrix("view", viewMatrix);
-            this._renderEffect.setMatrix("projection", this._scene.getProjectionMatrix());
-            this._renderEffect.setTexture("textureSampler", this.particleTexture);
-            this._renderEffect.setVector2("translationPivot", this.translationPivot);
-            this._renderEffect.setVector3("worldOffset", this.worldOffset);
+            effect.setMatrix("view", viewMatrix);
+            effect.setMatrix("projection", this._scene.getProjectionMatrix());
+            effect.setTexture("diffuseSampler", this.particleTexture);
+            effect.setVector2("translationPivot", this.translationPivot);
+            effect.setVector3("worldOffset", this.worldOffset);
             if (this.isLocal) {
-                this._renderEffect.setMatrix("emitterWM", emitterWM);
+                effect.setMatrix("emitterWM", emitterWM);
             }
             if (this._colorGradientsTexture) {
-                this._renderEffect.setTexture("colorGradientSampler", this._colorGradientsTexture);
+                effect.setTexture("colorGradientSampler", this._colorGradientsTexture);
             } else {
-                this._renderEffect.setDirectColor4("colorDead", this.colorDead);
+                effect.setDirectColor4("colorDead", this.colorDead);
             }
 
             if (this._isAnimationSheetEnabled && this.particleTexture) {
                 let baseSize = this.particleTexture.getBaseSize();
-                this._renderEffect.setFloat3("sheetInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
+                effect.setFloat3("sheetInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, baseSize.width / this.spriteCellWidth);
             }
 
             if (this._isBillboardBased) {
                 var camera = this._scene.activeCamera!;
-                this._renderEffect.setVector3("eyePosition", camera.globalPosition);
+                effect.setVector3("eyePosition", camera.globalPosition);
             }
 
             if (this._scene.clipPlane || this._scene.clipPlane2 || this._scene.clipPlane3 || this._scene.clipPlane4 || this._scene.clipPlane5 || this._scene.clipPlane6) {
                 var invView = viewMatrix.clone();
                 invView.invert();
-                this._renderEffect.setMatrix("invView", invView);
-                MaterialHelper.BindClipPlane(this._renderEffect, this._scene);
+                effect.setMatrix("invView", invView);
+                MaterialHelper.BindClipPlane(effect, this._scene);
             }
 
             // image processing
             if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {
-                this._imageProcessingConfiguration.bind(this._renderEffect);
+                this._imageProcessingConfiguration.bind(effect);
             }
 
             // Draw order
@@ -1458,6 +1544,10 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
             // Bind source VAO
             this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
 
+            if (this._onBeforeDrawParticlesObservable) {
+                this._onBeforeDrawParticlesObservable.notifyObservers(effect);
+            }
+
             // Render
             this._engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._currentActiveCount);
             this._engine.setAlphaMode(Constants.ALPHA_DISABLE);
@@ -1588,7 +1678,9 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
      * @returns the cloned particle system
      */
     public clone(name: string, newEmitter: any): GPUParticleSystem {
+        var custom = { ...this._customEffect };
         var result = new GPUParticleSystem(name, { capacity: this._capacity, randomTextureSize: this._randomTextureSize }, this._scene);
+        result._customEffect = custom;
 
         DeepCopier.DeepCopy(this, result, ["particles", "customShader", "noiseTexture", "particleTexture", "onDisposeObservable"]);
 

+ 1 - 1
src/Particles/particleHelper.ts

@@ -184,7 +184,7 @@ export class ParticleHelper {
                 }
             });
 
-            request.open("GET", this.SnippetUrl + "/" + snippetId.replace("#", "/"));
+            request.open("GET", this.SnippetUrl + "/" + snippetId.replace(/#/g, "/"));
             request.send();
         });
     }

+ 97 - 25
src/Particles/particleSystem.ts

@@ -81,9 +81,9 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
     /**
     * An event triggered when the system is disposed
     */
-    public onDisposeObservable = new Observable<ParticleSystem>();
+    public onDisposeObservable = new Observable<IParticleSystem>();
 
-    private _onDisposeObserver: Nullable<Observer<ParticleSystem>>;
+    private _onDisposeObserver: Nullable<Observer<IParticleSystem>>;
     /**
      * Sets a callback that will be triggered when the system is disposed
      */
@@ -105,7 +105,7 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
     private _spriteBuffer: Nullable<Buffer>;
     private _indexBuffer: Nullable<DataBuffer>;
     private _effect: Effect;
-    private _customEffect: Nullable<Effect>;
+    private _customEffect: { [blendMode: number] : Nullable<Effect> };
     private _cachedDefines: string;
     private _scaledColorStep = new Color4(0, 0, 0, 0);
     private _colorDiff = new Color4(0, 0, 0, 0);
@@ -214,6 +214,45 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
     }
 
     /**
+     * Gets the custom effect used to render the particles
+     * @param blendMode Blend mode for which the effect should be retrieved
+     * @returns The effect
+     */
+    public getCustomEffect(blendMode: number = 0): Nullable<Effect> {
+        return this._customEffect[blendMode] ?? this._customEffect[0];
+    }
+
+    /**
+     * Sets the custom effect used to render the particles
+     * @param effect The effect to set
+     * @param blendMode Blend mode for which the effect should be set
+     */
+    public setCustomEffect(effect: Nullable<Effect>, blendMode: number = 0) {
+        this._customEffect[blendMode] = effect;
+    }
+
+    /** @hidden */
+    private _onBeforeDrawParticlesObservable: Nullable<Observable<Nullable<Effect>>> = null;
+
+    /**
+     * Observable that will be called just before the particles are drawn
+     */
+    public get onBeforeDrawParticlesObservable(): Observable<Nullable<Effect>> {
+        if (!this._onBeforeDrawParticlesObservable) {
+            this._onBeforeDrawParticlesObservable = new Observable<Nullable<Effect>>();
+        }
+
+        return this._onBeforeDrawParticlesObservable;
+    }
+
+    /**
+     * Gets the name of the particle vertex shader
+     */
+    public get vertexShaderName(): string {
+        return "particles";
+    }
+
+    /**
      * Instantiates a particle system.
      * Particles are often small sprites used to simulate hard-to-reproduce phenomena like fire, smoke, water, or abstract visual effects like magic glitter and faery dust.
      * @param name The name of the particle system
@@ -238,7 +277,7 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
         // Setup the default processing configuration to the scene.
         this._attachImageProcessingConfiguration(null);
 
-        this._customEffect = customEffect;
+        this._customEffect = { 0: customEffect };
 
         this._scene.particleSystems.push(this);
 
@@ -1564,14 +1603,12 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
         return effectCreationOption;
     }
 
-    /** @hidden */
-    private _getEffect(blendMode: number): Effect {
-        if (this._customEffect) {
-            return this._customEffect;
-        }
-
-        var defines = [];
-
+    /**
+     * Fill the defines array according to the current settings of the particle system
+     * @param defines Array to be updated
+     * @param blendMode blend mode to take into account when updating the array
+     */
+    public fillDefines(defines: Array<string>, blendMode: number) {
         if (this._scene.clipPlane) {
             defines.push("#define CLIPPLANE");
         }
@@ -1628,21 +1665,49 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
             this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
             defines.push(this._imageProcessingConfigurationDefines.toString());
         }
+    }
+
+    /**
+     * Fill the uniforms, attributes and samplers arrays according to the current settings of the particle system
+     * @param uniforms Uniforms array to fill
+     * @param attributes Attributes array to fill
+     * @param samplers Samplers array to fill
+     */
+    public fillUniformsAttributesAndSamplerNames(uniforms: Array<string>, attributes: Array<string>, samplers: Array<string>) {
+        attributes.push(...ParticleSystem._GetAttributeNamesOrOptions(this._isAnimationSheetEnabled, this._isBillboardBased && this.billboardMode !== ParticleSystem.BILLBOARDMODE_STRETCHED, this._useRampGradients));
+
+        uniforms.push(...ParticleSystem._GetEffectCreationOptions(this._isAnimationSheetEnabled));
+
+        samplers.push("diffuseSampler", "rampSampler");
+
+        if (this._imageProcessingConfiguration) {
+            ImageProcessingConfiguration.PrepareUniforms(uniforms, this._imageProcessingConfigurationDefines);
+            ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
+        }
+    }
+
+    /** @hidden */
+    private _getEffect(blendMode: number): Effect {
+        const customEffect = this.getCustomEffect(blendMode);
+
+        if (customEffect) {
+            return customEffect;
+        }
+
+        var defines: Array<string> = [];
+
+        this.fillDefines(defines, blendMode);
 
         // Effect
         var join = defines.join("\n");
         if (this._cachedDefines !== join) {
             this._cachedDefines = join;
 
-            var attributesNamesOrOptions = ParticleSystem._GetAttributeNamesOrOptions(this._isAnimationSheetEnabled, this._isBillboardBased && this.billboardMode !== ParticleSystem.BILLBOARDMODE_STRETCHED, this._useRampGradients);
-            var effectCreationOption = ParticleSystem._GetEffectCreationOptions(this._isAnimationSheetEnabled);
-
-            var samplers = ["diffuseSampler", "rampSampler"];
+            var attributesNamesOrOptions: Array<string> = [];
+            var effectCreationOption: Array<string> = [];
+            var samplers: Array<string> = [];
 
-            if (ImageProcessingConfiguration) {
-                ImageProcessingConfiguration.PrepareUniforms(effectCreationOption, this._imageProcessingConfigurationDefines);
-                ImageProcessingConfiguration.PrepareSamplers(samplers, this._imageProcessingConfigurationDefines);
-            }
+            this.fillUniformsAttributesAndSamplerNames(effectCreationOption, attributesNamesOrOptions, samplers);
 
             this._effect = this._scene.getEngine().createEffect(
                 "particles",
@@ -1867,6 +1932,10 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
                 break;
         }
 
+        if (this._onBeforeDrawParticlesObservable) {
+            this._onBeforeDrawParticlesObservable.notifyObservers(effect);
+        }
+
         if (this._useInstancing) {
             engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._particles.length);
         } else {
@@ -1958,6 +2027,10 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
             (<AbstractMesh>this.emitter).dispose(true);
         }
 
+        if (this._onBeforeDrawParticlesObservable) {
+            this._onBeforeDrawParticlesObservable.clear();
+        }
+
         // Remove from scene
         var index = this._scene.particleSystems.indexOf(this);
         if (index > -1) {
@@ -1981,17 +2054,16 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
      * @returns the cloned particle system
      */
     public clone(name: string, newEmitter: any): ParticleSystem {
-        var custom: Nullable<Effect> = null;
+        var custom = { ...this._customEffect };
         var program: any = null;
         if (this.customShader != null) {
             program = this.customShader;
             var defines: string = (program.shaderOptions.defines.length > 0) ? program.shaderOptions.defines.join("\n") : "";
-            custom = this._scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
-        } else if (this._customEffect) {
-            custom = this._customEffect;
+            custom[0] = this._scene.getEngine().createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
         }
-        var result = new ParticleSystem(name, this._capacity, this._scene, custom);
+        var result = new ParticleSystem(name, this._capacity, this._scene, custom[0]);
         result.customShader = program;
+        result._customEffect = custom;
 
         DeepCopier.DeepCopy(this, result, ["particles", "customShader", "noiseTexture", "particleTexture", "onDisposeObservable"]);
 

+ 17 - 7
src/Particles/particleSystemComponent.ts

@@ -44,7 +44,8 @@ declare module "../Engines/engine" {
     export interface Engine {
         /**
          * Create an effect to use with particle systems.
-         * Please note that some parameters like animation sheets or not being billboard are not supported in this configuration
+         * Please note that some parameters like animation sheets or not being billboard are not supported in this configuration, except if you pass
+         * the particle system for which you want to create a custom effect in the last parameter
          * @param fragmentName defines the base name of the effect (The name of file without .fragment.fx)
          * @param uniformsNames defines a list of attribute names
          * @param samplers defines an array of string used to represent textures
@@ -52,18 +53,27 @@ declare module "../Engines/engine" {
          * @param fallbacks defines the list of potential fallbacks to use if shader conmpilation fails
          * @param onCompiled defines a function to call when the effect creation is successful
          * @param onError defines a function to call when the effect creation has failed
+         * @param particleSystem the particle system you want to create the effect for
          * @returns the new Effect
          */
         createEffectForParticles(fragmentName: string, uniformsNames: string[], samplers: string[], defines: string, fallbacks?: EffectFallbacks,
-            onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void): Effect;
+            onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, particleSystem?: IParticleSystem): Effect;
     }
 }
 
 Engine.prototype.createEffectForParticles = function(fragmentName: string, uniformsNames: string[] = [], samplers: string[] = [], defines = "", fallbacks?: EffectFallbacks,
-    onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void): Effect {
+    onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void, particleSystem?: IParticleSystem): Effect {
 
-    var attributesNamesOrOptions = ParticleSystem._GetAttributeNamesOrOptions();
-    var effectCreationOption = ParticleSystem._GetEffectCreationOptions();
+    var attributesNamesOrOptions: Array<string> = [];
+    var effectCreationOption: Array<string> = [];
+    var allSamplers: Array<string> = [];
+
+    if (particleSystem) {
+        particleSystem.fillUniformsAttributesAndSamplerNames(effectCreationOption, attributesNamesOrOptions, allSamplers);
+    } else {
+        attributesNamesOrOptions = ParticleSystem._GetAttributeNamesOrOptions();
+        effectCreationOption = ParticleSystem._GetEffectCreationOptions();
+    }
 
     if (defines.indexOf(" BILLBOARD") === -1) {
         defines += "\n#define BILLBOARD\n";
@@ -75,12 +85,12 @@ Engine.prototype.createEffectForParticles = function(fragmentName: string, unifo
 
     return this.createEffect(
         {
-            vertex: "particles",
+            vertex: particleSystem?.vertexShaderName ?? "particles",
             fragmentElement: fragmentName
         },
         attributesNamesOrOptions,
         effectCreationOption.concat(uniformsNames),
-        samplers, defines, fallbacks, onCompiled, onError);
+        allSamplers.concat(samplers), defines, fallbacks, onCompiled, onError);
 };
 
 declare module "../Meshes/mesh" {

+ 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;

+ 0 - 0
src/Shaders/gpuRenderParticles.fragment.fx


Некоторые файлы не были показаны из-за большого количества измененных файлов