瀏覽代碼

Merge pull request #3870 from bghgary/loader-updates

glTF loader updates
David Catuhe 7 年之前
父節點
當前提交
faed87539a

二進制
Playground/scenes/TwoQuads/TwoQuads.bin


+ 17 - 5
Playground/scenes/TwoQuads/TwoQuads.gltf

@@ -20,10 +20,16 @@
       "bufferView": 1,
       "componentType": 5126,
       "count": 4,
-      "type": "VEC2"
+      "type": "VEC3"
     },
     {
       "bufferView": 2,
+      "componentType": 5126,
+      "count": 4,
+      "type": "VEC2"
+    },
+    {
+      "bufferView": 3,
       "componentType": 5125,
       "count": 6,
       "type": "SCALAR"
@@ -35,7 +41,7 @@
   "buffers": [
     {
       "uri": "TwoQuads.bin",
-      "byteLength": 104
+      "byteLength": 152
     }
   ],
   "bufferViews": [
@@ -46,11 +52,16 @@
     {
       "buffer": 0,
       "byteOffset": 48,
+      "byteLength": 48
+    },
+    {
+      "buffer": 0,
+      "byteOffset": 96,
       "byteLength": 32
     },
     {
       "buffer": 0,
-      "byteOffset": 80,
+      "byteOffset": 128,
       "byteLength": 24
     }
   ],
@@ -111,9 +122,10 @@
         {
           "attributes": {
             "POSITION": 0,
-            "TEXCOORD_0": 1
+            "NORMAL": 1,
+            "TEXCOORD_0": 2
           },
-          "indices": 2,
+          "indices": 3,
           "material": 0
         }
       ]

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

