浏览代码

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Rousset 7 年之前
父节点
当前提交
14fb45c302
共有 61 个文件被更改,包括 15492 次插入12910 次删除
  1. 5964 5839
      Playground/babylon.d.txt
  2. 二进制
      Viewer/assets/babylon.woff
  3. 29 11
      Viewer/assets/templates/default/navbar.html
  4. 1 0
      Viewer/src/configuration/configuration.ts
  5. 10 4
      Viewer/src/configuration/types/default.ts
  6. 9 3
      Viewer/src/templating/templateManager.ts
  7. 46 9
      Viewer/src/viewer/defaultViewer.ts
  8. 23 3
      Viewer/src/viewer/viewer.ts
  9. 5783 5654
      dist/preview release/babylon.d.ts
  10. 52 52
      dist/preview release/babylon.js
  11. 634 184
      dist/preview release/babylon.max.js
  12. 634 184
      dist/preview release/babylon.no-module.max.js
  13. 30 30
      dist/preview release/babylon.worker.js
  14. 636 186
      dist/preview release/es6.js
  15. 1 1
      dist/preview release/glTF2Interface/package.json
  16. 1 1
      dist/preview release/gui/package.json
  17. 2 2
      dist/preview release/inspector/babylon.inspector.bundle.js
  18. 1 1
      dist/preview release/inspector/package.json
  19. 14 2
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  20. 62 51
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  21. 2 2
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  22. 14 2
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  23. 62 51
      dist/preview release/loaders/babylon.glTFFileLoader.js
  24. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  25. 14 2
      dist/preview release/loaders/babylonjs.loaders.d.ts
  26. 62 51
      dist/preview release/loaders/babylonjs.loaders.js
  27. 4 4
      dist/preview release/loaders/babylonjs.loaders.min.js
  28. 14 2
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  29. 2 2
      dist/preview release/loaders/package.json
  30. 1 1
      dist/preview release/materialsLibrary/package.json
  31. 1 1
      dist/preview release/postProcessesLibrary/package.json
  32. 1 1
      dist/preview release/proceduralTexturesLibrary/package.json
  33. 2 2
      dist/preview release/serializers/package.json
  34. 4 0
      dist/preview release/viewer/babylon.viewer.d.ts
  35. 42 42
      dist/preview release/viewer/babylon.viewer.js
  36. 782 260
      dist/preview release/viewer/babylon.viewer.max.js
  37. 4 0
      dist/preview release/viewer/babylon.viewer.module.d.ts
  38. 11 3
      dist/preview release/what's new.md
  39. 5 27
      loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts
  40. 4 25
      loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts
  41. 19 1
      loaders/src/glTF/2.0/Extensions/MSFT_lod.ts
  42. 26 15
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  43. 14 0
      loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts
  44. 1 1
      package.json
  45. 14 4
      src/Cameras/VR/babylon.vrExperienceHelper.ts
  46. 1 1
      src/Debug/babylon.rayHelper.ts
  47. 1 1
      src/Engine/babylon.engine.ts
  48. 24 1
      src/Gizmos/babylon.boundingBoxGizmo.ts
  49. 1 2
      src/Particles/EmitterTypes/babylon.IParticleEmitterType.ts
  50. 2 3
      src/Particles/EmitterTypes/babylon.boxParticleEmitter.ts
  51. 3 4
      src/Particles/EmitterTypes/babylon.coneParticleEmitter.ts
  52. 7 8
      src/Particles/EmitterTypes/babylon.sphereParticleEmitter.ts
  53. 41 11
      src/Particles/babylon.gpuParticleSystem.ts
  54. 5 0
      src/Particles/babylon.particle.ts
  55. 283 128
      src/Particles/babylon.particleSystem.ts
  56. 25 0
      src/Shaders/gpuRenderParticles.vertex.fx
  57. 8 7
      src/Shaders/gpuUpdateParticles.vertex.fx
  58. 41 12
      src/Shaders/particles.vertex.fx
  59. 10 13
      tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts
  60. 二进制
      tests/validation/ReferenceImages/particles.png
  61. 二进制
      tests/validation/ReferenceImages/yeti.png

文件差异内容过多而无法显示
+ 5964 - 5839
Playground/babylon.d.txt


二进制
Viewer/assets/babylon.woff


+ 29 - 11
Viewer/assets/templates/default/navbar.html

@@ -144,6 +144,16 @@
         content: "\EF4E";
     }
 
