Browse Source

Add support for sheen to gltf exporter

David Catuhe 5 years ago
parent
commit
c1d95d8baf

File diff suppressed because it is too large
+ 46 - 46
dist/preview release/babylon.max.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.js


+ 4 - 0
dist/preview release/nodeEditor/babylon.nodeEditor.max.js

@@ -52667,6 +52667,9 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _diagram_graphFrame__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ../../diagram/graphFrame */ "./diagram/graphFrame.ts");
 /* harmony import */ var _sharedComponents_textInputLineComponent__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../../sharedComponents/textInputLineComponent */ "./sharedComponents/textInputLineComponent.tsx");
 /* harmony import */ var _sharedComponents_color3LineComponent__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ../../sharedComponents/color3LineComponent */ "./sharedComponents/color3LineComponent.tsx");
+/* harmony import */ var _sharedComponents_textLineComponent__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ../../sharedComponents/textLineComponent */ "./sharedComponents/textLineComponent.tsx");
+
+
 
 
 
@@ -52750,6 +52753,7 @@ var PropertyTabComponent = /** @class */ (function (_super) {
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", { id: "title" }, "NODE MATERIAL EDITOR")),
             react__WEBPACK_IMPORTED_MODULE_1__["createElement"]("div", null,
                 react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_sharedComponents_lineContainerComponent__WEBPACK_IMPORTED_MODULE_3__["LineContainerComponent"], { title: "GENERAL" },
+                    react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_sharedComponents_textLineComponent__WEBPACK_IMPORTED_MODULE_15__["TextLineComponent"], { label: "Version", value: babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_6__["Engine"].Version }),
                     react__WEBPACK_IMPORTED_MODULE_1__["createElement"](_sharedComponents_buttonLineComponent__WEBPACK_IMPORTED_MODULE_2__["ButtonLineComponent"], { label: "Reset to default", onClick: function () {
                             _this.props.globalState.nodeMaterial.setToDefault();
                             _this.props.globalState.onResetRequiredObservable.notifyObservers();

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


File diff suppressed because it is too large
+ 4 - 4
dist/preview release/viewer/babylon.viewer.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/viewer/babylon.viewer.max.js


+ 1 - 2
serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts

@@ -1,6 +1,5 @@
 import { SpotLight } from "babylonjs/Lights/spotLight";
 import { Vector3, Color3, Quaternion } from "babylonjs/Maths/math";
-import { Nullable } from "babylonjs/types";
 import { Light } from "babylonjs/Lights/light";
 import { Node } from "babylonjs/node";
 import { ShadowLight } from "babylonjs/Lights/shadowLight";
@@ -97,7 +96,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 {
      * @param babylonNode BabylonJS node
      * @returns nullable INode promise
      */
-    public postExportNodeAsync(context: string, node: INode, babylonNode: Node): Nullable<Promise<INode>> {
+    public postExportNodeAsync(context: string, node: INode, babylonNode: Node): Promise<INode> {
         return new Promise((resolve, reject) => {
             if (babylonNode instanceof ShadowLight) {
                 const babylonLight: ShadowLight = babylonNode;

+ 115 - 0
serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts

@@ -0,0 +1,115 @@
+import { ITextureInfo, IMaterial } from "babylonjs-gltf2interface";
+import { IGLTFExporterExtensionV2 } from "../glTFExporterExtension";
+import { _Exporter } from "../glTFExporter";
+import { Material } from 'babylonjs/Materials/material';
+import { PBRMaterial } from 'babylonjs/Materials/PBR/pbrMaterial';
+import { Texture } from 'babylonjs/Materials/Textures/texture';
+import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
+import { Nullable } from 'babylonjs/types';
+
+const NAME = "KHR_materials_sheen";
+
+interface IKHR_materials_sheen {
+    intensityFactor: number;
+    colorFactor: number[];
+    colorIntensityTexture?: ITextureInfo;
+}
+
+/**
+ * @hidden
+ */
+export class KHR_materials_sheen implements IGLTFExporterExtensionV2 {
+    /** Name of this extension */
+    public readonly name = NAME;
+
+    /** Defines whether this extension is enabled */
+    public enabled = true;
+
+    /** Defines whether this extension is required */
+    public required = false;
+
+    /** Reference to the glTF exporter */
+    private _exporter: _Exporter;
+    private _textureInfo: ITextureInfo;
+    private _exportedTexture: Nullable<BaseTexture> = null;
+
+    private _wasUsed = false;
+
+    constructor(exporter: _Exporter) {
+        this._exporter = exporter;
+    }
+
+    public dispose() {
+        delete this._exporter;
+    }
+
+    /** @hidden */
+    public onExporting(): void {
+        if (this._wasUsed) {
+            if (this._exporter._glTF.extensionsUsed == null) {
+                this._exporter._glTF.extensionsUsed = [];
+            }
+            if (this._exporter._glTF.extensionsUsed.indexOf(NAME) === -1) {
+                this._exporter._glTF.extensionsUsed.push(NAME);
+            }
+            if (this.required) {
+                if (this._exporter._glTF.extensionsRequired == null) {
+                    this._exporter._glTF.extensionsRequired = [];
+                }
+                if (this._exporter._glTF.extensionsRequired.indexOf(NAME) === -1) {
+                    this._exporter._glTF.extensionsRequired.push(NAME);
+                }
+            }
+            if (this._exporter._glTF.extensions == null) {
+                this._exporter._glTF.extensions = {};
+            }
+        }
+    }
+
+    public postExportTexture?(context: string, textureInfo:ITextureInfo, babylonTexture: Texture): void {
+        if (babylonTexture === this._exportedTexture || babylonTexture.reservedDataStore && babylonTexture.reservedDataStore.source === this._exportedTexture) {
+            this._textureInfo = textureInfo;
+        }
+    }
+
+    public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] {
+        if (babylonMaterial instanceof PBRMaterial) {
+            if (babylonMaterial.sheen.isEnabled && babylonMaterial.sheen.texture) {
+                this._exportedTexture = babylonMaterial.sheen.texture;
+                return [babylonMaterial.sheen.texture];
+            }
+        }
+
+        return [];
+    }
+    
+    public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise<IMaterial> {
+        return new Promise((resolve, reject) => {
+            if (babylonMaterial instanceof PBRMaterial) {
+                if (!babylonMaterial.sheen.isEnabled) {
+                    resolve(node);
+                    return;
+                }
+
+                this._wasUsed = true;
+
+                if (node.extensions == null) {
+                    node.extensions = {};
+                }
+                const sheenInfo: IKHR_materials_sheen = {
+                    colorFactor: babylonMaterial.sheen.color.asArray(),
+                    intensityFactor: babylonMaterial.sheen.intensity
+                };
+
+                if (this._textureInfo) {
+                    sheenInfo.colorIntensityTexture = this._textureInfo;
+                }
+
+                node.extensions[NAME] = sheenInfo;
+            }
+            resolve(node);
+        });
+    }    
+}
+
+_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_sheen(exporter));

+ 19 - 3
serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts

@@ -1,6 +1,4 @@
 import { ImageMimeType } from "babylonjs-gltf2interface";
-
-import { Nullable } from "babylonjs/types";
 import { Tools } from "babylonjs/Misc/tools";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { ProceduralTexture } from "babylonjs/Materials/Textures/Procedurals/proceduralTexture";
@@ -28,6 +26,8 @@ interface IKHRTextureTransform {
  * @hidden
  */
 export class KHR_texture_transform implements IGLTFExporterExtensionV2 {
+    private _recordedTextures: ProceduralTexture[] = [];
+
     /** Name of this extension */
     public readonly name = NAME;
 
@@ -45,10 +45,14 @@ export class KHR_texture_transform implements IGLTFExporterExtensionV2 {
     }
 
     public dispose() {
+        for (var texture of this._recordedTextures) {
+            texture.dispose();
+        }
+
         delete this._exporter;
     }
 
-    public preExportTextureAsync(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Nullable<Promise<Texture>> {
+    public preExportTextureAsync(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Promise<Texture> {
         return new Promise((resolve, reject) => {
             const scene = babylonTexture.getScene();
             if (!scene) {
@@ -72,6 +76,10 @@ export class KHR_texture_transform implements IGLTFExporterExtensionV2 {
                 texture_transform_extension.rotation = babylonTexture.wAng;
             }
 
+            if (babylonTexture.coordinatesIndex !== 0) {
+                texture_transform_extension.texCoord = babylonTexture.coordinatesIndex;
+            }
+
             if (!Object.keys(texture_transform_extension).length) {
                 resolve(babylonTexture);
                 return;
@@ -103,6 +111,14 @@ export class KHR_texture_transform implements IGLTFExporterExtensionV2 {
                 resolve(babylonTexture);
             }
 
+            proceduralTexture.reservedDataStore = {
+                hidden: true,
+                source: babylonTexture
+            }
+
+            this._recordedTextures.push(proceduralTexture);
+
+            proceduralTexture.coordinatesIndex = babylonTexture.coordinatesIndex;
             proceduralTexture.setTexture("textureSampler", babylonTexture);
             proceduralTexture.setMatrix("textureTransformMat", babylonTexture.getTextureMatrix());
 

+ 2 - 1
serializers/src/glTF/2.0/Extensions/index.ts

@@ -1,2 +1,3 @@
 export * from "./KHR_texture_transform";
-export * from "./KHR_lights_punctual";
+export * from "./KHR_lights_punctual";
+export * from "./KHR_materials_sheen";

+ 77 - 35
serializers/src/glTF/2.0/glTFExporter.ts

@@ -1,4 +1,4 @@
-import { AccessorType, IBufferView, IAccessor, INode, IScene, IMesh, IMaterial, ITexture, IImage, ISampler, IAnimation, ImageMimeType, IMeshPrimitive, IBuffer, IGLTF, MeshPrimitiveMode, AccessorComponentType } from "babylonjs-gltf2interface";
+import { AccessorType, IBufferView, IAccessor, INode, IScene, IMesh, IMaterial, ITexture, IImage, ISampler, IAnimation, ImageMimeType, IMeshPrimitive, IBuffer, IGLTF, MeshPrimitiveMode, AccessorComponentType, ITextureInfo } from "babylonjs-gltf2interface";
 
 import { FloatArray, Nullable, IndicesArray } from "babylonjs/types";
 import { Viewport, Color3, Vector2, Vector3, Vector4, Quaternion } from "babylonjs/Maths/math";
@@ -142,43 +142,68 @@ export class _Exporter {
     private static _ExtensionNames = new Array<string>();
     private static _ExtensionFactories: { [name: string]: (exporter: _Exporter) => IGLTFExporterExtensionV2 } = {};
 
-    private _applyExtensions<T>(property: any, actionAsync: (extension: IGLTFExporterExtensionV2) => Nullable<T> | undefined): Nullable<T> {
+    private _applyExtension<T>(node: T, extensions: IGLTFExporterExtensionV2[], index: number, actionAsync: (extension: IGLTFExporterExtensionV2, node: T) => Promise<Nullable<T>> | undefined): Promise<Nullable<T>> {
+        if (index >= extensions.length) {
+            return Promise.resolve(node);
+        }
+
+        let currentPromise = actionAsync(extensions[index], node);
+
+        if (!currentPromise) {
+            return this._applyExtension(node, extensions, index + 1, actionAsync)
+        }
+
+        return currentPromise.then(newNode => this._applyExtension(newNode || node, extensions, index + 1, actionAsync));
+    }
+
+
+    private _applyExtensions<T>(node: T, actionAsync: (extension: IGLTFExporterExtensionV2, node: T) => Promise<Nullable<T>> | undefined): Promise<Nullable<T>> {
+        var extensions: IGLTFExporterExtensionV2[] = [];
         for (const name of _Exporter._ExtensionNames) {
-            const extension = this._extensions[name];
-            if (extension.enabled) {
-                const exporterProperty = property as any;
-                exporterProperty._activeLoaderExtensions = exporterProperty._activeLoaderExtensions || {};
-                const activeLoaderExtensions = exporterProperty._activeLoaderExtensions;
-                if (!activeLoaderExtensions[name]) {
-                    activeLoaderExtensions[name] = true;
-
-                    try {
-                        const result = actionAsync(extension);
-                        if (result) {
-                            return result;
-                        }
-                    }
-                    finally {
-                        delete activeLoaderExtensions[name];
-                        delete exporterProperty._activeLoaderExtensions;
-                    }
-                }
-            }
+            extensions.push(this._extensions[name]);
         }
+        
+        return this._applyExtension(node, extensions, 0, actionAsync);
+    }
 
-        return null;
+    public _extensionsPreExportTextureAsync(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Promise<Nullable<BaseTexture>> {
+        return this._applyExtensions(babylonTexture, (extension, node) => extension.preExportTextureAsync && extension.preExportTextureAsync(context, node, mimeType));
     }
 
-    public _extensionsPreExportTextureAsync(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Nullable<Promise<BaseTexture>> {
-        return this._applyExtensions(babylonTexture, (extension) => extension.preExportTextureAsync && extension.preExportTextureAsync(context, babylonTexture, mimeType));
+    public _extensionsPostExportMeshPrimitiveAsync(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Promise<Nullable<IMeshPrimitive>> {
+        return this._applyExtensions(meshPrimitive, (extension, node) => extension.postExportMeshPrimitiveAsync && extension.postExportMeshPrimitiveAsync(context, node, babylonSubMesh, binaryWriter));
     }
 
-    public _extensionsPostExportMeshPrimitiveAsync(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Nullable<Promise<IMeshPrimitive>> {
-        return this._applyExtensions(meshPrimitive, (extension) => extension.postExportMeshPrimitiveAsync && extension.postExportMeshPrimitiveAsync(context, meshPrimitive, babylonSubMesh, binaryWriter));
+    public _extensionsPostExportNodeAsync(context: string, node: INode, babylonNode: Node): Promise<Nullable<INode>> {
+        return this._applyExtensions(node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode));
     }
 
-    public _extensionsPostExportNodeAsync(context: string, node: INode, babylonNode: Node): Nullable<Promise<INode>> {
-        return this._applyExtensions(node, (extension) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode));
+    public _extensionsPostExportMaterialAsync(context: string, material: IMaterial, babylonMaterial: Material): Promise<Nullable<IMaterial>> {
+        return this._applyExtensions(material, (extension, node) => extension.postExportMaterialAsync && extension.postExportMaterialAsync(context, node, babylonMaterial));
+    }
+
+    public _extensionsPostExportMaterialAdditionalTextures(context: string, material: IMaterial, babylonMaterial: Material): BaseTexture[] {
+        let output: BaseTexture[] = [];
+
+        for (const name of _Exporter._ExtensionNames) {
+            var extension = this._extensions[name];
+
+            if (extension.postExportMaterialAdditionalTextures) {
+                output.push(...extension.postExportMaterialAdditionalTextures(context, material, babylonMaterial))
+            }
+        }
+
+        return output;
+    }
+
+    public _extensionsPostExportTextures(context: string, textureInfo: ITextureInfo, babylonTexture: BaseTexture): void {
+        for (const name of _Exporter._ExtensionNames) {
+            var extension = this._extensions[name];
+
+            if (extension.postExportTexture) {
+                extension.postExportTexture(context, textureInfo, babylonTexture);
+            }
+        }
     }
 
     private _forEachExtensions(action: (extension: IGLTFExporterExtensionV2) => void): void {
@@ -233,6 +258,14 @@ export class _Exporter {
         this._glTFMaterialExporter = new _GLTFMaterialExporter(this);
         this._loadExtensions();
     }
+    
+    public dispose() {
+        for (var extensionKey in this._extensions) {
+            const extension = this._extensions[extensionKey];
+
+            extension.dispose();
+        }
+    }
 
     /**
      * Registers a glTF exporter extension
@@ -749,9 +782,10 @@ export class _Exporter {
     /**
      * Generates data for .gltf and .bin files based on the glTF prefix string
      * @param glTFPrefix Text to use when prefixing a glTF file
+     * @param dispose Dispose the exporter
      * @returns GLTFData with glTF file data
      */
-    public _generateGLTFAsync(glTFPrefix: string): Promise<GLTFData> {
+    public _generateGLTFAsync(glTFPrefix: string, dispose = true): Promise<GLTFData> {
         return this._generateBinaryAsync().then((binaryBuffer) => {
             this._extensionsOnExporting();
             const jsonText = this.generateJSON(false, glTFPrefix, true);
@@ -771,6 +805,10 @@ export class _Exporter {
                 }
             }
 
+            if (dispose) {
+                this.dispose();
+            }
+
             return container;
         });
 
@@ -803,12 +841,9 @@ export class _Exporter {
     }
 
     /**
-     * Generates a glb file from the json and binary data
-     * Returns an object with the glb file name as the key and data as the value
-     * @param glTFPrefix
-     * @returns object with glb filename as key and data as value
+     * @hidden
      */
-    public _generateGLBAsync(glTFPrefix: string): Promise<GLTFData> {
+    public _generateGLBAsync(glTFPrefix: string, dispose = true): Promise<GLTFData> {
         return this._generateBinaryAsync().then((binaryBuffer) => {
             this._extensionsOnExporting();
             const jsonText = this.generateJSON(true);
@@ -890,6 +925,10 @@ export class _Exporter {
                 this._localEngine.dispose();
             }
 
+            if (dispose) {
+                this.dispose();
+            }
+
             return container;
         });
     }
@@ -1331,6 +1370,9 @@ export class _Exporter {
                         }
                         else {
                             return promise.then((node) => {
+                                if (!node) {
+                                    return;
+                                }
                                 this._nodes.push(node);
                                 nodeIndex = this._nodes.length - 1;
                                 nodeMap[babylonNode.uniqueId] = nodeIndex;

+ 32 - 7
serializers/src/glTF/2.0/glTFExporterExtension.ts

@@ -1,13 +1,14 @@
-import { ImageMimeType, IMeshPrimitive, INode } from "babylonjs-gltf2interface";
+import { ImageMimeType, IMeshPrimitive, INode, IMaterial, ITextureInfo } from "babylonjs-gltf2interface";
 import { Node } from "babylonjs/node";
 
-import { Nullable } from "babylonjs/types";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { SubMesh } from "babylonjs/Meshes/subMesh";
 import { IDisposable } from "babylonjs/scene";
 
 import { _BinaryWriter } from "./glTFExporter";
 import { IGLTFExporterExtension } from "../glTFFileExporter";
+import { Material } from 'babylonjs/Materials/material';
+import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
 
 /** @hidden */
 export var __IGLTFExporterExtensionV2 = 0; // I am here to allow dts to be created
@@ -20,11 +21,19 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo
     /**
      * Define this method to modify the default behavior before exporting a texture
      * @param context The context when loading the asset
-     * @param babylonTexture The glTF texture info property
+     * @param babylonTexture The Babylon.js texture
      * @param mimeType The mime-type of the generated image
-     * @returns A promise that resolves with the exported glTF texture info when the export is complete, or null if not handled
+     * @returns A promise that resolves with the exported texture
      */
-    preExportTextureAsync?(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Nullable<Promise<Texture>>;
+    preExportTextureAsync?(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Promise<Texture>;
+
+    /**
+     * Define this method to get notified when a texture info is created
+     * @param context The context when loading the asset
+     * @param textureInfo The glTF texture info
+     * @param babylonTexture The Babylon.js texture
+     */
+    postExportTexture?(context: string, textureInfo:ITextureInfo, babylonTexture: BaseTexture): void;    
 
     /**
      * Define this method to modify the default behavior when exporting texture info
@@ -34,7 +43,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo
      * @param binaryWriter glTF serializer binary writer instance
      * @returns nullable IMeshPrimitive promise
      */
-    postExportMeshPrimitiveAsync?(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Nullable<Promise<IMeshPrimitive>>;
+    postExportMeshPrimitiveAsync?(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Promise<IMeshPrimitive>;
 
     /**
      * Define this method to modify the default behavior when exporting a node
@@ -43,7 +52,23 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo
      * @param babylonNode BabylonJS node
      * @returns nullable INode promise
      */
-    postExportNodeAsync?(context: string, node: INode, babylonNode: Node): Nullable<Promise<INode>>;
+    postExportNodeAsync?(context: string, node: INode, babylonNode: Node): Promise<INode>;
+
+    /**
+     * Define this method to modify the default behavior when exporting a material
+     * @param material glTF material
+     * @param babylonMaterial BabylonJS material
+     * @returns nullable IMaterial promise
+     */
+    postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise<IMaterial>;   
+
+    /**
+     * Defint this method to return additional textures to export from a material
+     * @param material glTF material
+     * @param babylonMaterial BabylonJS material
+     * @returns List of textures
+     */
+    postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[];   
 
     /**
      * Called after the exporter state changes to EXPORTING

+ 43 - 12
serializers/src/glTF/2.0/glTFMaterialExporter.ts

@@ -122,7 +122,7 @@ export class _GLTFMaterialExporter {
      * @param hasTextureCoords specifies if texture coordinates are present on the material
      */
     public _convertMaterialsToGLTFAsync(babylonMaterials: Material[], mimeType: ImageMimeType, hasTextureCoords: boolean) {
-        let promises: Promise<void>[] = [];
+        let promises: Promise<IMaterial>[] = [];
         for (let babylonMaterial of babylonMaterials) {
             if (babylonMaterial instanceof StandardMaterial) {
                 promises.push(this._convertStandardMaterialAsync(babylonMaterial, mimeType, hasTextureCoords));
@@ -290,7 +290,7 @@ export class _GLTFMaterialExporter {
      * @param imageData map of image file name to data
      * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
      */
-    public _convertStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<void> {
+    public _convertStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<IMaterial> {
         const materialMap = this._exporter._materialMap;
         const materials = this._exporter._materials;
         const promises = [];
@@ -356,12 +356,39 @@ export class _GLTFMaterialExporter {
         }
 
         glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
-        _GLTFMaterialExporter._SetAlphaMode(glTFMaterial, babylonStandardMaterial);
+        _GLTFMaterialExporter._SetAlphaMode(glTFMaterial, babylonStandardMaterial);        
 
         materials.push(glTFMaterial);
-        materialMap[babylonStandardMaterial.uniqueId] = materials.length - 1;
+        materialMap[babylonStandardMaterial.uniqueId] = materials.length - 1;        
 
-        return Promise.all(promises).then(() => { /* do nothing */ });
+        return this._finishMaterial(promises, glTFMaterial, babylonStandardMaterial, mimeType);
+    }
+
+    private _finishMaterial<T>(promises: Promise<T>[], glTFMaterial: IMaterial, babylonMaterial: Material, mimeType: ImageMimeType) {
+        return Promise.all(promises).then(() => {
+
+            const textures = this._exporter._extensionsPostExportMaterialAdditionalTextures("exportMaterial", glTFMaterial, babylonMaterial);
+            let tasks: Nullable<Promise<Nullable<ITextureInfo>>[]> = null;
+
+            for (var texture of textures) {
+                if (!tasks) {
+                    tasks = [];
+                }
+                tasks.push(this._exportTextureAsync(texture, mimeType));
+            }
+
+            if (!tasks) {
+                tasks = [Promise.resolve(null)];
+            }
+
+            return Promise.all(tasks).then(() => {
+                let extensionWork = this._exporter._extensionsPostExportMaterialAsync("exportMaterial", glTFMaterial, babylonMaterial);
+                if (!extensionWork) {
+                    return glTFMaterial;
+                }
+                return extensionWork.then(() =>glTFMaterial);
+            });
+        });
     }
 
     /**
@@ -374,7 +401,7 @@ export class _GLTFMaterialExporter {
      * @param imageData map of image file name to data
      * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
      */
-    public _convertPBRMetallicRoughnessMaterialAsync(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<void> {
+    public _convertPBRMetallicRoughnessMaterialAsync(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<IMaterial> {
         const materialMap = this._exporter._materialMap;
         const materials = this._exporter._materials;
         let promises: Promise<void>[] = [];
@@ -451,8 +478,8 @@ export class _GLTFMaterialExporter {
 
         materials.push(glTFMaterial);
         materialMap[babylonPBRMetalRoughMaterial.uniqueId] = materials.length - 1;
-
-        return Promise.all(promises).then(() => { /* do nothing */ });
+    
+        return this._finishMaterial(promises, glTFMaterial, babylonPBRMetalRoughMaterial, mimeType);
     }
 
     /**
@@ -1001,7 +1028,7 @@ export class _GLTFMaterialExporter {
      * @param imageData map of image file name to data
      * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
      */
-    public _convertPBRMaterialAsync(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<void> {
+    public _convertPBRMaterialAsync(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<IMaterial> {
         const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
         const glTFMaterial: IMaterial = {
             name: babylonPBRMaterial.name
@@ -1028,7 +1055,7 @@ export class _GLTFMaterialExporter {
         }
     }
 
-    private setMetallicRoughnessPbrMaterial(metallicRoughness: Nullable<_IPBRMetallicRoughness>, babylonPBRMaterial: PBRMaterial, glTFMaterial: IMaterial, glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<void> {
+    private setMetallicRoughnessPbrMaterial(metallicRoughness: Nullable<_IPBRMetallicRoughness>, babylonPBRMaterial: PBRMaterial, glTFMaterial: IMaterial, glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise<IMaterial> {
         const materialMap = this._exporter._materialMap;
         const materials = this._exporter._materials;
         let promises = [];
@@ -1075,7 +1102,8 @@ export class _GLTFMaterialExporter {
                     let promise = this._exportTextureAsync(babylonPBRMaterial.ambientTexture, mimeType).then((glTFTexture) => {
                         if (glTFTexture) {
                             let occlusionTexture: IMaterialOcclusionTextureInfo = {
-                                index: glTFTexture.index
+                                index: glTFTexture.index,
+                                texCoord: glTFTexture.texCoord
                             };
 
                             glTFMaterial.occlusionTexture = occlusionTexture;
@@ -1105,7 +1133,8 @@ export class _GLTFMaterialExporter {
             materials.push(glTFMaterial);
             materialMap[babylonPBRMaterial.uniqueId] = materials.length - 1;
         }
-        return Promise.all(promises).then((result) => { /* do nothing */ });
+
+        return this._finishMaterial(promises, glTFMaterial, babylonPBRMaterial, mimeType);
     }
 
     private getPixelsFromTexture(babylonTexture: BaseTexture): Uint8Array | Float32Array {
@@ -1168,7 +1197,9 @@ export class _GLTFMaterialExporter {
                     const textureInfo = this._getTextureInfoFromBase64(base64Data, babylonTexture.name.replace(/\.\/|\/|\.\\|\\/g, "_"), mimeType, babylonTexture.coordinatesIndex, samplerIndex);
                     if (textureInfo) {
                         this._textureMap[textureUid] = textureInfo;
+                        this._exporter._extensionsPostExportTextures("linkTextureInfo", textureInfo, babylonTexture);
                     }
+
                     return textureInfo;
                 });
             }