@@ -107,5 +107,5 @@
 
 - Removed the unused PostProcessRenderPass class and extended postProcessingRenderingEffect to support multiple PostProcesses ([trevordev](https://github.com/trevordev))
 - VertexData.merge no longer supports merging of data that do not have the same set of attributes. ([bghgary](https://github.com/bghgary)]
-- glTF 2.0 loader will now create a mesh for each primitive instead of merging the primitives together into one mesh. ([bghgary](https://github.com/bghgary)]
+- glTF 2.0 loader now creates a mesh for each primitive instead of merging the primitives together into one mesh. If a mesh only has one primitive, the behavior is the same as before. This change only affects meshes that have multiple primitives. ([bghgary](https://github.com/bghgary)]
 - Engine's onCanvasPointerOutObservable will now return a PointerEvent instead of the Engine. ([trevordev](https://github.com/trevordev))

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

@@ -42,7 +42,10 @@ module BABYLON.GLTF2.Extensions {
                     const promise = this._loader._loadNodeAsync(`#/nodes/${nodeLOD._index}`, nodeLOD).then(() => {
                         if (indexLOD !== 0) {
                             const previousNodeLOD = nodeLODs[indexLOD - 1];
-                            previousNodeLOD._babylonMesh!.setEnabled(false);
+                            if (previousNodeLOD._babylonMesh) {
+                                previousNodeLOD._babylonMesh.dispose();
+                                delete previousNodeLOD._babylonMesh;
+                            }
                         }
 
                         if (indexLOD !== nodeLODs.length - 1) {
@@ -92,6 +95,12 @@ module BABYLON.GLTF2.Extensions {
                     const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD._index}`, materialLOD, babylonMesh, indexLOD === 0 ? assign : () => {}).then(() => {
                         if (indexLOD !== 0) {
                             assign(materialLOD._babylonMaterial!);
+
+                            const previousMaterialLOD = materialLODs[indexLOD - 1];
+                            if (previousMaterialLOD._babylonMaterial) {
+                                previousMaterialLOD._babylonMaterial.dispose();
+                                delete previousMaterialLOD._babylonMaterial;
+                            }
                         }
 
                         if (indexLOD !== materialLODs.length - 1) {

+ 39 - 32
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -307,8 +307,20 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => {});
         }
 
-        private _getMeshes(): AbstractMesh[] {
-            const meshes = new Array<AbstractMesh>();
+        private _forEachNodeMesh(node: ILoaderNode, callback: (babylonMesh: Mesh) => void): void {
+            if (node._babylonMesh) {
+                callback(node._babylonMesh);
+            }
+
+            if (node._primitiveBabylonMeshes) {
+                for (const babylonMesh of node._primitiveBabylonMeshes) {
+                    callback(babylonMesh);
+                }
+            }
+        }
+
+        private _getMeshes(): Mesh[] {
+            const meshes = new Array<Mesh>();
 
             // Root mesh is always first.
             meshes.push(this._rootBabylonMesh);
@@ -316,15 +328,9 @@ module BABYLON.GLTF2 {
             const nodes = this._gltf.nodes;
             if (nodes) {
                 for (const node of nodes) {
-                    if (node._babylonMesh) {
-                        meshes.push(node._babylonMesh);
-                    }
-
-                    if (node._primitiveBabylonMeshes) {
-                        for (const babylonMesh of node._primitiveBabylonMeshes) {
-                            meshes.push(babylonMesh);
-                        }
-                    }
+                    this._forEachNodeMesh(node, mesh => {
+                        meshes.push(mesh);
+                    });
                 }
             }
 
@@ -397,7 +403,7 @@ module BABYLON.GLTF2 {
 
             if (node.mesh != undefined) {
                 const mesh = GLTFLoader._GetProperty(`${context}/mesh`, this._gltf.meshes, node.mesh);
-                promises.push(this._loadMeshAsync(`#/meshes/${mesh._index}`, node, mesh));
+                promises.push(this._loadMeshAsync(`#/meshes/${mesh._index}`, node, mesh, babylonMesh));
             }
 
             if (node.children) {
@@ -412,7 +418,7 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => {});
         }
 
-        private _loadMeshAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh): Promise<void> {
+        private _loadMeshAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, babylonMesh: Mesh): Promise<void> {
             // TODO: instancing
 
             const promises = new Array<Promise<void>>();
@@ -423,8 +429,18 @@ module BABYLON.GLTF2 {
             }
 
             ArrayItem.Assign(primitives);
-            for (const primitive of primitives) {
-                promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive));
+            if (primitives.length === 1) {
+                const primitive = primitives[0];
+                promises.push(this._loadPrimitiveAsync(`${context}/primitives/${primitive._index}`, node, mesh, primitive, babylonMesh));
+            }
+            else {
+                node._primitiveBabylonMeshes = [];
+                for (const primitive of primitives) {
+                    const primitiveBabylonMesh = new Mesh(`${mesh.name || babylonMesh.name}_${primitive._index}`, this._babylonScene, babylonMesh);
+                    node._primitiveBabylonMeshes.push(babylonMesh);
+                    promises.push(this._loadPrimitiveAsync(`${context}/primitives/${primitive._index}`, node, mesh, primitive, primitiveBabylonMesh));
+                    this.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                }
             }
 
             if (node.skin != undefined) {
@@ -433,22 +449,15 @@ module BABYLON.GLTF2 {
             }
 
             return Promise.all(promises).then(() => {
-                if (node._primitiveBabylonMeshes) {
-                    for (const primitiveBabylonMesh of node._primitiveBabylonMeshes) {
-                        primitiveBabylonMesh._refreshBoundingInfo(true);
-                    }
-                }
+                this._forEachNodeMesh(node, babylonMesh => {
+                    babylonMesh._refreshBoundingInfo(true);
+                });
             });
         }
 
-        private _loadPrimitiveAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, primitive: ILoaderMeshPrimitive): Promise<void> {
+        private _loadPrimitiveAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Promise<void> {
             const promises = new Array<Promise<void>>();
 
-            const babylonMesh = new Mesh((mesh.name || node._babylonMesh!.name) + "_" + primitive._index, this._babylonScene, node._babylonMesh);
-
-            node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
-            node._primitiveBabylonMeshes[primitive._index] = babylonMesh;
-
             this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
 
             promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(babylonVertexData => {
@@ -466,8 +475,6 @@ module BABYLON.GLTF2 {
                 }));
             }
 
-            this.onMeshLoadedObservable.notifyObservers(babylonMesh);
-
             return Promise.all(promises).then(() => {});
         }
 
@@ -703,9 +710,9 @@ module BABYLON.GLTF2 {
 
         private _loadSkinAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, skin: ILoaderSkin): Promise<void> {
             const assignSkeleton = () => {
-                for (const babylonMesh of node._primitiveBabylonMeshes!) {
+                this._forEachNodeMesh(node, babylonMesh => {
                     babylonMesh.skeleton = skin._babylonSkeleton;
-                }
+                });
 
                 node._babylonMesh!.parent = this._rootBabylonMesh;
                 node._babylonMesh!.position = Vector3.Zero();
@@ -957,10 +964,10 @@ module BABYLON.GLTF2 {
                             outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                         })));
 
-                        for (const babylonMesh of targetNode._primitiveBabylonMeshes!) {
+                        this._forEachNodeMesh(targetNode, babylonMesh => {
                             const morphTarget = babylonMesh.morphTargetManager!.getTarget(targetIndex);
                             babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
-                        }
+                        });
                     }
                 }
                 else {

+ 54 - 34
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -40,7 +40,7 @@ describe('Babylon Scene Loader', function () {
         it('Load BoomBox', () => {
             const scene = new BABYLON.Scene(subject);
             return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(scene => {
-                expect(scene.meshes.length, "scene.meshes.length").to.equal(3);
+                expect(scene.meshes.length, "scene.meshes.length").to.equal(2);
                 expect(scene.materials.length, "scene.materials.length").to.equal(1);
             });
         });
@@ -48,7 +48,7 @@ describe('Babylon Scene Loader', function () {
         it('Load BoomBox GLB', () => {
             const scene = new BABYLON.Scene(subject);
             return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/", "BoomBox.glb", scene).then(scene => {
-                expect(scene.meshes.length, "scene.meshes.length").to.equal(3);
+                expect(scene.meshes.length, "scene.meshes.length").to.equal(2);
                 expect(scene.materials.length, "scene.materials.length").to.equal(1);
             });
         });
@@ -161,6 +161,12 @@ describe('Babylon Scene Loader', function () {
 
             const scene = new BABYLON.Scene(subject);
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
+                for (const mesh of scene.meshes) {
+                    if (mesh.material) {
+                        expect(mesh.material.isReady(mesh)).to.be.true;
+                    }
+                }
+
                 createShaderProgramSpy = sinon.spy(subject, "createShaderProgram");
             }));
 
@@ -200,14 +206,14 @@ describe('Babylon Scene Loader', function () {
                 expect(result.skeletons.length, "skeletons.length").to.equal(scene.skeletons.length);
 
                 const mapping = {
-                    "AlienHead_0": "skeleton0",
-                    "Collar_0": "skeleton1",
-                    "LeftEye_0": "skeleton2",
-                    "RightEye_0": "skeleton3",
-                    "CollarClasp_0": "skeleton1",
-                    "Shirt_0": "skeleton1",
-                    "ShirtPlate_0": "skeleton1",
-                    "Teeth_0": "skeleton1",
+                    "AlienHead": "skeleton0",
+                    "Collar": "skeleton1",
+                    "LeftEye": "skeleton2",
+                    "RightEye": "skeleton3",
+                    "CollarClasp": "skeleton1",
+                    "Shirt": "skeleton1",
+                    "ShirtPlate": "skeleton1",
+                    "Teeth": "skeleton1",
                 };
 
                 for (const meshName in mapping) {
@@ -220,45 +226,59 @@ describe('Babylon Scene Loader', function () {
         it('Load TwoQuads with LODs', () => {
             const scene = new BABYLON.Scene(subject);
             const promises = new Array<Promise<void>>();
-            const materials: { [name: string]: BABYLON.Material } = {};
 
             subject.runRenderLoop(() => {
                 for (const mesh of scene.meshes) {
                     if (mesh.material && mesh.isEnabled()) {
-                        expect(mesh.material.getActiveTextures().every(texture => texture.isReady()), "active mesh material textures are ready").to.be.true;
+                        expect(mesh.material.isReady(mesh), "mesh material is ready").to.be.true;
                     }
                 }
             });
 
             BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
-                loader.onMaterialLoaded = material => {
-                    expect(materials[material.name], `materials["${material.name}"]`).to.be.undefined;
-                    materials[material.name] = material;
-                };
+                loader.compileMaterials = true;
 
                 promises.push(loader.whenCompleteAsync().then(() => {
-                    expect(materials["LOD0"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 0 ready").to.be.true;
-                    expect(materials["LOD1"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 1 ready").to.be.true;
-                    expect(materials["LOD2"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 2 ready").to.be.true;
+                    const meshes = [
+                        scene.getMeshByName("node0"),
+                        scene.getMeshByName("node1")
+                    ];
+
+                    expect(meshes[0].material.name, "Material for node 0").to.equal("LOD0");
+                    expect(meshes[1].material.name, "Material for node 1").to.equal("LOD0");
+
+                    expect(scene.materials, "scene.materials").to.have.lengthOf(1);
+                    const materials = [
+                        scene.getMaterialByName("LOD0")
+                    ];
 
-                    expect(scene.getMeshByName("node0_0").material.name, "node 0 primitive 0 material").to.equal("LOD0");
-                    expect(scene.getMeshByName("node1_0").material.name, "node 1 primitive 0 material").to.equal("LOD0");
+                    expect(materials[0].isReady(meshes[0]), "Material of LOD 0 is ready for node 0").to.be.true;
+                    expect(materials[0].isReady(meshes[1]), "Material of LOD 0 is ready for node 1").to.be.true;
                 }));
             }, undefined, undefined, undefined, true);
 
             promises.push(BABYLON.SceneLoader.AppendAsync("/Playground/scenes/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
-                expect(Object.keys(materials), "materials").to.have.lengthOf(3);
-
-                expect(materials["LOD0"].getActiveTextures(), "material LOD 0 active textures").to.have.lengthOf(1);
-                expect(materials["LOD1"].getActiveTextures(), "material LOD 1 active textures").to.have.lengthOf(1);
-                expect(materials["LOD2"].getActiveTextures(), "material LOD 2 active textures").to.have.lengthOf(1);
-
-                expect(materials["LOD0"].getActiveTextures().some(texture => texture.isReady()), "Some textures of LOD 0 ready").to.be.false;
-                expect(materials["LOD1"].getActiveTextures().some(texture => texture.isReady()), "Some textures of LOD 1 ready").to.be.false;
-                expect(materials["LOD2"].getActiveTextures().every(texture => texture.isReady()), "All textures of LOD 2 ready").to.be.true;
-
-                expect(scene.getMeshByName("node0_0").material.name, "node 0 primitive 0 material").to.equal("LOD2");
-                expect(scene.getMeshByName("node1_0").material.name, "node 1 primitive 0 material").to.equal("LOD2");
+                const meshes = [
+                    scene.getMeshByName("node0"),
+                    scene.getMeshByName("node1")
+                ];
+
+                expect(meshes[0].material.name, "Material for node 0").to.equal("LOD2");
+                expect(meshes[1].material.name, "Material for node 1").to.equal("LOD2");
+
+                expect(scene.materials, "scene.materials").to.have.lengthOf(3);
+                const materials = [
+                    scene.getMaterialByName("LOD0"),
+                    scene.getMaterialByName("LOD1"),
+                    scene.getMaterialByName("LOD2")
+                ];
+
+                expect(materials[0].isReady(meshes[0]), "Material of LOD 0 is ready for node 0").to.be.false;
+                expect(materials[0].isReady(meshes[1]), "Material of LOD 0 is ready for node 1").to.be.false;
+                expect(materials[1].isReady(meshes[0]), "Material of LOD 1 is ready for node 0").to.be.false;
+                expect(materials[1].isReady(meshes[1]), "Material of LOD 1 is ready for node 1").to.be.false;
+                expect(materials[2].isReady(meshes[0]), "Material of LOD 2 is ready for node 0").to.be.true;
+                expect(materials[2].isReady(meshes[1]), "Material of LOD 2 is ready for node 1").to.be.true;
             }));
 
             return Promise.all(promises);
@@ -275,7 +295,7 @@ describe('Babylon Scene Loader', function () {
         it('should be loaded from BoomBox GLTF', () => {
             var scene = new BABYLON.Scene(subject);
             return BABYLON.SceneLoader.LoadAssetContainerAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(container => {
-                expect(container.meshes.length).to.eq(3);
+                expect(container.meshes.length).to.eq(2);
             });
         });
         it('should be adding and removing objects from scene', () => {