+    .hd-icon:after {
+        font-size: 16px;
+        content: "\F765";
+    }
+
+    .sd-icon:after {
+        font-size: 16px;
+        content: "\F766";
+    }
+
     .progress-control {
         display: flex;
         flex: 1;
@@ -422,7 +432,7 @@
 </style>
 {{/if}}
 
-<div class="nav-container" id="navbar-control">
+<div class="nav-container navbar-control">
     {{#unless hideLogo}}
     <div class="logo-button" title="{{logoText}}">
         {{#if logoLink}}
@@ -435,7 +445,7 @@
     {{/unless}}{{#unless (or (not animations) hideAnimations)}}
     <div class="animation-control">
         <div class="types">
-            <button class="flex-container animation-buttons" id="types-button">
+            <button class="flex-container types-button animation-buttons">
                 <!-- <div> -->
                 <span class="icon types-icon"></span>
                 <span class="control-text animation-label">{{selectedAnimationName}}</span>
@@ -447,7 +457,7 @@
             </button>
             <div class="menu-options">
                 {{#each animations}} {{#unless (eq ../selectedAnimation (add @index 1))}}
-                <button class="flex-container animation-buttons" id="label-option-button" data-value="{{this}}">
+                <button class="flex-container label-option-button animation-buttons" data-value="{{this}}">
                     <!-- <div> -->
                     <span class="icon types-icon"></span>
                     <span class="control-text animation-label">{{this}}</span>
@@ -457,24 +467,24 @@
                 {{/unless}} {{/each}}
             </div>
         </div>
-        <div class="progress-control" id="progress-control">
-            <button class="play-pause" id="play-pause-button">
+        <div class="progress-control">
+            <button class="play-pause play-pause-button">
                 {{#if paused}}
                 <span class="icon play-icon"></span>
                 {{else}}
                 <span class="icon pause-icon"></span>
                 {{/if}}
             </button>
-            <input class="progress-wrapper" id="progress-wrapper" type="range" min="0" max="100" step="0.01">
+            <input class="progress-wrapper" type="range" min="0" max="100" step="0.01">
         </div>
         <div class="speed">
-            <button class="flex-container" id="speed-button">
+            <button class="flex-container speed-button">
                 <span class="control-text speed-text">{{selectedSpeed}}</span>
                 <span class="icon up-icon"></span>
             </button>
             <div class="menu-options">
                 {{#eachInMap speedList}} {{#unless (eq ../selectedSpeed id)}}
-                <button class="flex-container" id="speed-option-button" data-value="{{value}}">
+                <button class="flex-container speed-option-button" data-value="{{value}}">
                     <span class="control-text speed-text">{{id}}</span>
                 </button>
                 {{/unless}} {{/eachInMap}}
@@ -483,12 +493,20 @@
     </div>
     {{/unless}}
     <div class="default-control">
-        {{#unless hideHelp}}
-        <button class="help" id="help-button" title="Help">
+        {{#unless hideHdButton}}
+        <button class="hd-button hd-button" title="{{text.hdButton}}">
+            {{#if hdEnabled}}
+            <span class="icon sd-icon"></span>
+            {{else}}
+            <span class="icon hd-icon"></span>
+            {{/if}}
+        </button>
+        {{/unless}} {{#unless hideHelp}}
+        <button class="help help-button" title="{{text.helpButton}}">
             <span class="icon help-icon"></span>
         </button>
         {{/unless}} {{#unless hideFullScreen}}
-        <button class="fullscreen" id="fullscreen-button" title="Fullscreen">
+        <button class="fullscreen fullscreen-button" title="{{text.fullscreenButton}}">
             <span class="icon fullscreen-icon"></span>
         </button>
         {{/unless}}

+ 1 - 0
Viewer/src/configuration/configuration.ts

@@ -54,6 +54,7 @@ export interface ViewerConfiguration {
         disableResize?: boolean;
         engineOptions?: EngineOptions;
         adaptiveQuality?: boolean;
+        hdEnabled?: boolean;
     },
     //templateStructure?: ITemplateStructure,
     templates?: {

+ 10 - 4
Viewer/src/configuration/types/default.ts

@@ -49,18 +49,24 @@ export let defaultConfiguration: ViewerConfiguration = {
                 logoText: 'BabylonJS',
                 logoLink: 'https://babylonjs.com',
                 hideHelp: true,
+                hideHdButton: true,
                 disableOnFullscreen: false,
+                text: {
+                    hdButton: "Toggle HD",
+                    fullscreenButton: "Fullscreen",
+                    helpButton: "Help"
+                }
             },
             events: {
                 pointerdown: {
-                    'navbar-control': true,
-                    'help-button': true
+                    '.navbar-control': true,
+                    '.help-button': true
                 },
                 input: {
-                    'progress-wrapper': true
+                    '.progress-wrapper': true
                 },
                 pointerup: {
-                    'progress-wrapper': true
+                    '.progress-wrapper': true
                 }
             }
         },

+ 9 - 3
Viewer/src/templating/templateManager.ts

@@ -592,10 +592,16 @@ export class Template {
                         // strict null checl is working incorrectly, must override:
                         let event = this._configuration.events[eventName] || {};
                         selectorsArray.filter(selector => event[selector]).forEach(selector => {
-                            if (selector && selector.indexOf('#') !== 0) {
-                                selector = '#' + selector;
-                            }
                             let htmlElement = <HTMLElement>this.parent.querySelector(selector);
+                            if (!htmlElement) {
+                                // backcompat, fallback to id
+                                if (selector && selector.indexOf('#') !== 0) {
+                                    selector = '#' + selector;
+                                }
+                                try {
+                                    htmlElement = <HTMLElement>this.parent.querySelector(selector);
+                                } catch (e) { }
+                            }
                             if (htmlElement) {
                                 let binding = functionToFire.bind(this, selector);
                                 htmlElement.addEventListener(eventName, binding, false);

+ 46 - 9
Viewer/src/viewer/defaultViewer.ts

@@ -47,11 +47,15 @@ export class DefaultViewer extends AbstractViewer {
         this._initNavbar();
 
         // close overlay button
-        let closeButton = document.getElementById('close-button');
-        if (closeButton) {
-            closeButton.addEventListener('pointerdown', () => {
-                this.hideOverlayScreen();
-            });
+        let template = this.templateManager.getTemplate('overlay');
+        if (template) {
+
+            let closeButton = template.parent.querySelector('.close-button');
+            if (closeButton) {
+                closeButton.addEventListener('pointerdown', () => {
+                    this.hideOverlayScreen();
+                });
+            }
         }
 
         if (this.configuration.templates && this.configuration.templates.viewer) {
@@ -85,7 +89,7 @@ export class DefaultViewer extends AbstractViewer {
             // an example how to trigger the help button. publiclly available
             this.templateManager.eventManager.registerCallback("navBar", () => {
                 // do your thing
-            }, "pointerdown", "#help-button");
+            }, "pointerdown", ".help-button");
 
             this.templateManager.eventManager.registerCallback("navBar", (event: EventCallback) => {
                 const evt = event.event;
@@ -101,7 +105,7 @@ export class DefaultViewer extends AbstractViewer {
                     this._togglePlayPause(true);
                 }
                 this._resumePlay = false;
-            }, "pointerup", "#progress-wrapper");
+            }, "pointerup", ".progress-wrapper");
         }
     }
 
@@ -122,7 +126,19 @@ export class DefaultViewer extends AbstractViewer {
 
         let parentClasses = element.parentElement!.classList;
 
-        switch (element.id) {
+        let elementClasses = element.classList;
+
+        let elementName = ""; 0
+
+        for (let i = 0; i < elementClasses.length; ++i) {
+            let className = elementClasses[i];
+            if (className.indexOf("-button") !== -1 || className.indexOf("-wrapper") !== -1) {
+                elementName = className;
+                break;
+            }
+        }
+
+        switch (elementName) {
             case "speed-button":
             case "types-button":
                 if (parentClasses.contains("open")) {
@@ -157,6 +173,9 @@ export class DefaultViewer extends AbstractViewer {
             case "fullscreen-button":
                 this.toggleFullscreen();
                 break;
+            case "hd-button":
+                this.toggleHD();
+                break;
             default:
                 return;
         }
@@ -195,7 +214,7 @@ export class DefaultViewer extends AbstractViewer {
     private _updateProgressBar = () => {
         let navbar = this.templateManager.getTemplate('navBar');
         if (!navbar) return;
-        var progressSlider = <HTMLInputElement>navbar.parent.querySelector("input#progress-wrapper");
+        var progressSlider = <HTMLInputElement>navbar.parent.querySelector("input.progress-wrapper");
         if (progressSlider && this._currentAnimation) {
             const progress = this._currentAnimation.currentFrame / this._currentAnimation.frames * 100;
             var currentValue = progressSlider.valueAsNumber;
@@ -264,6 +283,24 @@ export class DefaultViewer extends AbstractViewer {
         this._updateAnimationSpeed("1.0", paramsObject);
     }
 
+    public toggleHD() {
+        super.toggleHD();
+
+        // update UI element
+        let navbar = this.templateManager.getTemplate('navBar');
+        if (!navbar) return;
+
+        if (navbar.configuration.params) {
+            navbar.configuration.params.hdEnabled = this._hdToggled;
+        }
+
+        let span = navbar.parent.querySelector("button.hd-button span");
+        if (span) {
+            span.classList.remove(this._hdToggled ? "hd-icon" : "sd-icon");
+            span.classList.add(!this._hdToggled ? "hd-icon" : "sd-icon")
+        }
+    }
+
     /**
      * Toggle fullscreen of the entire viewer
      */

+ 23 - 3
Viewer/src/viewer/viewer.ts

@@ -250,6 +250,21 @@ export abstract class AbstractViewer {
         this._resize();
     }
 
+    protected _hdToggled: boolean = false;
+
+    public toggleHD() {
+        this._hdToggled = !this._hdToggled;
+
+        let currentLevel = this.engine.getHardwareScalingLevel();
+        const scalingFactor = 2;
+
+        if (this._hdToggled) {
+            this.engine.setHardwareScalingLevel(currentLevel / scalingFactor);
+        } else {
+            this.engine.setHardwareScalingLevel(currentLevel * scalingFactor);
+        }
+    }
+
     /**
      * The resize function that will be registered with the window object
      */
@@ -507,9 +522,14 @@ export abstract class AbstractViewer {
             window.addEventListener('resize', this._resize);
         }
 
-        if (this.configuration.engine && this.configuration.engine.adaptiveQuality) {
-            var scale = Math.max(0.5, 1 / (window.devicePixelRatio || 2));
-            this.engine.setHardwareScalingLevel(scale);
+        if (this.configuration.engine) {
+            if (this.configuration.engine.adaptiveQuality) {
+                var scale = Math.max(0.5, 1 / (window.devicePixelRatio || 2));
+                this.engine.setHardwareScalingLevel(scale);
+            }
+            if (this.configuration.engine.hdEnabled) {
+                this.toggleHD();
+            }
         }
 
         // create a new template manager for this viewer

文件差异内容过多而无法显示
+ 5783 - 5654
dist/preview release/babylon.d.ts


文件差异内容过多而无法显示
+ 52 - 52
dist/preview release/babylon.js


文件差异内容过多而无法显示
+ 634 - 184
dist/preview release/babylon.max.js


文件差异内容过多而无法显示
+ 634 - 184
dist/preview release/babylon.no-module.max.js


文件差异内容过多而无法显示
+ 30 - 30
dist/preview release/babylon.worker.js


文件差异内容过多而无法显示
+ 636 - 186
dist/preview release/es6.js


+ 1 - 1
dist/preview release/glTF2Interface/package.json

@@ -1,7 +1,7 @@
 {
     "name": "babylonjs-gltf2interface",
     "description": "A typescript declaration of babylon's gltf2 inteface.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/gui/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-gui",
     "description": "The Babylon.js GUI library is an extension you can use to generate interactive user interface. It is build on top of the DynamicTexture.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

文件差异内容过多而无法显示
+ 2 - 2
dist/preview release/inspector/babylon.inspector.bundle.js


+ 1 - 1
dist/preview release/inspector/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 14 - 2
dist/preview release/loaders/babylon.glTF2FileLoader.d.ts

@@ -512,6 +512,7 @@ declare module BABYLON.GLTF2 {
         private _getDefaultMaterial(drawMode);
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial);
         _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Promise<void>;
+        _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Promise<void>;
         _createMaterial(name: string, drawMode: number): PBRMaterial;
         _loadMaterialBasePropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): Promise<void>;
         _loadMaterialAlphaProperties(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): void;
@@ -578,6 +579,11 @@ declare module BABYLON.GLTF2 {
          */
         protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Override this method to modify the default behavior for loading material properties.
+         * @hidden
+         */
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Override this method to modify the default behavior for loading textures.
          * @hidden
          */
@@ -618,6 +624,11 @@ declare module BABYLON.GLTF2 {
          */
         static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Helper method called by the loader to allow extensions to override loading material properties.
+         * @hidden
+         */
+        static _LoadMaterialPropertiesAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Helper method called by the loader to allow extensions to override loading textures.
          * @hidden
          */
@@ -673,6 +684,7 @@ declare module BABYLON.GLTF2.Extensions {
          * Gets an array of LOD properties from lowest to highest.
          */
         private _getLODs<T>(context, property, array, ids);
+        private _disposeUnusedMaterials();
     }
 }
 
@@ -715,7 +727,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadSpecularGlossinessPropertiesAsync(context, material, properties, babylonMaterial);
     }
 }
@@ -727,7 +739,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_unlit extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadUnlitPropertiesAsync(context, material, babylonMaterial);
     }
 }

+ 62 - 51
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -822,9 +822,6 @@ var BABYLON;
                     });
                     resultPromise.then(function () {
                         _this._parent._endPerformanceCounter("Loading => Ready");
-                        if (_this._rootBabylonMesh) {
-                            _this._rootBabylonMesh.setEnabled(true);
-                        }
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {
@@ -922,7 +919,6 @@ var BABYLON;
             };
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
-                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 switch (this._parent.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -1059,6 +1055,7 @@ var BABYLON;
                 this._parent._logOpen(context + " " + (node.name || ""));
                 var babylonMesh = new BABYLON.Mesh(node.name || "node" + node._index, this._babylonScene, node._parent ? node._parent._babylonMesh : null);
                 node._babylonMesh = babylonMesh;
+                babylonMesh.setEnabled(false);
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 if (node.mesh != undefined) {
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
@@ -1077,7 +1074,9 @@ var BABYLON;
                 }
                 this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this._parent._logClose();
-                return Promise.all(promises).then(function () { });
+                return Promise.all(promises).then(function () {
+                    babylonMesh.setEnabled(true);
+                });
             };
             GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
                 var _this = this;
@@ -1771,25 +1770,38 @@ var BABYLON;
                 material._babylonData = material._babylonData || {};
                 var babylonData = material._babylonData[babylonDrawMode];
                 if (!babylonData) {
-                    var promises = new Array();
                     this._parent._logOpen(context + " " + (material.name || ""));
-                    var name_4 = material.name || "material_" + material._index;
+                    var name_4 = material.name || "material" + material._index;
                     var babylonMaterial = this._createMaterial(name_4, babylonDrawMode);
-                    promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                    promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial));
-                    this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     babylonData = {
                         material: babylonMaterial,
                         meshes: [],
-                        loaded: Promise.all(promises).then(function () { })
+                        loaded: this._loadMaterialPropertiesAsync(context, material, babylonMaterial)
                     };
                     material._babylonData[babylonDrawMode] = babylonData;
+                    this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     this._parent._logClose();
                 }
                 babylonData.meshes.push(babylonMesh);
+                babylonMesh.onDisposeObservable.addOnce(function () {
+                    var index = babylonData.meshes.indexOf(babylonMesh);
+                    if (index !== -1) {
+                        babylonData.meshes.splice(index, 1);
+                    }
+                });
                 assign(babylonData.material);
                 return babylonData.loaded;
             };
+            GLTFLoader.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
+                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialPropertiesAsync(this, context, material, babylonMaterial);
+                if (promise) {
+                    return promise;
+                }
+                var promises = new Array();
+                promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
+                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial));
+                return Promise.all(promises).then(function () { });
+            };
             GLTFLoader.prototype._createMaterial = function (name, drawMode) {
                 var babylonMaterial = new BABYLON.PBRMaterial(name, this._babylonScene);
                 babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? BABYLON.Material.CounterClockWiseSideOrientation : BABYLON.Material.ClockWiseSideOrientation;
@@ -2213,6 +2225,11 @@ var BABYLON;
              */
             GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) { return null; };
             /**
+             * Override this method to modify the default behavior for loading material properties.
+             * @hidden
+             */
+            GLTFLoaderExtension.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) { return null; };
+            /**
              * Override this method to modify the default behavior for loading textures.
              * @hidden
              */
@@ -2298,6 +2315,13 @@ var BABYLON;
                 return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, mesh, babylonMesh, babylonDrawMode, assign); });
             };
             /**
+             * Helper method called by the loader to allow extensions to override loading material properties.
+             * @hidden
+             */
+            GLTFLoaderExtension._LoadMaterialPropertiesAsync = function (loader, context, material, babylonMaterial) {
+                return loader._applyExtensions(function (extension) { return extension._loadMaterialPropertiesAsync(context, material, babylonMaterial); });
+            };
+            /**
              * Helper method called by the loader to allow extensions to override loading textures.
              * @hidden
              */
@@ -2432,8 +2456,9 @@ var BABYLON;
                                 if (indexLOD !== 0) {
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
                                     if (previousNodeLOD._babylonMesh) {
-                                        previousNodeLOD._babylonMesh.dispose(false, true);
+                                        previousNodeLOD._babylonMesh.dispose();
                                         delete previousNodeLOD._babylonMesh;
+                                        _this._disposeUnusedMaterials();
                                     }
                                 }
                             });
@@ -2533,6 +2558,23 @@ var BABYLON;
                     properties.push(property);
                     return properties;
                 };
+                MSFT_lod.prototype._disposeUnusedMaterials = function () {
+                    var materials = this._loader._gltf.materials;
+                    if (materials) {
+                        for (var _i = 0, materials_1 = materials; _i < materials_1.length; _i++) {
+                            var material = materials_1[_i];
+                            if (material._babylonData) {
+                                for (var drawMode in material._babylonData) {
+                                    var babylonData = material._babylonData[drawMode];
+                                    if (babylonData.meshes.length === 0) {
+                                        babylonData.material.dispose(false, true);
+                                        delete material._babylonData[drawMode];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                };
                 return MSFT_lod;
             }(GLTF2.GLTFLoaderExtension));
             Extensions.MSFT_lod = MSFT_lod;
@@ -2777,28 +2819,13 @@ var BABYLON;
                     _this.name = NAME;
                     return _this;
                 }
-                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (extensionContext, extension) {
-                        material._babylonData = material._babylonData || {};
-                        var babylonData = material._babylonData[babylonDrawMode];
-                        if (!babylonData) {
-                            var promises = new Array();
-                            var name_1 = material.name || "materialSG_" + material._index;
-                            var babylonMaterial = _this._loader._createMaterial(name_1, babylonDrawMode);
-                            promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                            promises.push(_this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
-                            _this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                            babylonData = {
-                                material: babylonMaterial,
-                                meshes: [],
-                                loaded: Promise.all(promises).then(function () { })
-                            };
-                            material._babylonData[babylonDrawMode] = babylonData;
-                        }
-                        babylonData.meshes.push(babylonMesh);
-                        assign(babylonData.material);
-                        return babylonData.loaded;
+                        var promises = new Array();
+                        promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
+                        promises.push(_this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
+                        return Promise.all(promises).then(function () { });
                     });
                 };
                 KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (context, material, properties, babylonMaterial) {
@@ -2865,31 +2892,15 @@ var BABYLON;
                     _this.name = NAME;
                     return _this;
                 }
-                KHR_materials_unlit.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) {
+                KHR_materials_unlit.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function () {
-                        material._babylonData = material._babylonData || {};
-                        var babylonData = material._babylonData[babylonDrawMode];
-                        if (!babylonData) {
-                            var name_1 = material.name || "materialUnlit_" + material._index;
-                            var babylonMaterial = _this._loader._createMaterial(name_1, babylonDrawMode);
-                            babylonMaterial.unlit = true;
-                            var promise = _this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
-                            _this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                            babylonData = {
-                                material: babylonMaterial,
-                                meshes: [],
-                                loaded: promise
-                            };
-                            material._babylonData[babylonDrawMode] = babylonData;
-                        }
-                        babylonData.meshes.push(babylonMesh);
-                        assign(babylonData.material);
-                        return babylonData.loaded;
+                        return _this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
                     });
                 };
                 KHR_materials_unlit.prototype._loadUnlitPropertiesAsync = function (context, material, babylonMaterial) {
                     var promises = new Array();
+                    babylonMaterial.unlit = true;
                     // Ensure metallic workflow
                     babylonMaterial.metallic = 1;
                     babylonMaterial.roughness = 1;

文件差异内容过多而无法显示
+ 2 - 2
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 14 - 2
dist/preview release/loaders/babylon.glTFFileLoader.d.ts

@@ -1074,6 +1074,7 @@ declare module BABYLON.GLTF2 {
         private _getDefaultMaterial(drawMode);
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial);
         _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Promise<void>;
+        _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Promise<void>;
         _createMaterial(name: string, drawMode: number): PBRMaterial;
         _loadMaterialBasePropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): Promise<void>;
         _loadMaterialAlphaProperties(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): void;
@@ -1140,6 +1141,11 @@ declare module BABYLON.GLTF2 {
          */
         protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Override this method to modify the default behavior for loading material properties.
+         * @hidden
+         */
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Override this method to modify the default behavior for loading textures.
          * @hidden
          */
@@ -1180,6 +1186,11 @@ declare module BABYLON.GLTF2 {
          */
         static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Helper method called by the loader to allow extensions to override loading material properties.
+         * @hidden
+         */
+        static _LoadMaterialPropertiesAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Helper method called by the loader to allow extensions to override loading textures.
          * @hidden
          */
@@ -1235,6 +1246,7 @@ declare module BABYLON.GLTF2.Extensions {
          * Gets an array of LOD properties from lowest to highest.
          */
         private _getLODs<T>(context, property, array, ids);
+        private _disposeUnusedMaterials();
     }
 }
 
@@ -1277,7 +1289,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadSpecularGlossinessPropertiesAsync(context, material, properties, babylonMaterial);
     }
 }
@@ -1289,7 +1301,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_unlit extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadUnlitPropertiesAsync(context, material, babylonMaterial);
     }
 }

+ 62 - 51
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -3023,9 +3023,6 @@ var BABYLON;
                     });
                     resultPromise.then(function () {
                         _this._parent._endPerformanceCounter("Loading => Ready");
-                        if (_this._rootBabylonMesh) {
-                            _this._rootBabylonMesh.setEnabled(true);
-                        }
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {
@@ -3123,7 +3120,6 @@ var BABYLON;
             };
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
-                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 switch (this._parent.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -3260,6 +3256,7 @@ var BABYLON;
                 this._parent._logOpen(context + " " + (node.name || ""));
                 var babylonMesh = new BABYLON.Mesh(node.name || "node" + node._index, this._babylonScene, node._parent ? node._parent._babylonMesh : null);
                 node._babylonMesh = babylonMesh;
+                babylonMesh.setEnabled(false);
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 if (node.mesh != undefined) {
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
@@ -3278,7 +3275,9 @@ var BABYLON;
                 }
                 this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this._parent._logClose();
-                return Promise.all(promises).then(function () { });
+                return Promise.all(promises).then(function () {
+                    babylonMesh.setEnabled(true);
+                });
             };
             GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
                 var _this = this;
@@ -3972,25 +3971,38 @@ var BABYLON;
                 material._babylonData = material._babylonData || {};
                 var babylonData = material._babylonData[babylonDrawMode];
                 if (!babylonData) {
-                    var promises = new Array();
                     this._parent._logOpen(context + " " + (material.name || ""));
-                    var name_4 = material.name || "material_" + material._index;
+                    var name_4 = material.name || "material" + material._index;
                     var babylonMaterial = this._createMaterial(name_4, babylonDrawMode);
-                    promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                    promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial));
-                    this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     babylonData = {
                         material: babylonMaterial,
                         meshes: [],
-                        loaded: Promise.all(promises).then(function () { })
+                        loaded: this._loadMaterialPropertiesAsync(context, material, babylonMaterial)
                     };
                     material._babylonData[babylonDrawMode] = babylonData;
+                    this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     this._parent._logClose();
                 }
                 babylonData.meshes.push(babylonMesh);
+                babylonMesh.onDisposeObservable.addOnce(function () {
+                    var index = babylonData.meshes.indexOf(babylonMesh);
+                    if (index !== -1) {
+                        babylonData.meshes.splice(index, 1);
+                    }
+                });
                 assign(babylonData.material);
                 return babylonData.loaded;
             };
+            GLTFLoader.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
+                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialPropertiesAsync(this, context, material, babylonMaterial);
+                if (promise) {
+                    return promise;
+                }
+                var promises = new Array();
+                promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
+                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial));
+                return Promise.all(promises).then(function () { });
+            };
             GLTFLoader.prototype._createMaterial = function (name, drawMode) {
                 var babylonMaterial = new BABYLON.PBRMaterial(name, this._babylonScene);
                 babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? BABYLON.Material.CounterClockWiseSideOrientation : BABYLON.Material.ClockWiseSideOrientation;
@@ -4414,6 +4426,11 @@ var BABYLON;
              */
             GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) { return null; };
             /**
+             * Override this method to modify the default behavior for loading material properties.
+             * @hidden
+             */
+            GLTFLoaderExtension.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) { return null; };
+            /**
              * Override this method to modify the default behavior for loading textures.
              * @hidden
              */
@@ -4499,6 +4516,13 @@ var BABYLON;
                 return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, mesh, babylonMesh, babylonDrawMode, assign); });
             };
             /**
+             * Helper method called by the loader to allow extensions to override loading material properties.
+             * @hidden
+             */
+            GLTFLoaderExtension._LoadMaterialPropertiesAsync = function (loader, context, material, babylonMaterial) {
+                return loader._applyExtensions(function (extension) { return extension._loadMaterialPropertiesAsync(context, material, babylonMaterial); });
+            };
+            /**
              * Helper method called by the loader to allow extensions to override loading textures.
              * @hidden
              */
@@ -4633,8 +4657,9 @@ var BABYLON;
                                 if (indexLOD !== 0) {
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
                                     if (previousNodeLOD._babylonMesh) {
-                                        previousNodeLOD._babylonMesh.dispose(false, true);
+                                        previousNodeLOD._babylonMesh.dispose();
                                         delete previousNodeLOD._babylonMesh;
+                                        _this._disposeUnusedMaterials();
                                     }
                                 }
                             });
@@ -4734,6 +4759,23 @@ var BABYLON;
                     properties.push(property);
                     return properties;
                 };
+                MSFT_lod.prototype._disposeUnusedMaterials = function () {
+                    var materials = this._loader._gltf.materials;
+                    if (materials) {
+                        for (var _i = 0, materials_1 = materials; _i < materials_1.length; _i++) {
+                            var material = materials_1[_i];
+                            if (material._babylonData) {
+                                for (var drawMode in material._babylonData) {
+                                    var babylonData = material._babylonData[drawMode];
+                                    if (babylonData.meshes.length === 0) {
+                                        babylonData.material.dispose(false, true);
+                                        delete material._babylonData[drawMode];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                };
                 return MSFT_lod;
             }(GLTF2.GLTFLoaderExtension));
             Extensions.MSFT_lod = MSFT_lod;
@@ -4978,28 +5020,13 @@ var BABYLON;
                     _this.name = NAME;
                     return _this;
                 }
-                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (extensionContext, extension) {
-                        material._babylonData = material._babylonData || {};
-                        var babylonData = material._babylonData[babylonDrawMode];
-                        if (!babylonData) {
-                            var promises = new Array();
-                            var name_1 = material.name || "materialSG_" + material._index;
-                            var babylonMaterial = _this._loader._createMaterial(name_1, babylonDrawMode);
-                            promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                            promises.push(_this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
-                            _this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                            babylonData = {
-                                material: babylonMaterial,
-                                meshes: [],
-                                loaded: Promise.all(promises).then(function () { })
-                            };
-                            material._babylonData[babylonDrawMode] = babylonData;
-                        }
-                        babylonData.meshes.push(babylonMesh);
-                        assign(babylonData.material);
-                        return babylonData.loaded;
+                        var promises = new Array();
+                        promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
+                        promises.push(_this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
+                        return Promise.all(promises).then(function () { });
                     });
                 };
                 KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (context, material, properties, babylonMaterial) {
@@ -5066,31 +5093,15 @@ var BABYLON;
                     _this.name = NAME;
                     return _this;
                 }
-                KHR_materials_unlit.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) {
+                KHR_materials_unlit.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function () {
-                        material._babylonData = material._babylonData || {};
-                        var babylonData = material._babylonData[babylonDrawMode];
-                        if (!babylonData) {
-                            var name_1 = material.name || "materialUnlit_" + material._index;
-                            var babylonMaterial = _this._loader._createMaterial(name_1, babylonDrawMode);
-                            babylonMaterial.unlit = true;
-                            var promise = _this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
-                            _this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                            babylonData = {
-                                material: babylonMaterial,
-                                meshes: [],
-                                loaded: promise
-                            };
-                            material._babylonData[babylonDrawMode] = babylonData;
-                        }
-                        babylonData.meshes.push(babylonMesh);
-                        assign(babylonData.material);
-                        return babylonData.loaded;
+                        return _this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
                     });
                 };
                 KHR_materials_unlit.prototype._loadUnlitPropertiesAsync = function (context, material, babylonMaterial) {
                     var promises = new Array();
+                    babylonMaterial.unlit = true;
                     // Ensure metallic workflow
                     babylonMaterial.metallic = 1;
                     babylonMaterial.roughness = 1;

文件差异内容过多而无法显示
+ 3 - 3
dist/preview release/loaders/babylon.glTFFileLoader.min.js


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

@@ -1170,6 +1170,7 @@ declare module BABYLON.GLTF2 {
         private _getDefaultMaterial(drawMode);
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial);
         _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Promise<void>;
+        _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Promise<void>;
         _createMaterial(name: string, drawMode: number): PBRMaterial;
         _loadMaterialBasePropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): Promise<void>;
         _loadMaterialAlphaProperties(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): void;
@@ -1236,6 +1237,11 @@ declare module BABYLON.GLTF2 {
          */
         protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Override this method to modify the default behavior for loading material properties.
+         * @hidden
+         */
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Override this method to modify the default behavior for loading textures.
          * @hidden
          */
@@ -1276,6 +1282,11 @@ declare module BABYLON.GLTF2 {
          */
         static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Helper method called by the loader to allow extensions to override loading material properties.
+         * @hidden
+         */
+        static _LoadMaterialPropertiesAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Helper method called by the loader to allow extensions to override loading textures.
          * @hidden
          */
@@ -1331,6 +1342,7 @@ declare module BABYLON.GLTF2.Extensions {
          * Gets an array of LOD properties from lowest to highest.
          */
         private _getLODs<T>(context, property, array, ids);
+        private _disposeUnusedMaterials();
     }
 }
 
@@ -1373,7 +1385,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadSpecularGlossinessPropertiesAsync(context, material, properties, babylonMaterial);
     }
 }
@@ -1385,7 +1397,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_unlit extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadUnlitPropertiesAsync(context, material, babylonMaterial);
     }
 }

+ 62 - 51
dist/preview release/loaders/babylonjs.loaders.js

@@ -4008,9 +4008,6 @@ var BABYLON;
                     });
                     resultPromise.then(function () {
                         _this._parent._endPerformanceCounter("Loading => Ready");
-                        if (_this._rootBabylonMesh) {
-                            _this._rootBabylonMesh.setEnabled(true);
-                        }
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {
@@ -4108,7 +4105,6 @@ var BABYLON;
             };
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
-                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 switch (this._parent.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -4245,6 +4241,7 @@ var BABYLON;
                 this._parent._logOpen(context + " " + (node.name || ""));
                 var babylonMesh = new BABYLON.Mesh(node.name || "node" + node._index, this._babylonScene, node._parent ? node._parent._babylonMesh : null);
                 node._babylonMesh = babylonMesh;
+                babylonMesh.setEnabled(false);
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 if (node.mesh != undefined) {
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
@@ -4263,7 +4260,9 @@ var BABYLON;
                 }
                 this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this._parent._logClose();
-                return Promise.all(promises).then(function () { });
+                return Promise.all(promises).then(function () {
+                    babylonMesh.setEnabled(true);
+                });
             };
             GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
                 var _this = this;
@@ -4957,25 +4956,38 @@ var BABYLON;
                 material._babylonData = material._babylonData || {};
                 var babylonData = material._babylonData[babylonDrawMode];
                 if (!babylonData) {
-                    var promises = new Array();
                     this._parent._logOpen(context + " " + (material.name || ""));
-                    var name_4 = material.name || "material_" + material._index;
+                    var name_4 = material.name || "material" + material._index;
                     var babylonMaterial = this._createMaterial(name_4, babylonDrawMode);
-                    promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                    promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial));
-                    this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     babylonData = {
                         material: babylonMaterial,
                         meshes: [],
-                        loaded: Promise.all(promises).then(function () { })
+                        loaded: this._loadMaterialPropertiesAsync(context, material, babylonMaterial)
                     };
                     material._babylonData[babylonDrawMode] = babylonData;
+                    this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     this._parent._logClose();
                 }
                 babylonData.meshes.push(babylonMesh);
+                babylonMesh.onDisposeObservable.addOnce(function () {
+                    var index = babylonData.meshes.indexOf(babylonMesh);
+                    if (index !== -1) {
+                        babylonData.meshes.splice(index, 1);
+                    }
+                });
                 assign(babylonData.material);
                 return babylonData.loaded;
             };
+            GLTFLoader.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
+                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialPropertiesAsync(this, context, material, babylonMaterial);
+                if (promise) {
+                    return promise;
+                }
+                var promises = new Array();
+                promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
+                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial));
+                return Promise.all(promises).then(function () { });
+            };
             GLTFLoader.prototype._createMaterial = function (name, drawMode) {
                 var babylonMaterial = new BABYLON.PBRMaterial(name, this._babylonScene);
                 babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? BABYLON.Material.CounterClockWiseSideOrientation : BABYLON.Material.ClockWiseSideOrientation;
@@ -5399,6 +5411,11 @@ var BABYLON;
              */
             GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) { return null; };
             /**
+             * Override this method to modify the default behavior for loading material properties.
+             * @hidden
+             */
+            GLTFLoaderExtension.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) { return null; };
+            /**
              * Override this method to modify the default behavior for loading textures.
              * @hidden
              */
@@ -5484,6 +5501,13 @@ var BABYLON;
                 return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, mesh, babylonMesh, babylonDrawMode, assign); });
             };
             /**
+             * Helper method called by the loader to allow extensions to override loading material properties.
+             * @hidden
+             */
+            GLTFLoaderExtension._LoadMaterialPropertiesAsync = function (loader, context, material, babylonMaterial) {
+                return loader._applyExtensions(function (extension) { return extension._loadMaterialPropertiesAsync(context, material, babylonMaterial); });
+            };
+            /**
              * Helper method called by the loader to allow extensions to override loading textures.
              * @hidden
              */
@@ -5609,8 +5633,9 @@ var BABYLON;
                                 if (indexLOD !== 0) {
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
                                     if (previousNodeLOD._babylonMesh) {
-                                        previousNodeLOD._babylonMesh.dispose(false, true);
+                                        previousNodeLOD._babylonMesh.dispose();
                                         delete previousNodeLOD._babylonMesh;
+                                        _this._disposeUnusedMaterials();
                                     }
                                 }
                             });
@@ -5710,6 +5735,23 @@ var BABYLON;
                     properties.push(property);
                     return properties;
                 };
+                MSFT_lod.prototype._disposeUnusedMaterials = function () {
+                    var materials = this._loader._gltf.materials;
+                    if (materials) {
+                        for (var _i = 0, materials_1 = materials; _i < materials_1.length; _i++) {
+                            var material = materials_1[_i];
+                            if (material._babylonData) {
+                                for (var drawMode in material._babylonData) {
+                                    var babylonData = material._babylonData[drawMode];
+                                    if (babylonData.meshes.length === 0) {
+                                        babylonData.material.dispose(false, true);
+                                        delete material._babylonData[drawMode];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                };
                 return MSFT_lod;
             }(GLTF2.GLTFLoaderExtension));
             Extensions.MSFT_lod = MSFT_lod;
@@ -5918,28 +5960,13 @@ var BABYLON;
                     _this.name = NAME;
                     return _this;
                 }
-                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (extensionContext, extension) {
-                        material._babylonData = material._babylonData || {};
-                        var babylonData = material._babylonData[babylonDrawMode];
-                        if (!babylonData) {
-                            var promises = new Array();
-                            var name_1 = material.name || "materialSG_" + material._index;
-                            var babylonMaterial = _this._loader._createMaterial(name_1, babylonDrawMode);
-                            promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                            promises.push(_this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
-                            _this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                            babylonData = {
-                                material: babylonMaterial,
-                                meshes: [],
-                                loaded: Promise.all(promises).then(function () { })
-                            };
-                            material._babylonData[babylonDrawMode] = babylonData;
-                        }
-                        babylonData.meshes.push(babylonMesh);
-                        assign(babylonData.material);
-                        return babylonData.loaded;
+                        var promises = new Array();
+                        promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
+                        promises.push(_this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
+                        return Promise.all(promises).then(function () { });
                     });
                 };
                 KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (context, material, properties, babylonMaterial) {
@@ -5997,31 +6024,15 @@ var BABYLON;
                     _this.name = NAME;
                     return _this;
                 }
-                KHR_materials_unlit.prototype._loadMaterialAsync = function (context, material, mesh, babylonMesh, babylonDrawMode, assign) {
+                KHR_materials_unlit.prototype._loadMaterialPropertiesAsync = function (context, material, babylonMaterial) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function () {
-                        material._babylonData = material._babylonData || {};
-                        var babylonData = material._babylonData[babylonDrawMode];
-                        if (!babylonData) {
-                            var name_1 = material.name || "materialUnlit_" + material._index;
-                            var babylonMaterial = _this._loader._createMaterial(name_1, babylonDrawMode);
-                            babylonMaterial.unlit = true;
-                            var promise = _this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
-                            _this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                            babylonData = {
-                                material: babylonMaterial,
-                                meshes: [],
-                                loaded: promise
-                            };
-                            material._babylonData[babylonDrawMode] = babylonData;
-                        }
-                        babylonData.meshes.push(babylonMesh);
-                        assign(babylonData.material);
-                        return babylonData.loaded;
+                        return _this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
                     });
                 };
                 KHR_materials_unlit.prototype._loadUnlitPropertiesAsync = function (context, material, babylonMaterial) {
                     var promises = new Array();
+                    babylonMaterial.unlit = true;
                     // Ensure metallic workflow
                     babylonMaterial.metallic = 1;
                     babylonMaterial.roughness = 1;

文件差异内容过多而无法显示
+ 4 - 4
dist/preview release/loaders/babylonjs.loaders.min.js


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

@@ -1177,6 +1177,7 @@ declare module BABYLON.GLTF2 {
         private _getDefaultMaterial(drawMode);
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial);
         _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Promise<void>;
+        _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Promise<void>;
         _createMaterial(name: string, drawMode: number): PBRMaterial;
         _loadMaterialBasePropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): Promise<void>;
         _loadMaterialAlphaProperties(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): void;
@@ -1243,6 +1244,11 @@ declare module BABYLON.GLTF2 {
          */
         protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Override this method to modify the default behavior for loading material properties.
+         * @hidden
+         */
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Override this method to modify the default behavior for loading textures.
          * @hidden
          */
@@ -1283,6 +1289,11 @@ declare module BABYLON.GLTF2 {
          */
         static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /**
+         * Helper method called by the loader to allow extensions to override loading material properties.
+         * @hidden
+         */
+        static _LoadMaterialPropertiesAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
+        /**
          * Helper method called by the loader to allow extensions to override loading textures.
          * @hidden
          */
@@ -1338,6 +1349,7 @@ declare module BABYLON.GLTF2.Extensions {
          * Gets an array of LOD properties from lowest to highest.
          */
         private _getLODs<T>(context, property, array, ids);
+        private _disposeUnusedMaterials();
     }
 }
 
@@ -1380,7 +1392,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadSpecularGlossinessPropertiesAsync(context, material, properties, babylonMaterial);
     }
 }
@@ -1392,7 +1404,7 @@ declare module BABYLON.GLTF2.Extensions {
      */
     class KHR_materials_unlit extends GLTFLoaderExtension {
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
         private _loadUnlitPropertiesAsync(context, material, babylonMaterial);
     }
 }

+ 2 - 2
dist/preview release/loaders/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-loaders",
     "description": "The Babylon.js file loaders library is an extension you can use to load different 3D file types into a Babylon scene.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "3.3.0-alpha.6"
+        "babylonjs-gltf2interface": "3.3.0-alpha.7"
     },
     "peerDependencies": {
         "babylonjs": ">=3.2.0-alpha"

+ 1 - 1
dist/preview release/materialsLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-materials",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/postProcessesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-post-process",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/proceduralTexturesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-procedural-textures",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 2 - 2
dist/preview release/serializers/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-serializers",
     "description": "The Babylon.js serializers library is an extension you can use to serialize Babylon scenes.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "3.3.0-alpha.6"
+        "babylonjs-gltf2interface": "3.3.0-alpha.7"
     },
     "peerDependencies": {
         "babylonjs": ">=3.2.0-alpha"

+ 4 - 0
dist/preview release/viewer/babylon.viewer.d.ts

@@ -168,6 +168,7 @@ declare module BabylonViewer {
                 * This will be executed when the templates initialize.
                 */
             protected _onTemplatesLoaded(): Promise<AbstractViewer>;
+            toggleHD(): void;
             /**
                 * Toggle fullscreen of the entire viewer
                 */
@@ -366,6 +367,8 @@ declare module BabylonViewer {
                 * force resizing the engine.
                 */
             forceResize(): void;
+            protected _hdToggled: boolean;
+            toggleHD(): void;
             /**
                 * The resize function that will be registered with the window object
                 */
@@ -986,6 +989,7 @@ declare module BabylonViewer {
                     disableResize?: boolean;
                     engineOptions?: BABYLON.EngineOptions;
                     adaptiveQuality?: boolean;
+                    hdEnabled?: boolean;
             };
             templates?: {
                     main: ITemplateConfiguration;

文件差异内容过多而无法显示
+ 42 - 42
dist/preview release/viewer/babylon.viewer.js


文件差异内容过多而无法显示
+ 782 - 260
dist/preview release/viewer/babylon.viewer.max.js


+ 4 - 0
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -168,6 +168,7 @@ declare module 'babylonjs-viewer/viewer/defaultViewer' {
                 * This will be executed when the templates initialize.
                 */
             protected _onTemplatesLoaded(): Promise<AbstractViewer>;
+            toggleHD(): void;
             /**
                 * Toggle fullscreen of the entire viewer
                 */
@@ -366,6 +367,8 @@ declare module 'babylonjs-viewer/viewer/viewer' {
                 * force resizing the engine.
                 */
             forceResize(): void;
+            protected _hdToggled: boolean;
+            toggleHD(): void;
             /**
                 * The resize function that will be registered with the window object
                 */
@@ -986,6 +989,7 @@ declare module 'babylonjs-viewer/configuration/configuration' {
                     disableResize?: boolean;
                     engineOptions?: EngineOptions;
                     adaptiveQuality?: boolean;
+                    hdEnabled?: boolean;
             };
             templates?: {
                     main: ITemplateConfiguration;

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

@@ -9,6 +9,12 @@
 - Added [Environment Texture Tools](https://doc.babylonjs.com/how_to/physically_based_rendering#creating-a-compressed-environment-texture) to reduce the size of the usual .DDS file ([sebavan](http://www.github.com/sebavan))
 - New GUI control: the [Grid](http://doc.babylonjs.com/how_to/gui#grid) ([Deltakosh](https://github.com/deltakosh))
 - New `serialize` and `Parse` functions to serialize and parse all procedural textures from the Procedural Textures Library ([julien-moreau](https://github.com/julien-moreau))
+- Particle system improvements ([Deltakosh](https://github.com/deltakosh))
+  - Improved CPU particles rendering performance (up to x2 on low end devices)
+  - Added support for `isBillboardBased`. [Doc](http://doc.babylonjs.com/babylon101/particles#alignment)
+  - Added support for `minScaleX`, `minScaleY`, `maxScaleX`, `maxScaleY`. [Doc](http://doc.babylonjs.com/babylon101/particles#size)
+  - Added support for `radiusRange` for sphere emitter. [Doc](http://doc.babylonjs.com/babylon101/particles#sphere-emitter)
+  - Added support for `ParticleSystem.BLENDMODE_ADD` alpha mode. [Doc](http://doc.babylonjs.com/babylon101/particles#particle-colors)
 
 ## Updates
 
@@ -28,12 +34,11 @@
 - UtilityLayer class to render another scene as a layer on top of an existing scene ([TrevorDev](https://github.com/TrevorDev))
 - AnimationGroup has now onAnimationGroupEnd observable ([RaananW](https://github.com/RaananW))
 - PointerDragBehavior, SixDofDragBehavior and MultiPointerScaleBehavior to enable drag and drop/scaling with mouse or 6dof controller on a mesh ([TrevorDev](https://github.com/TrevorDev))
-- Gizmo and GizmoManager classes used to manipulate meshes in a scene. Position, rotation, scale, and bounding box gizmos ([TrevorDev](https://github.com/TrevorDev))
+- Gizmo and GizmoManager classes used to manipulate meshes in a scene. Gizmo types include position, rotation, scale, and bounding box ([TrevorDev](https://github.com/TrevorDev))
 - Added a new `mesh.ignoreNonUniformScaling` to turn off non uniform scaling compensation ([Deltakosh](https://github.com/deltakosh))
 - AssetsManager tasks will only run when their state is INIT. It is now possible to remove a task from the assets manager ([RaananW](https://github.com/RaananW))
 - Added sprite isVisible field ([TrevorDev](https://github.com/TrevorDev))
-- Added support for `minScaleX`, `minScaleY`, `maxScaleX`, `maxScaleY` for particles ([Deltakosh](https://github.com/deltakosh))
-- Added support for `radiusRange` for sphere particle emitter ([Deltakosh](https://github.com/deltakosh))
+
 
 ### glTF Loader
 
@@ -51,6 +56,7 @@
 - Support for model drag and drop onto the canvas ([RaananW](https://github.com/RaananW))
 - New lab feature - global light rotation [#4347](https://github.com/BabylonJS/Babylon.js/issues/4347) ([RaananW](https://github.com/RaananW))
 - New NPM package - babylonjs-viewer-assets, to separate the binary assets and the code of the viewer ([RaananW](https://github.com/RaananW))
+- A new HD-Toggler button allows setting a better hardware scaling rate ([RaananW](https://github.com/RaananW))
 
 ### Documentation
 
@@ -62,6 +68,7 @@
 - RawTexture.CreateAlphaTexture no longer fails to create a usable texture ([TrevorDev](https://github.com/TrevorDev))
 - SceneSerializer.SerializeMesh now serializes all materials kinds (not only StandardMaterial) ([julien-moreau](https://github.com/julien-moreau))
 - WindowsMotionController's trackpad field will be updated prior to it's onTrackpadChangedObservable event ([TrevorDev](https://github.com/TrevorDev))
+- VR experience helper's controllers will not fire pointer events when laser's are disabled, instead the camera ray pointer event will be used ([TrevorDev](https://github.com/TrevorDev))
 
 ### Core Engine
 
@@ -87,6 +94,7 @@
 - Animation navbar now updates correctly when a new model is loaded [#4441](https://github.com/BabylonJS/Babylon.js/issues/4441) ([RaananW](https://github.com/RaananW))
 - Non-normalized meshes didn't center and focus correctly ([RaananW](https://github.com/RaananW))
 - Meshes with skeletons could have incorrect animations ([RaananW](https://github.com/RaananW))
+- Removed element IDs from viewer's templates to allow more than one viewqer in a single page [#4500](https://github.com/BabylonJS/Babylon.js/issues/4500) ([RaananW](https://github.com/RaananW))
 
 ### Loaders
 

+ 5 - 27
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -17,34 +17,12 @@ module BABYLON.GLTF2.Extensions {
     export class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         public readonly name = NAME;
 
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
             return this._loadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, (extensionContext, extension) => {
-                material._babylonData = material._babylonData || {};
-                let babylonData = material._babylonData[babylonDrawMode];
-                if (!babylonData) {
-                    const promises = new Array<Promise<void>>();
-
-                    const name = material.name || `materialSG_${material._index}`;
-                    const babylonMaterial = this._loader._createMaterial(name, babylonDrawMode);
-
-                    promises.push(this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                    promises.push(this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
-
-                    this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-
-                    babylonData = {
-                        material: babylonMaterial,
-                        meshes: [],
-                        loaded: Promise.all(promises).then(() => {})
-                    };
-
-                    material._babylonData[babylonDrawMode] = babylonData;
-                }
-
-                babylonData.meshes.push(babylonMesh);
-
-                assign(babylonData.material);
-                return babylonData.loaded;
+                const promises = new Array<Promise<void>>();
+                promises.push(this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial as PBRMaterial));
+                promises.push(this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial as PBRMaterial));
+                return Promise.all(promises).then(() => {});
             });
         }
 

+ 4 - 25
loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts

@@ -9,38 +9,17 @@ module BABYLON.GLTF2.Extensions {
     export class KHR_materials_unlit extends GLTFLoaderExtension {
         public readonly name = NAME;
 
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
             return this._loadExtensionAsync<{}>(context, material, () => {
-                material._babylonData = material._babylonData || {};
-                let babylonData = material._babylonData[babylonDrawMode];
-                if (!babylonData) {
-                    const name = material.name || `materialUnlit_${material._index}`;
-                    const babylonMaterial = this._loader._createMaterial(name, babylonDrawMode);
-                    babylonMaterial.unlit = true;
-
-                    const promise = this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
-
-                    this._loader._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-
-                    babylonData = {
-                        material: babylonMaterial,
-                        meshes: [],
-                        loaded: promise
-                    };
-
-                    material._babylonData[babylonDrawMode] = babylonData;
-                }
-
-                babylonData.meshes.push(babylonMesh);
-
-                assign(babylonData.material);
-                return babylonData.loaded;
+                return this._loadUnlitPropertiesAsync(context, material, babylonMaterial as PBRMaterial);
             });
         }
 
         private _loadUnlitPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): Promise<void> {
             const promises = new Array<Promise<void>>();
 
+            babylonMaterial.unlit = true;
+
             // Ensure metallic workflow
             babylonMaterial.metallic = 1;
             babylonMaterial.roughness = 1;

+ 19 - 1
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -116,8 +116,9 @@ module BABYLON.GLTF2.Extensions {
                         if (indexLOD !== 0) {
                             const previousNodeLOD = nodeLODs[indexLOD - 1];
                             if (previousNodeLOD._babylonMesh) {
-                                previousNodeLOD._babylonMesh.dispose(false, true);
+                                previousNodeLOD._babylonMesh.dispose();
                                 delete previousNodeLOD._babylonMesh;
+                                this._disposeUnusedMaterials();
                             }
                         }
                     });
@@ -228,6 +229,23 @@ module BABYLON.GLTF2.Extensions {
             properties.push(property);
             return properties;
         }
+
+        private _disposeUnusedMaterials(): void {
+            const materials = this._loader._gltf.materials;
+            if (materials) {
+                for (const material of materials) {
+                    if (material._babylonData) {
+                        for (const drawMode in material._babylonData) {
+                            const babylonData = material._babylonData[drawMode];
+                            if (babylonData.meshes.length === 0) {
+                                babylonData.material.dispose(false, true);
+                                delete material._babylonData[drawMode];
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
 
     GLTFLoader._Register(NAME, loader => new MSFT_lod(loader));

+ 26 - 15
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -189,10 +189,6 @@ module BABYLON.GLTF2 {
                 resultPromise.then(() => {
                     this._parent._endPerformanceCounter("Loading => Ready");
 
-                    if (this._rootBabylonMesh) {
-                        this._rootBabylonMesh.setEnabled(true);
-                    }
-
                     Tools.SetImmediate(() => {
                         if (!this._disposed) {
                             Promise.all(this._completePromises).then(() => {
@@ -299,7 +295,6 @@ module BABYLON.GLTF2 {
 
         private _createRootNode(): _ILoaderNode {
             this._rootBabylonMesh = new Mesh("__root__", this._babylonScene);
-            this._rootBabylonMesh.setEnabled(false);
 
             const rootNode = { _babylonMesh: this._rootBabylonMesh } as _ILoaderNode;
             switch (this._parent.coordinateSystemMode) {
@@ -457,6 +452,7 @@ module BABYLON.GLTF2 {
             const babylonMesh = new Mesh(node.name || `node${node._index}`, this._babylonScene, node._parent ? node._parent._babylonMesh : null);
             node._babylonMesh = babylonMesh;
 
+            babylonMesh.setEnabled(false);
             GLTFLoader._LoadTransform(node, babylonMesh);
 
             if (node.mesh != undefined) {
@@ -480,7 +476,9 @@ module BABYLON.GLTF2 {
 
             this._parent._logClose();
 
-            return Promise.all(promises).then(() => {});
+            return Promise.all(promises).then(() => {
+                babylonMesh.setEnabled(true);
+            });
         }
 
         private _loadMeshAsync(context: string, node: _ILoaderNode, mesh: _ILoaderMesh, babylonMesh: Mesh): Promise<void> {
@@ -1286,35 +1284,48 @@ module BABYLON.GLTF2 {
             material._babylonData = material._babylonData || {};
             let babylonData = material._babylonData[babylonDrawMode];
             if (!babylonData) {
-                const promises = new Array<Promise<void>>();
-
                 this._parent._logOpen(`${context} ${material.name || ""}`);
 
-                const name = material.name || `material_${material._index}`;
+                const name = material.name || `material${material._index}`;
                 const babylonMaterial = this._createMaterial(name, babylonDrawMode);
 
-                promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
-                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial));
-
-                this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-
                 babylonData = {
                     material: babylonMaterial,
                     meshes: [],
-                    loaded: Promise.all(promises).then(() => {})
+                    loaded: this._loadMaterialPropertiesAsync(context, material, babylonMaterial)
                 };
 
                 material._babylonData[babylonDrawMode] = babylonData;
 
+                this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+
                 this._parent._logClose();
             }
 
             babylonData.meshes.push(babylonMesh);
+            babylonMesh.onDisposeObservable.addOnce(() => {
+                const index = babylonData.meshes.indexOf(babylonMesh);
+                if (index !== -1) {
+                    babylonData.meshes.splice(index, 1);
+                }
+            });
 
             assign(babylonData.material);
             return babylonData.loaded;
         }
 
+        public _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Promise<void> {
+            const promise = GLTFLoaderExtension._LoadMaterialPropertiesAsync(this, context, material, babylonMaterial);
+            if (promise) {
+                return promise;
+            }
+
+            const promises = new Array<Promise<void>>();
+            promises.push(this._loadMaterialBasePropertiesAsync(context, material, babylonMaterial as PBRMaterial));
+            promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material, babylonMaterial as PBRMaterial));
+            return Promise.all(promises).then(() => {});
+        }
+
         public _createMaterial(name: string, drawMode: number): PBRMaterial {
             const babylonMaterial = new PBRMaterial(name, this._babylonScene);
             babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation;

+ 14 - 0
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -59,6 +59,12 @@ module BABYLON.GLTF2 {
         protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> { return null; }
 
         /**
+         * Override this method to modify the default behavior for loading material properties.
+         * @hidden
+         */
+        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> { return null; }
+
+        /**
          * Override this method to modify the default behavior for loading textures.
          * @hidden
          */
@@ -161,6 +167,14 @@ module BABYLON.GLTF2 {
         }
 
         /**
+         * Helper method called by the loader to allow extensions to override loading material properties.
+         * @hidden
+         */
+        public static _LoadMaterialPropertiesAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
+            return loader._applyExtensions(extension => extension._loadMaterialPropertiesAsync(context, material, babylonMaterial));
+        }
+
+        /**
          * Helper method called by the loader to allow extensions to override loading textures.
          * @hidden
          */

+ 1 - 1
package.json

@@ -9,7 +9,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "3.3.0-alpha.6",
+    "version": "3.3.0-alpha.7",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 14 - 4
src/Cameras/VR/babylon.vrExperienceHelper.ts

@@ -129,6 +129,7 @@ module BABYLON {
             this._laserPointer.rotation.x = Math.PI / 2;
             this._laserPointer.position.z = -0.5;
             this._laserPointer.isVisible = false;
+            this._laserPointer.isPickable = false;
 
             if(!webVRController.mesh){
                 // Create an empty mesh that is used prior to loading the high quality model
@@ -167,6 +168,7 @@ module BABYLON {
         public _setLaserPointerParent(mesh:AbstractMesh){
             var makeNotPick = (root: AbstractMesh) => {
                 root.name += " laserPointer";
+                root.isPickable = false;
                 root.getChildMeshes().forEach((c) => {
                     makeNotPick(c);
                 });
@@ -937,6 +939,10 @@ module BABYLON {
             }
         }
 
+        private get _noControllerIsActive(){
+            return !(this.leftController && this.leftController._activePointer) && !(this.rightController && this.rightController._activePointer)
+        }
+
         private beforeRender = () => {
             if(this.leftController && this.leftController._activePointer){
                 this._castRayAndSelectObject(this.leftController);
@@ -946,7 +952,7 @@ module BABYLON {
                 this._castRayAndSelectObject(this.rightController);
             }
 
-            if(!(this.leftController && this.leftController._activePointer) && !(this.rightController && this.rightController._activePointer)){
+            if(this._noControllerIsActive){
                 this._castRayAndSelectObject(this._cameraGazer);
             }else{
                 this._cameraGazer._gazeTracker.isVisible = false;
@@ -1139,12 +1145,16 @@ module BABYLON {
                     });
                 }
                 controller.webVRController.onTriggerStateChangedObservable.add((stateObject) => {
-                    if (!controller._pointerDownOnMeshAsked) {
+                    var gazer:VRExperienceHelperGazer = controller;
+                    if(this._noControllerIsActive){
+                        gazer = this._cameraGazer;
+                    }
+                    if (!gazer._pointerDownOnMeshAsked) {
                         if (stateObject.value > this._padSensibilityUp) {
-                            controller._selectionPointerDown();
+                            gazer._selectionPointerDown();
                         }
                     } else if (stateObject.value < this._padSensibilityDown) {
-                        controller._selectionPointerUp();
+                        gazer._selectionPointerUp();
                     }
                 });
             }

+ 1 - 1
src/Debug/babylon.rayHelper.ts

@@ -25,7 +25,7 @@ module BABYLON {
             this.ray = ray;
         }
 
-        public show(scene: Scene, color: Color3): void {
+        public show(scene: Scene, color?: Color3): void {
 
             if (!this._renderFunction && this.ray) {
 

+ 1 - 1
src/Engine/babylon.engine.ts

@@ -710,7 +710,7 @@
          * Returns the current version of the framework
          */
         public static get Version(): string {
-            return "3.3.0-alpha.6";
+            return "3.3.0-alpha.7";
         }
 
         // Updatable statics so stick with vars here

+ 24 - 1
src/Gizmos/babylon.boundingBoxGizmo.ts

@@ -8,6 +8,7 @@ module BABYLON {
         private _scaleBoxesParent:AbstractMesh;
         private _boundingDimensions = new BABYLON.Vector3(1,1,1);
         private _renderObserver:Nullable<Observer<Scene>> = null;
+        private _pointerObserver:Nullable<Observer<PointerInfo>> = null;
 
         /**
          * Creates an BoundingBoxGizmo
@@ -20,10 +21,13 @@ module BABYLON {
             // Do not update the gizmo's scale so it has a fixed size to the object its attached to
             this._updateScale = false;
 
-            // Create Material
+            // Create Materials
             var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
             coloredMaterial.disableLighting = true;
             coloredMaterial.emissiveColor = color;
+            var hoverColoredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
+            hoverColoredMaterial.disableLighting = true;
+            hoverColoredMaterial.emissiveColor = color.clone().add(new Color3(0.2,0.2,0.2));
 
             // Build bounding box out of lines
             this._lineBoundingBox = new BABYLON.AbstractMesh("", gizmoLayer.utilityLayerScene);
@@ -141,6 +145,24 @@ module BABYLON {
             }
             this._rootMesh.addChild(this._scaleBoxesParent);
 
+            // Hover color change
+            var pointerIds = new Array<AbstractMesh>();
+            this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo, eventState)=>{
+                if(!pointerIds[(<PointerEvent>pointerInfo.event).pointerId]){
+                    this._rotateSpheresParent.getChildMeshes().concat(this._scaleBoxesParent.getChildMeshes()).forEach((mesh)=>{
+                        if(pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh == mesh){
+                            pointerIds[(<PointerEvent>pointerInfo.event).pointerId]=mesh;
+                            mesh.material = hoverColoredMaterial;
+                        }
+                    });
+                }else{
+                    if(pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh != pointerIds[(<PointerEvent>pointerInfo.event).pointerId]){
+                        pointerIds[(<PointerEvent>pointerInfo.event).pointerId].material = coloredMaterial;
+                        delete pointerIds[(<PointerEvent>pointerInfo.event).pointerId];
+                    }
+                }
+            });
+
             // Update bounding box positions
             this._renderObserver = this.gizmoLayer.originalScene.onBeforeRenderObservable.add(()=>{
                 this._updateBoundingBox();
@@ -214,6 +236,7 @@ module BABYLON {
          * Disposes of the gizmo
          */
         public dispose(){
+            this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver); 
             this.gizmoLayer.originalScene.onBeforeRenderObservable.remove(this._renderObserver);
             this._lineBoundingBox.dispose();
             this._rotateSpheresParent.dispose();

+ 1 - 2
src/Particles/EmitterTypes/babylon.IParticleEmitterType.ts

@@ -6,12 +6,11 @@ module BABYLON {
     export interface IParticleEmitterType {
         /**
          * Called by the particle System when the direction is computed for the created particle.
-         * @param emitPower is the power of the particle (speed)
          * @param worldMatrix is the world matrix of the particle system
          * @param directionToUpdate is the direction vector to update with the result
          * @param particle is the particle we are computed the direction for
          */
-        startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void;
+        startDirectionFunction(worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void;
 
         /**
          * Called by the particle System when the position is computed for the created particle.

+ 2 - 3
src/Particles/EmitterTypes/babylon.boxParticleEmitter.ts

@@ -32,17 +32,16 @@ module BABYLON {
 
         /**
          * Called by the particle System when the direction is computed for the created particle.
-         * @param emitPower is the power of the particle (speed)
          * @param worldMatrix is the world matrix of the particle system
          * @param directionToUpdate is the direction vector to update with the result
          * @param particle is the particle we are computed the direction for
          */
-        public startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+        public startDirectionFunction(worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
             var randX = Scalar.RandomRange(this.direction1.x, this.direction2.x);
             var randY = Scalar.RandomRange(this.direction1.y, this.direction2.y);
             var randZ = Scalar.RandomRange(this.direction1.z, this.direction2.z);
 
-            Vector3.TransformNormalFromFloatsToRef(randX * emitPower, randY * emitPower, randZ * emitPower, worldMatrix, directionToUpdate);
+            Vector3.TransformNormalFromFloatsToRef(randX, randY, randZ, worldMatrix, directionToUpdate);
         }
 
         /**

+ 3 - 4
src/Particles/EmitterTypes/babylon.coneParticleEmitter.ts

@@ -57,14 +57,13 @@ module BABYLON {
 
         /**
          * Called by the particle System when the direction is computed for the created particle.
-         * @param emitPower is the power of the particle (speed)
          * @param worldMatrix is the world matrix of the particle system
          * @param directionToUpdate is the direction vector to update with the result
          * @param particle is the particle we are computed the direction for
          */
-        public startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+        public startDirectionFunction(worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
             if (this._angle === 0) {
-                Vector3.TransformNormalFromFloatsToRef(0, emitPower, 0, worldMatrix, directionToUpdate);
+                Vector3.TransformNormalFromFloatsToRef(0, 1.0, 0, worldMatrix, directionToUpdate);
             }
             else {
                 // measure the direction Vector from the emitter to the particle.
@@ -77,7 +76,7 @@ module BABYLON {
                 direction.z += randZ;
                 direction.normalize();
 
-                Vector3.TransformNormalFromFloatsToRef(direction.x * emitPower, direction.y * emitPower, direction.z * emitPower, worldMatrix, directionToUpdate);
+                Vector3.TransformNormalFromFloatsToRef(direction.x, direction.y, direction.z, worldMatrix, directionToUpdate);
             }
         }
 

+ 7 - 8
src/Particles/EmitterTypes/babylon.sphereParticleEmitter.ts

@@ -28,12 +28,11 @@ module BABYLON {
 
         /**
          * Called by the particle System when the direction is computed for the created particle.
-         * @param emitPower is the power of the particle (speed)
          * @param worldMatrix is the world matrix of the particle system
          * @param directionToUpdate is the direction vector to update with the result
          * @param particle is the particle we are computed the direction for
          */
-        public startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+        public startDirectionFunction(worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
             var direction = particle.position.subtract(worldMatrix.getTranslation()).normalize();
             var randX = Scalar.RandomRange(0, this.directionRandomizer);
             var randY = Scalar.RandomRange(0, this.directionRandomizer);
@@ -43,7 +42,7 @@ module BABYLON {
             direction.z += randZ;
             direction.normalize();
 
-            Vector3.TransformNormalFromFloatsToRef(direction.x * emitPower, direction.y * emitPower, direction.z * emitPower, worldMatrix, directionToUpdate);
+            Vector3.TransformNormalFromFloatsToRef(direction.x, direction.y, direction.z, worldMatrix, directionToUpdate);
         }
 
         /**
@@ -53,9 +52,10 @@ module BABYLON {
          * @param particle is the particle we are computed the position for
          */
         public startPositionFunction(worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle): void {
-            var phi = Scalar.RandomRange(0, 2 * Math.PI);
-            var theta = Scalar.RandomRange(0, Math.PI);
             var randRadius = this.radius - Scalar.RandomRange(0, this.radius * this.radiusRange);
+            var v = Scalar.RandomRange(0, 1.0); 
+            var phi = Scalar.RandomRange(0, 2 * Math.PI);
+            var theta = Math.acos(2 * v - 1);
             var randX = randRadius * Math.cos(phi) * Math.sin(theta);
             var randY = randRadius * Math.cos(theta);
             var randZ = randRadius * Math.sin(phi) * Math.sin(theta);
@@ -149,16 +149,15 @@ module BABYLON {
 
         /**
          * Called by the particle System when the direction is computed for the created particle.
-         * @param emitPower is the power of the particle (speed)
          * @param worldMatrix is the world matrix of the particle system
          * @param directionToUpdate is the direction vector to update with the result
          * @param particle is the particle we are computed the direction for
          */
-        public startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+        public startDirectionFunction(worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
             var randX = Scalar.RandomRange(this.direction1.x, this.direction2.x);
             var randY = Scalar.RandomRange(this.direction1.y, this.direction2.y);
             var randZ = Scalar.RandomRange(this.direction1.z, this.direction2.z);
-            Vector3.TransformNormalFromFloatsToRef(randX * emitPower, randY * emitPower, randZ * emitPower, worldMatrix, directionToUpdate);
+            Vector3.TransformNormalFromFloatsToRef(randX, randY, randZ, worldMatrix, directionToUpdate);
         }
 
         /**

+ 41 - 11
src/Particles/babylon.gpuParticleSystem.ts

@@ -57,7 +57,7 @@
 
         private _randomTexture: RawTexture;
 
-        private readonly _attributesStrideSize = 18;
+        private readonly _attributesStrideSize = 19;
         private _updateEffectOptions: EffectCreationOptions;
 
         private _randomTextureSize: number;
@@ -343,6 +343,23 @@
         public getClassName(): string {
             return "GPUParticleSystem";
         }            
+        
+        private _isBillboardBased = true;
+
+        /**
+         * Gets or sets a boolean indicating if the particles must be rendered as billboard or aligned with the direction
+         */
+        public get isBillboardBased(): boolean {
+            return this._isBillboardBased;
+        }      
+        
+        public set isBillboardBased(value: boolean) {
+            if (this._isBillboardBased === value) {
+                return;
+            }
+
+            this._isBillboardBased = value;
+        }          
 
         /**
          * Instantiates a GPU particle system.
@@ -413,8 +430,8 @@
             updateVertexBuffers["seed"] = source.createVertexBuffer("seed", 5, 1);
             updateVertexBuffers["size"] = source.createVertexBuffer("size", 6, 3);
             updateVertexBuffers["color"] = source.createVertexBuffer("color", 9, 4);
-            updateVertexBuffers["direction"] = source.createVertexBuffer("direction", 13, 3);
-            updateVertexBuffers["angle"] = source.createVertexBuffer("angle", 16, 2);
+            updateVertexBuffers["direction"] = source.createVertexBuffer("direction", 13, 4);
+            updateVertexBuffers["angle"] = source.createVertexBuffer("angle", 17, 2);
            
             let vao = this._engine.recordVertexArrayObject(updateVertexBuffers, null, this._updateEffect);
             this._engine.bindArrayBuffer(null);
@@ -429,7 +446,8 @@
             renderVertexBuffers["life"] = source.createVertexBuffer("life", 4, 1, this._attributesStrideSize, true);
             renderVertexBuffers["size"] = source.createVertexBuffer("size", 6, 3, this._attributesStrideSize, true);           
             renderVertexBuffers["color"] = source.createVertexBuffer("color", 9, 4, this._attributesStrideSize, true);
-            renderVertexBuffers["angle"] = source.createVertexBuffer("angle", 16, 2, this._attributesStrideSize, true);
+            renderVertexBuffers["direction"] = source.createVertexBuffer("direction", 13, 4, this._attributesStrideSize, true);
+            renderVertexBuffers["angle"] = source.createVertexBuffer("angle", 17, 2, this._attributesStrideSize, true);
 
             renderVertexBuffers["offset"] = spriteSource.createVertexBuffer("offset", 0, 2);
             renderVertexBuffers["uv"] = spriteSource.createVertexBuffer("uv", 2, 2);
@@ -474,7 +492,8 @@
               // direction
               data.push(0.0);
               data.push(0.0);
-              data.push(0.0);     
+              data.push(0.0);  
+              data.push(0.0);    
               
               // angle
               data.push(0.0);  
@@ -525,12 +544,16 @@
                 defines = "\n#define CLIPPLANE";
             }
 
+            if (this._isBillboardBased) {
+                defines = "\n#define BILLBOARD";
+            }            
+
             if (this._renderEffect && this._renderEffect.defines === defines) {
                 return;
             }
 
             this._renderEffect = new Effect("gpuRenderParticles", 
-                                            ["position", "age", "life", "size", "color", "offset", "uv", "angle"], 
+                                            ["position", "age", "life", "size", "color", "offset", "uv", "direction", "angle"], 
                                             ["view", "projection", "colorDead", "invView", "vClipPlane"], 
                                             ["textureSampler"], this._scene.getEngine(), defines);
         }        
@@ -639,11 +662,18 @@
             }            
 
             // Draw order
-            if (this.blendMode === ParticleSystem.BLENDMODE_ONEONE) {
-                this._engine.setAlphaMode(Engine.ALPHA_ONEONE);
-            } else {
-                this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
-            }            
+            switch(this.blendMode)
+            {
+                case ParticleSystem.BLENDMODE_ADD:
+                    this._engine.setAlphaMode(Engine.ALPHA_ADD);
+                    break;
+                case ParticleSystem.BLENDMODE_ONEONE:
+                    this._engine.setAlphaMode(Engine.ALPHA_ONEONE);
+                    break;
+                case ParticleSystem.BLENDMODE_STANDARD:
+                    this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                    break;
+            }      
 
             if (this.forceDepthWrite) {
                 this._engine.setDepthWrite(true);

+ 5 - 0
src/Particles/babylon.particle.ts

@@ -60,6 +60,11 @@
          */
         public cellIndex: number = 0;
 
+        /**
+         * Defines the energy applied to the particle direction.
+         */
+        public emitPower = 0;        
+
         private _currentFrameCounter = 0;
 
         /**

+ 283 - 128
src/Particles/babylon.particleSystem.ts

@@ -1,4 +1,11 @@
 module BABYLON {
+
+    /** @hidden */
+    class ColorGradient {
+        public gradient: number;
+        public color: Color4;
+    }
+
     /**
      * This represents a particle system in Babylon.
      * 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.
@@ -16,6 +23,11 @@
         public static BLENDMODE_STANDARD = 1;
 
         /**
+         * Add current color and particle color multiplied by particle’s alpha.
+         */
+        public static BLENDMODE_ADD = 2;
+
+        /**
          * List of animations used by the particle system.
          */
         public animations: Animation[] = [];
@@ -169,6 +181,8 @@
          */
         public gravity = Vector3.Zero();
 
+        private _colorGradients: Nullable<Array<ColorGradient>> = null;
+
        /**
          * Random direction of each particle after it has been emitted, between direction1 and direction2 vectors.
          * This only works when particleEmitterTyps is a BoxParticleEmitter
@@ -242,20 +256,20 @@
         }
 
         /**
-         * Random color of each particle after it has been emitted, between color1 and color2 vectors.
+         * Random color of each particle after it has been emitted, between color1 and color2 vectors
          */
         public color1 = new Color4(1.0, 1.0, 1.0, 1.0);
         /**
-         * Random color of each particle after it has been emitted, between color1 and color2 vectors.
+         * Random color of each particle after it has been emitted, between color1 and color2 vectors
          */
         public color2 = new Color4(1.0, 1.0, 1.0, 1.0);
         /**
-         * Color the particle will have at the end of its lifetime.
+         * Color the particle will have at the end of its lifetime
          */
         public colorDead = new Color4(0, 0, 0, 1.0);
 
         /**
-         * An optional mask to filter some colors out of the texture, or filter a part of the alpha channel.
+         * An optional mask to filter some colors out of the texture, or filter a part of the alpha channel
          */
         public textureMask = new Color4(1.0, 1.0, 1.0, 1.0);
 
@@ -267,48 +281,48 @@
 
         /**
          * This function can be defined to specify initial direction for every new particle.
-         * It by default use the emitterType defined function.
+         * It by default use the emitterType defined function
          */
-        public startDirectionFunction: (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle) => void;
+        public startDirectionFunction: (worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle) => void;
         /**
          * This function can be defined to specify initial position for every new particle.
-         * It by default use the emitterType defined function.
+         * It by default use the emitterType defined function
          */
         public startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle) => void;
 
         /**
-         * If using a spritesheet (isAnimationSheetEnabled), defines if the sprite animation should loop between startSpriteCellID and endSpriteCellID or not.
+         * If using a spritesheet (isAnimationSheetEnabled), defines if the sprite animation should loop between startSpriteCellID and endSpriteCellID or not
          */
         public spriteCellLoop = true;
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the speed of the sprite loop.
+         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the speed of the sprite loop
          */
         public spriteCellChangeSpeed = 0;
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the first sprite cell to display.
+         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the first sprite cell to display
          */
         public startSpriteCellID = 0;
         /**
-         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the last sprite cell to display.
+         * If using a spritesheet (isAnimationSheetEnabled) and spriteCellLoop defines the last sprite cell to display
          */
         public endSpriteCellID = 0;
         /**
-         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell width to use.
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell width to use
          */
         public spriteCellWidth = 0;
         /**
-         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell height to use.
+         * If using a spritesheet (isAnimationSheetEnabled), defines the sprite cell height to use
          */
         public spriteCellHeight = 0;
 
         /**
-        * An event triggered when the system is disposed.
+        * An event triggered when the system is disposed
         */
         public onDisposeObservable = new Observable<ParticleSystem>();
 
         private _onDisposeObserver: Nullable<Observer<ParticleSystem>>;
         /**
-         * Sets a callback that will be triggered when the system is disposed.
+         * Sets a callback that will be triggered when the system is disposed
          */
         public set onDispose(callback: () => void) {
             if (this._onDisposeObserver) {
@@ -318,12 +332,28 @@
         }
 
         /**
-         * Gets wether an animation sprite sheet is enabled or not on the particle system.
+         * Gets whether an animation sprite sheet is enabled or not on the particle system
          */
-        public get isAnimationSheetEnabled(): Boolean {
+        public get isAnimationSheetEnabled(): boolean {
             return this._isAnimationSheetEnabled;
         }
 
+        /**
+         * Gets or sets a boolean indicating if the particles must be rendered as billboard or aligned with the direction
+         */
+        public get isBillboardBased(): boolean {
+            return this._isBillboardBased;
+        }      
+        
+        public set isBillboardBased(value: boolean) {
+            if (this._isBillboardBased === value) {
+                return;
+            }
+
+            this._isBillboardBased = value;
+            this._resetEffect();
+        }            
+
         private _particles = new Array<Particle>();
         private _epsilon: number;
         private _capacity: number;
@@ -333,6 +363,7 @@
         private _vertexData: Float32Array;
         private _vertexBuffer: Nullable<Buffer>;
         private _vertexBuffers: { [key: string]: VertexBuffer } = {};
+        private _spriteBuffer: Nullable<Buffer>;
         private _indexBuffer: Nullable<WebGLBuffer>;
         private _effect: Effect;
         private _customEffect: Nullable<Effect>;
@@ -343,13 +374,15 @@
         private _scaledGravity = Vector3.Zero();
         private _currentRenderId = -1;
         private _alive: boolean;
+        private _useInstancing = false;
 
         private _started = false;
         private _stopped = false;
         private _actualFrame = 0;
         private _scaledUpdateSpeed: number;
-        private _vertexBufferSize = 12;
+        private _vertexBufferSize: number;
         private _isAnimationSheetEnabled: boolean;
+        private _isBillboardBased = true;
 
         // end of sheet animation
 
@@ -399,9 +432,6 @@
 
             this._epsilon = epsilon;
             this._isAnimationSheetEnabled = isAnimationSheetEnabled;
-            if (isAnimationSheetEnabled) {
-                this._vertexBufferSize = 13;
-            }
 
             this._scene = scene || Engine.LastCreatedScene;
 
@@ -409,26 +439,10 @@
 
             scene.particleSystems.push(this);
 
-            this._createIndexBuffer();
-
-            // 13 floats per particle (x, y, z, r, g, b, a, angle, scaleX, scaleY, offsetX, offsetY) + 1 filler
-            this._vertexData = new Float32Array(capacity * this._vertexBufferSize * 4);
-            this._vertexBuffer = new Buffer(scene.getEngine(), this._vertexData, true, this._vertexBufferSize);
-
-            var positions = this._vertexBuffer.createVertexBuffer(VertexBuffer.PositionKind, 0, 3);
-            var colors = this._vertexBuffer.createVertexBuffer(VertexBuffer.ColorKind, 3, 4);
-            var options = this._vertexBuffer.createVertexBuffer("options", 7, 3);
-            var size = this._vertexBuffer.createVertexBuffer("size", 10, 2);
-
-            if (this._isAnimationSheetEnabled) {
-                var cellIndexBuffer = this._vertexBuffer.createVertexBuffer("cellIndex", 12, 1);
-                this._vertexBuffers["cellIndex"] = cellIndexBuffer;
-            }
+            this._useInstancing = this._scene.getEngine().getCaps().instancedArrays;
 
-            this._vertexBuffers[VertexBuffer.PositionKind] = positions;
-            this._vertexBuffers[VertexBuffer.ColorKind] = colors;
-            this._vertexBuffers["options"] = options;
-            this._vertexBuffers["size"] = size;
+            this._createIndexBuffer();
+            this._createVertexBuffers();
 
             // Default emitter type
             this.particleEmitterType = new BoxParticleEmitter();
@@ -445,15 +459,31 @@
                         continue;
                     }
                     else {
-                        particle.colorStep.scaleToRef(this._scaledUpdateSpeed, this._scaledColorStep);
-                        particle.color.addInPlace(this._scaledColorStep);
-
-                        if (particle.color.a < 0)
-                            particle.color.a = 0;
+                        if (this._colorGradients) {
+                            let ratio = particle.age / particle.lifeTime;
+
+                            for (var gradientIndex = 0; gradientIndex < this._colorGradients.length - 1; gradientIndex++) {
+                                let currentGradient = this._colorGradients[gradientIndex];
+                                let nextGradient = this._colorGradients[gradientIndex + 1];
+
+                                if (ratio >= currentGradient.gradient && ratio <= nextGradient.gradient) {
+                                    let scale = (ratio - currentGradient.gradient) / (nextGradient.gradient - currentGradient.gradient);
+                                    Color4.LerpToRef(currentGradient.color, nextGradient.color, scale, particle.color);
+                                    break;
+                                }
+                            }
+                        }
+                        else {
+                            particle.colorStep.scaleToRef(this._scaledUpdateSpeed, this._scaledColorStep);
+                            particle.color.addInPlace(this._scaledColorStep);
 
+                            if (particle.color.a < 0) {
+                                particle.color.a = 0;
+                            }
+                        }
                         particle.angle += particle.angularSpeed * this._scaledUpdateSpeed;
 
-                        particle.direction.scaleToRef(this._scaledUpdateSpeed, this._scaledDirection);
+                        particle.direction.scaleToRef(this._scaledUpdateSpeed * particle.emitPower, this._scaledDirection);
                         particle.position.addInPlace(this._scaledDirection);
 
                         this.gravity.scaleToRef(this._scaledUpdateSpeed, this._scaledGravity);
@@ -467,7 +497,128 @@
             }
         }
 
+        /**
+         * Adds a new color gradient
+         * @param gradient defines the gradient to use (between 0 and 1)
+         * @param color defines the color to affect to the specified gradient
+         */
+        public addColorGradient(gradient: number, color: Color4): ParticleSystem {
+            if (!this._colorGradients) {
+                this._colorGradients = [];
+            }
+
+            let colorGradient = new ColorGradient();
+            colorGradient.gradient = gradient;
+            colorGradient.color = color;
+            this._colorGradients.push(colorGradient);
+
+            this._colorGradients.sort((a, b) => {
+                if (a.gradient < b.gradient) {
+                    return -1;
+                } else if (a.gradient > b.gradient) {
+                    return 1;
+                }
+
+                return 0;
+            })
+
+            return this;
+        }
+
+        /**
+         * Remove a specific color gradient
+         * @param gradient defines the gradient to remove
+         */
+        public removeColorGradient(gradient: number): ParticleSystem {
+            if (!this._colorGradients) {
+                return this;
+            }
+
+            let index = 0;
+            for (var colorGradient of this._colorGradients) {
+                if (colorGradient.gradient === gradient) {
+                    this._colorGradients.splice(index, 1);
+                    break;
+                }
+                index++;
+            }
+
+            return this;
+        }
+
+        private _resetEffect() {
+            if (this._vertexBuffer) {
+                this._vertexBuffer.dispose();
+                this._vertexBuffer = null;
+            }
+
+            if (this._spriteBuffer) {
+                this._spriteBuffer.dispose();
+                this._spriteBuffer = null;
+            }            
+
+            this._createVertexBuffers();           
+        }
+
+        private _createVertexBuffers() {
+            this._vertexBufferSize = this._useInstancing ? 10 : 12;
+            if (this._isAnimationSheetEnabled) {
+                this._vertexBufferSize += 1;
+            }
+
+            if (!this._isBillboardBased) {
+                this._vertexBufferSize += 3;
+            }
+
+            let engine = this._scene.getEngine();
+            this._vertexData = new Float32Array(this._capacity * this._vertexBufferSize * (this._useInstancing ? 1 : 4));
+            this._vertexBuffer = new Buffer(engine, this._vertexData, true, this._vertexBufferSize);
+
+            let dataOffset = 0;        
+            var positions = this._vertexBuffer.createVertexBuffer(VertexBuffer.PositionKind, dataOffset, 3, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers[VertexBuffer.PositionKind] = positions;
+            dataOffset += 3;
+
+            var colors = this._vertexBuffer.createVertexBuffer(VertexBuffer.ColorKind, dataOffset, 4, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers[VertexBuffer.ColorKind] = colors;
+            dataOffset += 4;
+
+            var options = this._vertexBuffer.createVertexBuffer("angle", dataOffset, 1, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers["angle"] = options;
+            dataOffset += 1;
+            
+            var size = this._vertexBuffer.createVertexBuffer("size", dataOffset, 2, this._vertexBufferSize, this._useInstancing);
+            this._vertexBuffers["size"] = size;
+            dataOffset += 2;
+
+            if (this._isAnimationSheetEnabled) {
+                var cellIndexBuffer = this._vertexBuffer.createVertexBuffer("cellIndex", dataOffset, 1, this._vertexBufferSize, this._useInstancing);
+                this._vertexBuffers["cellIndex"] = cellIndexBuffer;
+                dataOffset += 1;
+            }
+
+            if (!this._isBillboardBased) {
+                var directionBuffer = this._vertexBuffer.createVertexBuffer("direction", dataOffset, 3, this._vertexBufferSize, this._useInstancing);
+                this._vertexBuffers["direction"] = directionBuffer;
+                dataOffset += 3;
+            }
+
+            var offsets: VertexBuffer;
+            if (this._useInstancing) {
+                var spriteData = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);  
+                this._spriteBuffer = new Buffer(engine, spriteData, false, 2);  
+                offsets = this._spriteBuffer.createVertexBuffer("offset", 0, 2);
+            } else {
+                offsets = this._vertexBuffer.createVertexBuffer("offset", dataOffset, 2, this._vertexBufferSize, this._useInstancing);
+                dataOffset += 2;
+            }
+            this._vertexBuffers["offset"] = offsets;              
+        }
+
         private _createIndexBuffer() {
+            if (this._useInstancing) {
+                return;
+            }
             var indices = [];
             var index = 0;
             for (var count = 0; count < this._capacity; count++) {
@@ -546,48 +697,45 @@
          */
         public _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void {
             var offset = index * this._vertexBufferSize;
-            this._vertexData[offset] = particle.position.x;
-            this._vertexData[offset + 1] = particle.position.y;
-            this._vertexData[offset + 2] = particle.position.z;
-            this._vertexData[offset + 3] = particle.color.r;
-            this._vertexData[offset + 4] = particle.color.g;
-            this._vertexData[offset + 5] = particle.color.b;
-            this._vertexData[offset + 6] = particle.color.a;
-            this._vertexData[offset + 7] = particle.angle;
-            this._vertexData[offset + 8] = offsetX;
-            this._vertexData[offset + 9] = offsetY;   
-            this._vertexData[offset + 10] = particle.scale.x * particle.size;
-            this._vertexData[offset + 11] = particle.scale.y * particle.size;      
-        }
 
-        /**
-         * @hidden (for internal use only)
-         */
-        public _appendParticleVertexWithAnimation(index: number, particle: Particle, offsetX: number, offsetY: number): void {
-            if (offsetX === 0)
-                offsetX = this._epsilon;
-            else if (offsetX === 1)
-                offsetX = 1 - this._epsilon;
+            this._vertexData[offset++] = particle.position.x;
+            this._vertexData[offset++] = particle.position.y;
+            this._vertexData[offset++] = particle.position.z;
+            this._vertexData[offset++] = particle.color.r;
+            this._vertexData[offset++] = particle.color.g;
+            this._vertexData[offset++] = particle.color.b;
+            this._vertexData[offset++] = particle.color.a;
+            this._vertexData[offset++] = particle.angle;
+
+            this._vertexData[offset++] = particle.scale.x * particle.size;
+            this._vertexData[offset++] = particle.scale.y * particle.size;
+            
+            if (this._isAnimationSheetEnabled) {
+                this._vertexData[offset++] = particle.cellIndex;
+            }
 
-            if (offsetY === 0)
-                offsetY = this._epsilon;
-            else if (offsetY === 1)
-                offsetY = 1 - this._epsilon;
+            if (!this._isBillboardBased) {
+                this._vertexData[offset++] = particle.direction.x;
+                this._vertexData[offset++] = particle.direction.y;
+                this._vertexData[offset++] = particle.direction.z;
+            }
 
-            var offset = index * this._vertexBufferSize;
-            this._vertexData[offset] = particle.position.x;
-            this._vertexData[offset + 1] = particle.position.y;
-            this._vertexData[offset + 2] = particle.position.z;
-            this._vertexData[offset + 3] = particle.color.r;
-            this._vertexData[offset + 4] = particle.color.g;
-            this._vertexData[offset + 5] = particle.color.b;
-            this._vertexData[offset + 6] = particle.color.a;
-            this._vertexData[offset + 7] = particle.angle;
-            this._vertexData[offset + 8] = offsetX;
-            this._vertexData[offset + 9] = offsetY;   
-            this._vertexData[offset + 10] = particle.scale.x * particle.size;
-            this._vertexData[offset + 11] = particle.scale.y * particle.size;
-            this._vertexData[offset + 12] = particle.cellIndex;
+            if (!this._useInstancing) {
+                if (this._isAnimationSheetEnabled) {
+                    if (offsetX === 0)
+                        offsetX = this._epsilon;
+                    else if (offsetX === 1)
+                        offsetX = 1 - this._epsilon;
+    
+                    if (offsetY === 0)
+                        offsetY = this._epsilon;
+                    else if (offsetY === 1)
+                        offsetY = 1 - this._epsilon;
+                }
+
+                this._vertexData[offset++] = offsetX;
+                this._vertexData[offset++] = offsetY;   
+            }
         }
 
         // start of sub system methods
@@ -680,7 +828,7 @@
 
                 this._particles.push(particle);
 
-                var emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
+                particle.emitPower = Scalar.RandomRange(this.minEmitPower, this.maxEmitPower);
 
                 if (this.startPositionFunction) {
                     this.startPositionFunction(worldMatrix, particle.position, particle);
@@ -690,10 +838,10 @@
                 }
 
                 if (this.startDirectionFunction) {
-                    this.startDirectionFunction(emitPower, worldMatrix, particle.direction, particle);
+                    this.startDirectionFunction(worldMatrix, particle.direction, particle);
                 }
                 else {
-                    this.particleEmitterType.startDirectionFunction(emitPower, worldMatrix, particle.direction, particle);
+                    this.particleEmitterType.startDirectionFunction(worldMatrix, particle.direction, particle);
                 }
 
                 particle.lifeTime = Scalar.RandomRange(this.minLifeTime, this.maxLifeTime);
@@ -702,12 +850,14 @@
                 particle.scale.copyFromFloats(Scalar.RandomRange(this.minScaleX, this.maxScaleX), Scalar.RandomRange(this.minScaleY, this.maxScaleY));
                 particle.angularSpeed = Scalar.RandomRange(this.minAngularSpeed, this.maxAngularSpeed);
 
-                var step = Scalar.RandomRange(0, 1.0);
+                if (!this._colorGradients) {
+                    var step = Scalar.RandomRange(0, 1.0);
 
-                Color4.LerpToRef(this.color1, this.color2, step, particle.color);
+                    Color4.LerpToRef(this.color1, this.color2, step, particle.color);
 
-                this.colorDead.subtractToRef(particle.color, this._colorDiff);
-                this._colorDiff.scaleToRef(1.0 / particle.lifeTime, particle.colorStep);
+                    this.colorDead.subtractToRef(particle.color, this._colorDiff);
+                    this._colorDiff.scaleToRef(1.0 / particle.lifeTime, particle.colorStep);
+                }
             }
         }
 
@@ -726,21 +876,25 @@
                 defines.push("#define ANIMATESHEET");
             }
 
+            if (this._isBillboardBased) {
+                defines.push("#define BILLBOARD");
+            }
+
             // Effect
             var join = defines.join("\n");
             if (this._cachedDefines !== join) {
                 this._cachedDefines = join;
 
-                var attributesNamesOrOptions: any;
-                var effectCreationOption: any;
+                var attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "angle", "offset", "size"];
+                var effectCreationOption = ["invView", "view", "projection", "vClipPlane", "textureMask"];
 
                 if (this._isAnimationSheetEnabled) {
-                    attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "options", "size", "cellIndex"];
-                    effectCreationOption = ["invView", "view", "projection", "particlesInfos", "vClipPlane", "textureMask"];
+                    attributesNamesOrOptions.push("cellIndex");
+                    effectCreationOption.push("particlesInfos")
                 }
-                else {
-                    attributesNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "options", "size", ];
-                    effectCreationOption = ["invView", "view", "projection", "vClipPlane", "textureMask"]
+
+                if (!this._isBillboardBased) {
+                    attributesNamesOrOptions.push("direction");
                 }
 
                 this._effect = this._scene.getEngine().createEffect(
@@ -817,20 +971,12 @@
                 }
             }
 
-            // Animation sheet
-            if (this._isAnimationSheetEnabled) {
-                this._appendParticleVertexes = this._appenedParticleVertexesWithSheet;
-            }
-            else {
-                this._appendParticleVertexes = this._appenedParticleVertexesNoSheet;
-            }
-
             // Update VBO
             var offset = 0;
             for (var index = 0; index < this._particles.length; index++) {
                 var particle = this._particles[index];
-                this._appendParticleVertexes(offset, particle);
-                offset += 4;
+                this._appendParticleVertices(offset, particle);                
+                offset += this._useInstancing ? 1 : 4;
             }
 
             if (this._vertexBuffer) {
@@ -842,20 +988,13 @@
             }
         }
 
-        private _appendParticleVertexes: Nullable<(offset: number, particle: Particle) => void> = null;
-
-        private _appenedParticleVertexesWithSheet(offset: number, particle: Particle) {
-            this._appendParticleVertexWithAnimation(offset++, particle, 0, 0);
-            this._appendParticleVertexWithAnimation(offset++, particle, 1, 0);
-            this._appendParticleVertexWithAnimation(offset++, particle, 1, 1);
-            this._appendParticleVertexWithAnimation(offset++, particle, 0, 1);
-        }
-
-        private _appenedParticleVertexesNoSheet(offset: number, particle: Particle) {
+        private _appendParticleVertices(offset: number, particle: Particle) {
             this._appendParticleVertex(offset++, particle, 0, 0);
-            this._appendParticleVertex(offset++, particle, 1, 0);
-            this._appendParticleVertex(offset++, particle, 1, 1);
-            this._appendParticleVertex(offset++, particle, 0, 1);
+            if (!this._useInstancing) {
+                this._appendParticleVertex(offset++, particle, 1, 0);
+                this._appendParticleVertex(offset++, particle, 1, 1);
+                this._appendParticleVertex(offset++, particle, 0, 1);
+            }
         }
 
         /**
@@ -920,21 +1059,32 @@
                 effect.setFloat4("vClipPlane", clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.d);
             }
 
-            // VBOs
             engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
 
             // Draw order
-            if (this.blendMode === ParticleSystem.BLENDMODE_ONEONE) {
-                engine.setAlphaMode(Engine.ALPHA_ONEONE);
-            } else {
-                engine.setAlphaMode(Engine.ALPHA_COMBINE);
+            switch(this.blendMode)
+            {
+                case ParticleSystem.BLENDMODE_ADD:
+                    engine.setAlphaMode(Engine.ALPHA_ADD);
+                    break;
+                case ParticleSystem.BLENDMODE_ONEONE:
+                    engine.setAlphaMode(Engine.ALPHA_ONEONE);
+                    break;
+                case ParticleSystem.BLENDMODE_STANDARD:
+                    engine.setAlphaMode(Engine.ALPHA_COMBINE);
+                    break;
             }
 
             if (this.forceDepthWrite) {
                 engine.setDepthWrite(true);
             }
 
-            engine.drawElementsType(Material.TriangleFillMode, 0, this._particles.length * 6);
+            if (this._useInstancing) {
+                engine.drawArraysType(Material.TriangleFanDrawMode, 0, 4, this._particles.length);  
+                engine.unbindInstanceAttributes();
+            } else {
+                engine.drawElementsType(Material.TriangleFillMode, 0, this._particles.length * 6);
+            }
             engine.setAlphaMode(Engine.ALPHA_DISABLE);
 
             return this._particles.length;
@@ -950,6 +1100,11 @@
                 this._vertexBuffer = null;
             }
 
+            if (this._spriteBuffer) {
+                this._spriteBuffer.dispose();
+                this._spriteBuffer = null;
+            }
+
             if (this._indexBuffer) {
                 this._scene.getEngine()._releaseBuffer(this._indexBuffer);
                 this._indexBuffer = null;

+ 25 - 0
src/Shaders/gpuRenderParticles.vertex.fx

@@ -12,6 +12,7 @@ in vec3 size;
 in vec4 color;
 in vec2 offset;
 in vec2 uv;
+in vec4 direction;
 in vec2 angle;
 
 out vec2 vUV;
@@ -31,6 +32,7 @@ void main() {
 
   vec2 cornerPos = offset * size.yz * size.x;
 
+#ifdef BILLBOARD
   // Rotate
 	vec4 rotatedCorner;
 	rotatedCorner.x = cornerPos.x * cos(angle.x) - cornerPos.y * sin(angle.x);
@@ -41,6 +43,29 @@ void main() {
   // Expand position
   vec4 viewPosition = view * vec4(position, 1.0);
   gl_Position = projection * (viewPosition + rotatedCorner);
+#else
+  // Rotate
+	vec3 rotatedCorner;
+	rotatedCorner.x = cornerPos.x * cos(angle.x) - cornerPos.y * sin(angle.x);
+	rotatedCorner.y = 0.;
+	rotatedCorner.z = cornerPos.x * sin(angle.x) + cornerPos.y * cos(angle.x);
+
+	vec3 yaxis = normalize(direction.xyz);
+	vec3 xaxis = normalize(cross(vec3(0., 1.0, 0.), yaxis));
+	vec3 zaxis = normalize(cross(yaxis, xaxis));
+
+	vec3 row0 = vec3(xaxis.x, xaxis.y, xaxis.z);
+	vec3 row1 = vec3(yaxis.x, yaxis.y, yaxis.z);
+	vec3 row2 = vec3(zaxis.x, zaxis.y, zaxis.z);
+
+	mat3 rotMatrix =  mat3(row0, row1, row2);
+
+	vec3 alignedCorner = rotMatrix * rotatedCorner;
+
+  // Expand position
+  vec4 viewPosition = view * vec4(position + alignedCorner, 1.0);  
+  gl_Position = projection * viewPosition;
+#endif
 
 	// Clip plane
 #ifdef CLIPPLANE

+ 8 - 7
src/Shaders/gpuUpdateParticles.vertex.fx

@@ -49,7 +49,7 @@ in float life;
 in float seed;
 in vec3 size;
 in vec4 color;
-in vec3 direction;
+in vec4 direction;
 in vec2 angle;
 
 // Output
@@ -59,7 +59,7 @@ out float outLife;
 out float outSeed;
 out vec3 outSize;
 out vec4 outColor;
-out vec3 outDirection;
+out vec4 outDirection;
 out vec2 outAngle;
 
 vec3 getRandomVec3(float offset) {
@@ -123,7 +123,7 @@ void main() {
 
     // Position on the sphere surface
     float phi = 2.0 * PI * randoms2.x;
-    float theta = PI * randoms2.y;
+    float theta = acos(2.0 * randoms2.y - 1.0);
     float randX = cos(phi) * sin(theta);
     float randY = cos(theta);
     float randZ = sin(phi) * sin(theta);
@@ -168,19 +168,20 @@ void main() {
     direction = 2.0 * (getRandomVec3(seed) - vec3(0.5, 0.5, 0.5));
 #endif
 
-    float power = emitPower.x + (emitPower.y - emitPower.x) * randoms.a;
+    outDirection.w = emitPower.x + (emitPower.y - emitPower.x) * randoms.a;
 
     outPosition = (emitterWM * vec4(position, 1.)).xyz;
-    outDirection = (emitterWM * vec4(direction * power, 0.)).xyz;
+    outDirection.xyz = (emitterWM * vec4(direction, 0.)).xyz;
 
   } else {   
-    outPosition = position + direction * timeDelta;
+    outPosition = position + direction.xyz * timeDelta * direction.w;
     outAge = age + timeDelta;
     outLife = life;
     outSeed = seed;
     outColor = color;
     outSize = size;
-    outDirection = direction + gravity * timeDelta;
+    outDirection.w = direction.w;
+    outDirection.xyz = direction.xyz + gravity * timeDelta;
     outAngle = vec2(angle.x + angle.y * timeDelta, angle.y);
   }
 }

+ 41 - 12
src/Shaders/particles.vertex.fx

@@ -1,14 +1,23 @@
 // Attributes
 attribute vec3 position;
 attribute vec4 color;
-attribute vec3 options;
+attribute float angle;
 attribute vec2 size;
+#ifdef ANIMATESHEET	
 attribute float cellIndex;
+#endif
+#ifndef BILLBOARD	
+attribute vec3 direction;
+#endif
+attribute vec2 offset;
 
 // Uniforms
 uniform mat4 view;
 uniform mat4 projection;
+
+#ifdef ANIMATESHEET	
 uniform vec3 particlesInfos; // x (number of rows) y(number of columns) z(rowSize)
+#endif
 
 // Output
 varying vec2 vUV;
@@ -21,34 +30,54 @@ varying float fClipDistance;
 #endif
 
 void main(void) {	
-	vec3 viewPos = (view * vec4(position, 1.0)).xyz; 
 	vec2 cornerPos;
-	float angle = options.x;
-	vec2 offset = options.yz;
 	
 	cornerPos = vec2(offset.x - 0.5, offset.y  - 0.5) * size;
 
+#ifdef BILLBOARD	
 	// Rotate
 	vec3 rotatedCorner;
 	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
 	rotatedCorner.y = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
 	rotatedCorner.z = 0.;
 
+	vec3 viewPos = (view * vec4(position, 1.0)).xyz + rotatedCorner; 
+
 	// Position
-	viewPos += rotatedCorner;
 	gl_Position = projection * vec4(viewPos, 1.0);   
-	
+#else
+	// Rotate
+	vec3 rotatedCorner;
+	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
+	rotatedCorner.z = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
+	rotatedCorner.y = 0.;
+
+	vec3 yaxis = normalize(direction);
+	vec3 xaxis = normalize(cross(vec3(0., 1.0, 0.), yaxis));
+	vec3 zaxis = normalize(cross(yaxis, xaxis));
+
+	vec3 row0 = vec3(xaxis.x, xaxis.y, xaxis.z);
+	vec3 row1 = vec3(yaxis.x, yaxis.y, yaxis.z);
+	vec3 row2 = vec3(zaxis.x, zaxis.y, zaxis.z);
+
+	mat3 rotMatrix =  mat3(row0, row1, row2);
+
+	vec3 alignedCorner = rotMatrix * rotatedCorner;
+	vec3 worldPos = position + alignedCorner; 
+
+	gl_Position = projection * view * vec4(worldPos, 1.0);  
+#endif	
 	vColor = color;
 
 	#ifdef ANIMATESHEET
-	float rowOffset = floor(cellIndex / particlesInfos.z);
-    float columnOffset = cellIndex - rowOffset * particlesInfos.z;
+		float rowOffset = floor(cellIndex / particlesInfos.z);
+		float columnOffset = cellIndex - rowOffset * particlesInfos.z;
 
-	vec2 uvScale = particlesInfos.xy;
-	vec2 uvOffset = vec2(offset.x , 1.0 - offset.y);
-	vUV = (uvOffset + vec2(columnOffset, rowOffset)) * uvScale;
+		vec2 uvScale = particlesInfos.xy;
+		vec2 uvOffset = vec2(offset.x , 1.0 - offset.y);
+		vUV = (uvOffset + vec2(columnOffset, rowOffset)) * uvScale;
 	#else
-	vUV = offset;
+		vUV = offset;
 	#endif
 
 	// Clip plane

+ 10 - 13
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -161,28 +161,25 @@ describe('Babylon Scene Loader', function () {
             return Promise.race(promises);
         });
 
-        it('Load BoomBox with rootMesh.isEnabled check', () => {
+        it('Load BoomBox with mesh.isEnabled check', () => {
             const scene = new BABYLON.Scene(subject);
-            let rootMesh: BABYLON.AbstractMesh;
 
             subject.runRenderLoop(() => {
-                if (!rootMesh) {
-                    for (const mesh of scene.meshes) {
-                        if (!mesh.parent) {
-                            rootMesh = mesh;
-                            break;
-                        }
+                for (const mesh of scene.meshes) {
+                    if (mesh.getTotalVertices() !== 0) {
+                        expect(mesh.isEnabled(), "mesh.isEnabled").to.be.false;
                     }
                 }
-
-                if (rootMesh) {
-                    expect(rootMesh.isEnabled(), "rootMesh.isEnabled").to.be.false;
-                }
             });
 
             return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(scene => {
-                expect(rootMesh.isEnabled(), "rootMesh.isEnabled").to.be.true;
                 subject.stopRenderLoop();
+
+                for (const mesh of scene.meshes) {
+                    if (mesh.getTotalVertices() !== 0) {
+                        expect(mesh.isEnabled(), "mesh.isEnabled").to.be.true;
+                    }
+                }
             });
         });
 

二进制
tests/validation/ReferenceImages/particles.png


二进制
tests/validation/ReferenceImages/yeti.png