import { ITextureInfo , ImageMimeType , IMaterial , IMaterialPbrMetallicRoughness , MaterialAlphaMode , IMaterialOcclusionTextureInfo , ISampler , TextureMagFilter , TextureMinFilter , TextureWrapMode , ITexture , IImage } from "babylonjs-gltf2interface" ; import { Nullable } from "babylonjs/types" ; import { Vector2 } from "babylonjs/Maths/math.vector" ; import { Color3 } from "babylonjs/Maths/math.color" ; import { Scalar } from "babylonjs/Maths/math.scalar" ; import { Tools } from "babylonjs/Misc/tools" ; import { TextureTools } from "babylonjs/Misc/textureTools" ; import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture" ; import { Texture } from "babylonjs/Materials/Textures/texture" ; import { RawTexture } from "babylonjs/Materials/Textures/rawTexture" ; import { IMaterialExtension } from "babylonjs-gltf2interface" ; import { Scene } from "babylonjs/scene" ; import { _Exporter } from "./glTFExporter" ; import { Constants } from 'babylonjs/Engines/constants' ; declare type Material = import ( "babylonjs/Materials/material" ) . Material ; declare type StandardMaterial = import ( "babylonjs/Materials/standardMaterial" ) . StandardMaterial ; declare type PBRBaseMaterial = import ( "babylonjs/Materials/PBR/pbrBaseMaterial" ) . PBRBaseMaterial ; declare type PBRMaterial = import ( "babylonjs/Materials/PBR/pbrMaterial" ) . PBRMaterial ; declare type PBRBaseSimpleMaterial = import ( "babylonjs/Materials/PBR/pbrBaseSimpleMaterial" ) . PBRBaseSimpleMaterial ; declare type PBRMetallicRoughnessMaterial = import ( "babylonjs/Materials/PBR/pbrMetallicRoughnessMaterial" ) . PBRMetallicRoughnessMaterial ; declare type PBRSpecularGlossinessMaterial = import ( "babylonjs/Materials/PBR/pbrSpecularGlossinessMaterial" ) . PBRSpecularGlossinessMaterial ; hidden interface _IPBRSpecularGlossiness { /**
     * Represents the linear diffuse factors of the material
    */ diffuseColor : Color3 ; /**
     * Represents the linear specular factors of the material
    */ specularColor : Color3 ; /**
     * Represents the smoothness of the material
    */ glossiness : number ; } hidden interface _IPBRMetallicRoughness { /**
     * Represents the albedo color of the material
    */ baseColor : Color3 ; /**
     * Represents the metallness of the material
    */ metallic : Nullable < number > ; /**
     * Represents the roughness of the material
    */ roughness : Nullable < number > ; /**
     * The metallic roughness texture as a base64 string
    */ metallicRoughnessTextureBase64 ? : Nullable < string > ; /**
     * The base color texture as a base64 string
    */ baseColorTextureBase64 ? : Nullable < string > ; } hidden export class _GLTFMaterialExporter { /**
     * Represents the dielectric specular values for R, G and B
     */ private static readonly _DielectricSpecular : Color3 = new Color3 ( 0.04 , 0.04 , 0.04 ) ; /**
     * Allows the maximum specular power to be defined for material calculations
     */ private static readonly _MaxSpecularPower = 1024 ; /**
     * Mapping to store textures
     */ private _textureMap : { [ textureId ] : ITextureInfo ; } = { } ; /**
     * Numeric tolerance value
     */ private static readonly _Epsilon = 1e-6 ; /**
     * Reference to the glTF Exporter
     */ private _exporter : _Exporter ; constructor ( exporter ) { this . _textureMap = { } ; this . _exporter = exporter ; } param color1 param color2 param epsilon private static FuzzyEquals ( color1 , color2 , epsilon ) { return Scalar . WithinEpsilon ( color1 . r , color2 . r , epsilon ) && Scalar . WithinEpsilon ( color1 . g , color2 . g , epsilon ) && Scalar . WithinEpsilon ( color1 . b , color2 . b , epsilon ) ; } param scene param mimeType param images param textures param materials param imageData param hasTextureCoords public _convertMaterialsToGLTFAsync ( exportMaterials , mimeType , hasTextureCoords ) { let promises = [ ] ; exportMaterials . forEach ( ( material ) => { if ( material . getClassName ( ) === "StandardMaterial" ) { promises . push ( this . _convertStandardMaterialAsync ( material as StandardMaterial , mimeType , hasTextureCoords ) ) ; } else if ( material . getClassName ( ) . indexOf ( "PBR" ) !== - 1 ) { promises . push ( this . _convertPBRMaterialAsync ( material as PBRMaterial , mimeType , hasTextureCoords ) ) ; } else { Tools . Warn ( `Unsupported material type: ${ material . name }` ) ; } } ) ; return Promise . all ( promises ) . then ( ( ) => { } ) ; } param originalMaterial returns public _stripTexturesFromMaterial ( originalMaterial ) { let newMaterial = { } ; if ( originalMaterial ) { newMaterial . name = originalMaterial . name ; newMaterial . doubleSided = originalMaterial . doubleSided ; newMaterial . alphaMode = originalMaterial . alphaMode ; newMaterial . alphaCutoff = originalMaterial . alphaCutoff ; newMaterial . emissiveFactor = originalMaterial . emissiveFactor ; const originalPBRMetallicRoughness = originalMaterial . pbrMetallicRoughness ; if ( originalPBRMetallicRoughness ) { newMaterial . pbrMetallicRoughness = { } ; newMaterial . pbrMetallicRoughness . baseColorFactor = originalPBRMetallicRoughness . baseColorFactor ; newMaterial . pbrMetallicRoughness . metallicFactor = originalPBRMetallicRoughness . metallicFactor ; newMaterial . pbrMetallicRoughness . roughnessFactor = originalPBRMetallicRoughness . roughnessFactor ; } } return newMaterial ; } param material returns public _hasTexturesPresent ( material ) { if ( material . emissiveTexture || material . normalTexture || material . occlusionTexture ) { return true ; } const pbrMat = material . pbrMetallicRoughness ; if ( pbrMat ) { if ( pbrMat . baseColorTexture || pbrMat . metallicRoughnessTexture ) { return true ; } } if ( material . extensions ) { for ( let extension in material . extensions ) { let extensionObject = material . extensions [ extension ] ; if ( extensionObject as IMaterialExtension ) { return extensionObject . hasTextures ?. ( ) ; } } } return false ; } public _getTextureInfo ( babylonTexture ) { if ( babylonTexture ) { const textureUid = babylonTexture . uid ; if ( textureUid in this . _textureMap ) { return this . _textureMap [ textureUid ] ; } } return null ; } param babylonStandardMaterial returns public _convertToGLTFPBRMetallicRoughness ( babylonStandardMaterial ) { const P0 = new Vector2 ( 0 , 1 ) ; const P1 = new Vector2 ( 0 , 0.1 ) ; const P2 = new Vector2 ( 0 , 0.1 ) ; const P3 = new Vector2 ( 1300 , 0.1 ) ; param t param p0 param p1 param p2 param p3 returns function _cubicBezierCurve ( t , p0 , p1 , p2 , p3 ) { return ( ( 1 - t ) * ( 1 - t ) * ( 1 - t ) * p0 + 3 * ( 1 - t ) * ( 1 - t ) * t * p1 + 3 * ( 1 - t ) * t * t * p2 + t * t * t * p3 ) ; } param specularPower returns function _solveForRoughness ( specularPower ) { var t = Math . pow ( specularPower / P3 . x , 0.333333 ) ; return _cubicBezierCurve ( t , P0 . y , P1 . y , P2 . y , P3 . y ) ; } let diffuse = babylonStandardMaterial . diffuseColor . toLinearSpace ( ) . scale ( 0.5 ) ; let opacity = babylonStandardMaterial . alpha ; let specularPower = Scalar . Clamp ( babylonStandardMaterial . specularPower , 0 , _GLTFMaterialExporter . _MaxSpecularPower ) ; const roughness = _solveForRoughness ( specularPower ) ; const glTFPbrMetallicRoughness = { baseColorFactor : [ diffuse . r , diffuse . g , diffuse . b , opacity ] , metallicFactor : 0 , roughnessFactor : roughness , } ; return glTFPbrMetallicRoughness ; } param diffuse param specular param oneMinusSpecularStrength returns public static _SolveMetallic ( diffuse , specular , oneMinusSpecularStrength ) { if ( specular < this . _DielectricSpecular . r ) { this . _DielectricSpecular ; return 0 ; } const a = this . _DielectricSpecular . r ; const b = diffuse * oneMinusSpecularStrength / ( 1.0 - this . _DielectricSpecular . r ) + specular - 2.0 * this . _DielectricSpecular . r ; const c = this . _DielectricSpecular . r - specular ; const D = b * b - 4.0 * a * c ; return Scalar . Clamp ( ( - b + Math . sqrt ( D ) ) / ( 2.0 * a ) , 0 , 1 ) ; } param glTFMaterial param babylonMaterial private static _SetAlphaMode ( glTFMaterial , babylonMaterial ) { if ( babylonMaterial . needAlphaBlending ( ) ) { glTFMaterial . alphaMode = MaterialAlphaMode . BLEND ; } else if ( babylonMaterial . needAlphaTesting ( ) ) { glTFMaterial . alphaMode = MaterialAlphaMode . MASK ; glTFMaterial . alphaCutoff = babylonMaterial . alphaCutOff ; } } param babylonStandardMaterial param mimeType param images param textures param materials param imageData param hasTextureCoords public _convertStandardMaterialAsync ( babylonStandardMaterial , mimeType , hasTextureCoords ) { const materialMap = this . _exporter . _materialMap ; const materials = this . _exporter . _materials ; const promises = [ ] ; const glTFPbrMetallicRoughness = this . _convertToGLTFPBRMetallicRoughness ( babylonStandardMaterial ) ; const glTFMaterial = { name : babylonStandardMaterial . name } ; if ( babylonStandardMaterial . backFaceCulling != null && ! babylonStandardMaterial . backFaceCulling ) { if ( ! babylonStandardMaterial . twoSidedLighting ) { Tools . Warn ( babylonStandardMaterial . name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF." ) ; } glTFMaterial . doubleSided = true ; } if ( hasTextureCoords ) { if ( babylonStandardMaterial . diffuseTexture ) { promises . push ( this . _exportTextureAsync ( babylonStandardMaterial . diffuseTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFPbrMetallicRoughness . baseColorTexture = glTFTexture ; } } ) ) ; } if ( babylonStandardMaterial . bumpTexture ) { promises . push ( this . _exportTextureAsync ( babylonStandardMaterial . bumpTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFMaterial . normalTexture = glTFTexture ; if ( babylonStandardMaterial . bumpTexture != null && babylonStandardMaterial . bumpTexture . level !== 1 ) { glTFMaterial . normalTexture . scale = babylonStandardMaterial . bumpTexture . level ; } } } ) ) ; } if ( babylonStandardMaterial . emissiveTexture ) { glTFMaterial . emissiveFactor = [ 1.0 , 1.0 , 1.0 ] ; promises . push ( this . _exportTextureAsync ( babylonStandardMaterial . emissiveTexture , mimeType ) . then ( ( glTFEmissiveTexture ) => { if ( glTFEmissiveTexture ) { glTFMaterial . emissiveTexture = glTFEmissiveTexture ; } } ) ) ; } if ( babylonStandardMaterial . ambientTexture ) { promises . push ( this . _exportTextureAsync ( babylonStandardMaterial . ambientTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { const occlusionTexture = { index : glTFTexture . index } ; glTFMaterial . occlusionTexture = occlusionTexture ; occlusionTexture . strength = 1.0 ; } } ) ) ; } } if ( babylonStandardMaterial . alpha < 1.0 || babylonStandardMaterial . opacityTexture ) { if ( babylonStandardMaterial . alphaMode === Constants . ALPHA_COMBINE ) { glTFMaterial . alphaMode = MaterialAlphaMode . BLEND ; } else { Tools . Warn ( babylonStandardMaterial . name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial . alphaMode . toString ( ) ) ; } } if ( babylonStandardMaterial . emissiveColor && ! _GLTFMaterialExporter . FuzzyEquals ( babylonStandardMaterial . emissiveColor , Color3 . Black ( ) , _GLTFMaterialExporter . _Epsilon ) ) { glTFMaterial . emissiveFactor = babylonStandardMaterial . emissiveColor . asArray ( ) ; } glTFMaterial . pbrMetallicRoughness = glTFPbrMetallicRoughness ; _GLTFMaterialExporter . _SetAlphaMode ( glTFMaterial , babylonStandardMaterial ) ; materials . push ( glTFMaterial ) ; materialMap [ babylonStandardMaterial . uniqueId ] = materials . length - 1 ; return this . _finishMaterial ( promises , glTFMaterial , babylonStandardMaterial , mimeType ) ; } private _finishMaterial < T > ( promises , glTFMaterial , babylonMaterial , mimeType ) { return Promise . all ( promises ) . then ( ( ) => { const textures = this . _exporter . _extensionsPostExportMaterialAdditionalTextures ( "exportMaterial" , glTFMaterial , babylonMaterial ) ; let tasks = 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 ) ; } ) ; } ) ; } param babylonPBRMetalRoughMaterial param mimeType param images param textures param materials param imageData param hasTextureCoords public _convertPBRMetallicRoughnessMaterialAsync ( babylonPBRMetalRoughMaterial , mimeType , hasTextureCoords ) { const materialMap = this . _exporter . _materialMap ; const materials = this . _exporter . _materials ; let promises = [ ] ; const glTFPbrMetallicRoughness = { } ; if ( babylonPBRMetalRoughMaterial . baseColor ) { glTFPbrMetallicRoughness . baseColorFactor = [ babylonPBRMetalRoughMaterial . baseColor . r , babylonPBRMetalRoughMaterial . baseColor . g , babylonPBRMetalRoughMaterial . baseColor . b , babylonPBRMetalRoughMaterial . alpha ] ; } if ( babylonPBRMetalRoughMaterial . metallic != null && babylonPBRMetalRoughMaterial . metallic !== 1 ) { glTFPbrMetallicRoughness . metallicFactor = babylonPBRMetalRoughMaterial . metallic ; } if ( babylonPBRMetalRoughMaterial . roughness != null && babylonPBRMetalRoughMaterial . roughness !== 1 ) { glTFPbrMetallicRoughness . roughnessFactor = babylonPBRMetalRoughMaterial . roughness ; } const glTFMaterial = { name : babylonPBRMetalRoughMaterial . name } ; if ( babylonPBRMetalRoughMaterial . doubleSided ) { glTFMaterial . doubleSided = babylonPBRMetalRoughMaterial . doubleSided ; } _GLTFMaterialExporter . _SetAlphaMode ( glTFMaterial , babylonPBRMetalRoughMaterial ) ; if ( hasTextureCoords ) { if ( babylonPBRMetalRoughMaterial . baseTexture != null ) { promises . push ( this . _exportTextureAsync ( babylonPBRMetalRoughMaterial . baseTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFPbrMetallicRoughness . baseColorTexture = glTFTexture ; } } ) ) ; } if ( babylonPBRMetalRoughMaterial . normalTexture ) { promises . push ( this . _exportTextureAsync ( babylonPBRMetalRoughMaterial . normalTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFMaterial . normalTexture = glTFTexture ; if ( babylonPBRMetalRoughMaterial . normalTexture . level !== 1 ) { glTFMaterial . normalTexture . scale = babylonPBRMetalRoughMaterial . normalTexture . level ; } } } ) ) ; } if ( babylonPBRMetalRoughMaterial . occlusionTexture ) { promises . push ( this . _exportTextureAsync ( babylonPBRMetalRoughMaterial . occlusionTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFMaterial . occlusionTexture = glTFTexture ; if ( babylonPBRMetalRoughMaterial . occlusionStrength != null ) { glTFMaterial . occlusionTexture . strength = babylonPBRMetalRoughMaterial . occlusionStrength ; } } } ) ) ; } if ( babylonPBRMetalRoughMaterial . emissiveTexture ) { promises . push ( this . _exportTextureAsync ( babylonPBRMetalRoughMaterial . emissiveTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFMaterial . emissiveTexture = glTFTexture ; } } ) ) ; } } if ( _GLTFMaterialExporter . FuzzyEquals ( babylonPBRMetalRoughMaterial . emissiveColor , Color3 . Black ( ) , _GLTFMaterialExporter . _Epsilon ) ) { glTFMaterial . emissiveFactor = babylonPBRMetalRoughMaterial . emissiveColor . asArray ( ) ; } glTFMaterial . pbrMetallicRoughness = glTFPbrMetallicRoughness ; materials . push ( glTFMaterial ) ; materialMap [ babylonPBRMetalRoughMaterial . uniqueId ] = materials . length - 1 ; return this . _finishMaterial ( promises , glTFMaterial , babylonPBRMetalRoughMaterial , mimeType ) ; } param buffer param width param height param mimeType returns private _createBase64FromCanvasAsync ( buffer , width , height , mimeType ) { return new Promise < string > ( async ( resolve , reject ) => { const textureType = Constants . TEXTURETYPE_UNSIGNED_INT ; const hostingScene = this . _exporter . _babylonScene ; const engine = hostingScene . getEngine ( ) ; const tempTexture = engine . createRawTexture ( buffer , width , height , Constants . TEXTUREFORMAT_RGBA , false , true , Texture . NEAREST_SAMPLINGMODE , null , textureType ) ; await TextureTools . ApplyPostProcess ( "pass" , tempTexture , hostingScene , textureType , Constants . TEXTURE_NEAREST_SAMPLINGMODE , Constants . TEXTUREFORMAT_RGBA ) ; const data = await engine . _readTexturePixels ( tempTexture , width , height ) ; const base64 = await ( Tools . DumpDataAsync ( width , height , data , mimeType , undefined , true , false ) as Promise < string > ) ; resolve ( base64 ) ; } ) ; } param width param height param scene returns private _createWhiteTexture ( width , height , scene ) { const data = new Uint8Array ( width * height * 4 ) ; for ( let i = 0 ; i < data . length ; i = i + 4 ) { data [ i ] = data [ i + 1 ] = data [ i + 2 ] = data [ i + 3 ] = 0xFF ; } const rawTexture = RawTexture . CreateRGBATexture ( data , width , height , scene ) ; return rawTexture ; } param texture1 param texture2 param scene returns private _resizeTexturesToSameDimensions ( texture1 , texture2 , scene ) { let texture1Size = texture1 ? texture1 . getSize ( ) : { width : 0 , height : 0 } ; let texture2Size = texture2 ? texture2 . getSize ( ) : { width : 0 , height : 0 } ; let resizedTexture1 ; let resizedTexture2 ; if ( texture1Size . width < texture2Size . width ) { if ( texture1 && texture1 instanceof Texture ) { resizedTexture1 = TextureTools . CreateResizedCopy ( texture1 , texture2Size . width , texture2Size . height , true ) ; } else { resizedTexture1 = this . _createWhiteTexture ( texture2Size . width , texture2Size . height , scene ) ; } resizedTexture2 = texture2 ! ; } else if ( texture1Size . width > texture2Size . width ) { if ( texture2 && texture2 instanceof Texture ) { resizedTexture2 = TextureTools . CreateResizedCopy ( texture2 , texture1Size . width , texture1Size . height , true ) ; } else { resizedTexture2 = this . _createWhiteTexture ( texture1Size . width , texture1Size . height , scene ) ; } resizedTexture1 = texture1 ! ; } else { resizedTexture1 = texture1 ! ; resizedTexture2 = texture2 ! ; } return { "texture1" : resizedTexture1 ! , "texture2" : resizedTexture2 ! } ; } param pixels returns private _convertPixelArrayToFloat32 ( pixels ) { if ( pixels instanceof Uint8Array ) { const length = pixels . length ; const buffer = new Float32Array ( pixels . length ) ; for ( let i = 0 ; i < length ; ++ i ) { buffer [ i ] = pixels [ i ] / 255 ; } return buffer ; } else if ( pixels instanceof Float32Array ) { return pixels ; } else { throw new Error ( 'Unsupported pixel format!' ) ; } } link param diffuseTexture param specularGlossinessTexture param factors param mimeType returns private async _convertSpecularGlossinessTexturesToMetallicRoughnessAsync ( diffuseTexture , specularGlossinessTexture , factors , mimeType ) { let promises = [ ] ; if ( ! ( diffuseTexture || specularGlossinessTexture ) ) { return Promise . reject ( '_ConvertSpecularGlosinessTexturesToMetallicRoughness: diffuse and specular glossiness textures are not defined!' ) ; } const scene = diffuseTexture ? diffuseTexture . getScene ( ) : specularGlossinessTexture ? specularGlossinessTexture . getScene ( ) : null ; if ( scene ) { const resizedTextures = this . _resizeTexturesToSameDimensions ( diffuseTexture , specularGlossinessTexture , scene ) ; let diffuseSize = resizedTextures . texture1 ?. getSize ( ) ; let diffuseBuffer ; let specularGlossinessBuffer ; const width = diffuseSize . width ; const height = diffuseSize . height ; let diffusePixels = await resizedTextures . texture1 . readPixels ( ) ; let specularPixels = await resizedTextures . texture2 . readPixels ( ) ; if ( diffusePixels ) { diffuseBuffer = this . _convertPixelArrayToFloat32 ( diffusePixels ) ; } else { return Promise . reject ( "Failed to retrieve pixels from diffuse texture!" ) ; } if ( specularPixels ) { specularGlossinessBuffer = this . _convertPixelArrayToFloat32 ( specularPixels ) ; } else { return Promise . reject ( "Failed to retrieve pixels from specular glossiness texture!" ) ; } const byteLength = specularGlossinessBuffer . byteLength ; const metallicRoughnessBuffer = new Uint8Array ( byteLength ) ; const baseColorBuffer = new Uint8Array ( byteLength ) ; const strideSize = 4 ; const maxBaseColor = Color3 . Black ( ) ; let maxMetallic = 0 ; let maxRoughness = 0 ; for ( let h = 0 ; h < height ; ++ h ) { for ( let w = 0 ; w < width ; ++ w ) { const offset = ( width * h + w ) * strideSize ; const diffuseColor = new Color3 ( diffuseBuffer [ offset ] , diffuseBuffer [ offset + 1 ] , diffuseBuffer [ offset + 2 ] ) . toLinearSpace ( ) . multiply ( factors . diffuseColor ) ; const specularColor = new Color3 ( specularGlossinessBuffer [ offset ] , specularGlossinessBuffer [ offset + 1 ] , specularGlossinessBuffer [ offset + 2 ] ) . toLinearSpace ( ) . multiply ( factors . specularColor ) ; const glossiness = ( specularGlossinessBuffer [ offset + 3 ] ) * factors . glossiness ; const specularGlossiness = { diffuseColor : diffuseColor , specularColor : specularColor , glossiness : glossiness } ; const metallicRoughness = this . _convertSpecularGlossinessToMetallicRoughness ( specularGlossiness ) ; maxBaseColor . r = Math . max ( maxBaseColor . r , metallicRoughness . baseColor . r ) ; maxBaseColor . g = Math . max ( maxBaseColor . g , metallicRoughness . baseColor . g ) ; maxBaseColor . b = Math . max ( maxBaseColor . b , metallicRoughness . baseColor . b ) ; maxMetallic = Math . max ( maxMetallic , metallicRoughness . metallic ! ) ; maxRoughness = Math . max ( maxRoughness , metallicRoughness . roughness ! ) ; baseColorBuffer [ offset ] = metallicRoughness . baseColor . r * 255 ; baseColorBuffer [ offset + 1 ] = metallicRoughness . baseColor . g * 255 ; baseColorBuffer [ offset + 2 ] = metallicRoughness . baseColor . b * 255 ; baseColorBuffer [ offset + 3 ] = resizedTextures . texture1 . hasAlpha ? diffuseBuffer [ offset + 3 ] * 255 : 255 ; metallicRoughnessBuffer [ offset ] = 0 ; metallicRoughnessBuffer [ offset + 1 ] = metallicRoughness . roughness ! * 255 ; metallicRoughnessBuffer [ offset + 2 ] = metallicRoughness . metallic ! * 255 ; metallicRoughnessBuffer [ offset + 3 ] = 255 ; } } const metallicRoughnessFactors = { baseColor : maxBaseColor , metallic : maxMetallic , roughness : maxRoughness } ; let writeOutMetallicRoughnessTexture = false ; let writeOutBaseColorTexture = false ; for ( let h = 0 ; h < height ; ++ h ) { for ( let w = 0 ; w < width ; ++ w ) { const destinationOffset = ( width * h + w ) * strideSize ; baseColorBuffer [ destinationOffset ] /= metallicRoughnessFactors . baseColor . r > _GLTFMaterialExporter . _Epsilon ? metallicRoughnessFactors . baseColor . r : 1 ; baseColorBuffer [ destinationOffset + 1 ] /= metallicRoughnessFactors . baseColor . g > _GLTFMaterialExporter . _Epsilon ? metallicRoughnessFactors . baseColor . g : 1 ; baseColorBuffer [ destinationOffset + 2 ] /= metallicRoughnessFactors . baseColor . b > _GLTFMaterialExporter . _Epsilon ? metallicRoughnessFactors . baseColor . b : 1 ; const linearBaseColorPixel = Color3 . FromInts ( baseColorBuffer [ destinationOffset ] , baseColorBuffer [ destinationOffset + 1 ] , baseColorBuffer [ destinationOffset + 2 ] ) ; const sRGBBaseColorPixel = linearBaseColorPixel . toGammaSpace ( ) ; baseColorBuffer [ destinationOffset ] = sRGBBaseColorPixel . r * 255 ; baseColorBuffer [ destinationOffset + 1 ] = sRGBBaseColorPixel . g * 255 ; baseColorBuffer [ destinationOffset + 2 ] = sRGBBaseColorPixel . b * 255 ; if ( ! _GLTFMaterialExporter . FuzzyEquals ( sRGBBaseColorPixel , Color3 . White ( ) , _GLTFMaterialExporter . _Epsilon ) ) { writeOutBaseColorTexture = true ; } metallicRoughnessBuffer [ destinationOffset + 1 ] /= metallicRoughnessFactors . roughness ! > _GLTFMaterialExporter . _Epsilon ? metallicRoughnessFactors . roughness ! : 1 ; metallicRoughnessBuffer [ destinationOffset + 2 ] /= metallicRoughnessFactors . metallic ! > _GLTFMaterialExporter . _Epsilon ? metallicRoughnessFactors . metallic ! : 1 ; const metallicRoughnessPixel = Color3 . FromInts ( 255 , metallicRoughnessBuffer [ destinationOffset + 1 ] , metallicRoughnessBuffer [ destinationOffset + 2 ] ) ; if ( ! _GLTFMaterialExporter . FuzzyEquals ( metallicRoughnessPixel , Color3 . White ( ) , _GLTFMaterialExporter . _Epsilon ) ) { writeOutMetallicRoughnessTexture = true ; } } } if ( writeOutMetallicRoughnessTexture ) { let promise = this . _createBase64FromCanvasAsync ( metallicRoughnessBuffer , width , height , mimeType ) . then ( ( metallicRoughnessBase64 ) => { metallicRoughnessFactors . metallicRoughnessTextureBase64 = metallicRoughnessBase64 ; } ) ; promises . push ( promise ) ; } if ( writeOutBaseColorTexture ) { let promise = this . _createBase64FromCanvasAsync ( baseColorBuffer , width , height , mimeType ) . then ( ( baseColorBase64 ) => { metallicRoughnessFactors . baseColorTextureBase64 = baseColorBase64 ; } ) ; promises . push ( promise ) ; } return Promise . all ( promises ) . then ( ( ) => { return metallicRoughnessFactors ; } ) ; } else { return Promise . reject ( "_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!" ) ; } } param specularGlossiness returns private _convertSpecularGlossinessToMetallicRoughness ( specularGlossiness ) { const diffusePerceivedBrightness = this . _getPerceivedBrightness ( specularGlossiness . diffuseColor ) ; const specularPerceivedBrightness = this . _getPerceivedBrightness ( specularGlossiness . specularColor ) ; const oneMinusSpecularStrength = 1 - this . _getMaxComponent ( specularGlossiness . specularColor ) ; const metallic = _GLTFMaterialExporter . _SolveMetallic ( diffusePerceivedBrightness , specularPerceivedBrightness , oneMinusSpecularStrength ) ; const baseColorFromDiffuse = specularGlossiness . diffuseColor . scale ( oneMinusSpecularStrength / ( 1.0 - _GLTFMaterialExporter . _DielectricSpecular . r ) / Math . max ( 1 - metallic , _GLTFMaterialExporter . _Epsilon ) ) ; const baseColorFromSpecular = specularGlossiness . specularColor . subtract ( _GLTFMaterialExporter . _DielectricSpecular . scale ( 1 - metallic ) ) . scale ( 1 / Math . max ( metallic , _GLTFMaterialExporter . _Epsilon ) ) ; let baseColor = Color3 . Lerp ( baseColorFromDiffuse , baseColorFromSpecular , metallic * metallic ) ; baseColor = baseColor . clampToRef ( 0 , 1 , baseColor ) ; const metallicRoughness = { baseColor : baseColor , metallic : metallic , roughness : 1 - specularGlossiness . glossiness } ; return metallicRoughness ; } param color returns private _getPerceivedBrightness ( color ) { if ( color ) { return Math . sqrt ( 0.299 * color . r * color . r + 0.587 * color . g * color . g + 0.114 * color . b * color . b ) ; } return 0 ; } param color returns private _getMaxComponent ( color ) { if ( color ) { return Math . max ( color . r , Math . max ( color . g , color . b ) ) ; } return 0 ; } param babylonPBRMaterial param mimeType param images param textures param glTFPbrMetallicRoughness param imageData param hasTextureCoords returns private _convertMetalRoughFactorsToMetallicRoughnessAsync ( babylonPBRMaterial , mimeType , glTFPbrMetallicRoughness , hasTextureCoords ) { const promises = [ ] ; const baseColor = babylonPBRMaterial . _albedoColor ; const metallic = babylonPBRMaterial . _metallic ; const roughness = babylonPBRMaterial . _roughness ; const metallicRoughness = { baseColor : baseColor , metallic : metallic , roughness : roughness } ; if ( hasTextureCoords ) { const albedoTexture = babylonPBRMaterial . _albedoTexture ; if ( albedoTexture ) { promises . push ( this . _exportTextureAsync ( babylonPBRMaterial . _albedoTexture ! , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFPbrMetallicRoughness . baseColorTexture = glTFTexture ; } } ) ) ; } const metallicTexture = babylonPBRMaterial . _metallicTexture ; if ( metallicTexture ) { promises . push ( this . _exportTextureAsync ( metallicTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFPbrMetallicRoughness . metallicRoughnessTexture = glTFTexture ; } } ) ) ; } } return Promise . all ( promises ) . then ( ( ) => { return metallicRoughness ; } ) ; } private _getGLTFTextureSampler ( texture ) { const sampler = this . _getGLTFTextureWrapModesSampler ( texture ) ; let samplingMode = texture instanceof Texture ? texture . samplingMode : null ; if ( samplingMode != null ) { switch ( samplingMode ) { case Texture . LINEAR_LINEAR : { sampler . magFilter = TextureMagFilter . LINEAR ; sampler . minFilter = TextureMinFilter . LINEAR ; break ; } case Texture . LINEAR_NEAREST : { sampler . magFilter = TextureMagFilter . LINEAR ; sampler . minFilter = TextureMinFilter . NEAREST ; break ; } case Texture . NEAREST_LINEAR : { sampler . magFilter = TextureMagFilter . NEAREST ; sampler . minFilter = TextureMinFilter . LINEAR ; break ; } case Texture . NEAREST_LINEAR_MIPLINEAR : { sampler . magFilter = TextureMagFilter . NEAREST ; sampler . minFilter = TextureMinFilter . LINEAR_MIPMAP_LINEAR ; break ; } case Texture . NEAREST_NEAREST : { sampler . magFilter = TextureMagFilter . NEAREST ; sampler . minFilter = TextureMinFilter . NEAREST ; break ; } case Texture . NEAREST_LINEAR_MIPNEAREST : { sampler . magFilter = TextureMagFilter . NEAREST ; sampler . minFilter = TextureMinFilter . LINEAR_MIPMAP_NEAREST ; break ; } case Texture . LINEAR_NEAREST_MIPNEAREST : { sampler . magFilter = TextureMagFilter . LINEAR ; sampler . minFilter = TextureMinFilter . NEAREST_MIPMAP_NEAREST ; break ; } case Texture . LINEAR_NEAREST_MIPLINEAR : { sampler . magFilter = TextureMagFilter . LINEAR ; sampler . minFilter = TextureMinFilter . NEAREST_MIPMAP_LINEAR ; break ; } case Texture . NEAREST_NEAREST_MIPLINEAR : { sampler . magFilter = TextureMagFilter . NEAREST ; sampler . minFilter = TextureMinFilter . NEAREST_MIPMAP_LINEAR ; break ; } case Texture . LINEAR_LINEAR_MIPLINEAR : { sampler . magFilter = TextureMagFilter . LINEAR ; sampler . minFilter = TextureMinFilter . LINEAR_MIPMAP_LINEAR ; break ; } case Texture . LINEAR_LINEAR_MIPNEAREST : { sampler . magFilter = TextureMagFilter . LINEAR ; sampler . minFilter = TextureMinFilter . LINEAR_MIPMAP_NEAREST ; break ; } case Texture . NEAREST_NEAREST_MIPNEAREST : { sampler . magFilter = TextureMagFilter . NEAREST ; sampler . minFilter = TextureMinFilter . NEAREST_MIPMAP_NEAREST ; break ; } } } return sampler ; } private _getGLTFTextureWrapMode ( wrapMode ) { switch ( wrapMode ) { case Texture . WRAP_ADDRESSMODE : { return TextureWrapMode . REPEAT ; } case Texture . CLAMP_ADDRESSMODE : { return TextureWrapMode . CLAMP_TO_EDGE ; } case Texture . MIRROR_ADDRESSMODE : { return TextureWrapMode . MIRRORED_REPEAT ; } default : { Tools . Error ( `Unsupported Texture Wrap Mode ${ wrapMode }!` ) ; return TextureWrapMode . REPEAT ; } } } private _getGLTFTextureWrapModesSampler ( texture ) { let wrapS = this . _getGLTFTextureWrapMode ( texture instanceof Texture ? texture . wrapU : Texture . WRAP_ADDRESSMODE ) ; let wrapT = this . _getGLTFTextureWrapMode ( texture instanceof Texture ? texture . wrapV : Texture . WRAP_ADDRESSMODE ) ; if ( wrapS === TextureWrapMode . REPEAT && wrapT === TextureWrapMode . REPEAT ) { return { } ; } return { wrapS : wrapS , wrapT : wrapT } ; } param babylonPBRMaterial param mimeType param images param textures param glTFPbrMetallicRoughness param imageData param hasTextureCoords returns private _convertSpecGlossFactorsToMetallicRoughnessAsync ( babylonPBRMaterial , mimeType , glTFPbrMetallicRoughness , hasTextureCoords ) { return Promise . resolve ( ) . then ( ( ) => { const samplers = this . _exporter . _samplers ; const textures = this . _exporter . _textures ; const diffuseColor = babylonPBRMaterial . _albedoColor ; const specularColor = babylonPBRMaterial . _reflectivityColor ; const glossiness = babylonPBRMaterial . _microSurface ; const specGloss = { diffuseColor : diffuseColor , specularColor : specularColor , glossiness : glossiness , } ; let samplerIndex = null ; const albedoTexture = babylonPBRMaterial . _albedoTexture ; const reflectivityTexture = babylonPBRMaterial . _reflectivityTexture ; if ( albedoTexture ) { const sampler = this . _getGLTFTextureSampler ( albedoTexture ) ; if ( sampler . magFilter != null && sampler . minFilter != null && sampler . wrapS != null && sampler . wrapT != null ) { samplers . push ( sampler ) ; samplerIndex = samplers . length - 1 ; } } const useMicrosurfaceFromReflectivityMapAlpha = babylonPBRMaterial . _useMicroSurfaceFromReflectivityMapAlpha ; if ( reflectivityTexture && ! useMicrosurfaceFromReflectivityMapAlpha ) { return Promise . reject ( "_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture are currently not supported" ) ; } if ( ( albedoTexture || reflectivityTexture ) && hasTextureCoords ) { return this . _convertSpecularGlossinessTexturesToMetallicRoughnessAsync ( albedoTexture , reflectivityTexture , specGloss , mimeType ) . then ( ( metallicRoughnessFactors ) => { if ( metallicRoughnessFactors . baseColorTextureBase64 ) { const glTFBaseColorTexture = this . _getTextureInfoFromBase64 ( metallicRoughnessFactors . baseColorTextureBase64 , "bjsBaseColorTexture_" + ( textures . length ) + ".png" , mimeType , albedoTexture ? albedoTexture . coordinatesIndex : null , samplerIndex ) ; if ( glTFBaseColorTexture ) { glTFPbrMetallicRoughness . baseColorTexture = glTFBaseColorTexture ; } } if ( metallicRoughnessFactors . metallicRoughnessTextureBase64 ) { const glTFMRColorTexture = this . _getTextureInfoFromBase64 ( metallicRoughnessFactors . metallicRoughnessTextureBase64 , "bjsMetallicRoughnessTexture_" + ( textures . length ) + ".png" , mimeType , reflectivityTexture ? reflectivityTexture . coordinatesIndex : null , samplerIndex ) ; if ( glTFMRColorTexture ) { glTFPbrMetallicRoughness . metallicRoughnessTexture = glTFMRColorTexture ; } } return metallicRoughnessFactors ; } ) ; } else { return this . _convertSpecularGlossinessToMetallicRoughness ( specGloss ) ; } } ) ; } param babylonPBRMaterial param mimeType param images param textures param materials param imageData param hasTextureCoords public _convertPBRMaterialAsync ( babylonPBRMaterial , mimeType , hasTextureCoords ) { const glTFPbrMetallicRoughness = { } ; const glTFMaterial = { name : babylonPBRMaterial . name } ; const useMetallicRoughness = babylonPBRMaterial . isMetallicWorkflow ( ) ; if ( useMetallicRoughness ) { const albedoColor = babylonPBRMaterial . _albedoColor ; const alpha = babylonPBRMaterial . alpha ; if ( albedoColor ) { glTFPbrMetallicRoughness . baseColorFactor = [ albedoColor . r , albedoColor . g , albedoColor . b , alpha ] ; } return this . _convertMetalRoughFactorsToMetallicRoughnessAsync ( babylonPBRMaterial , mimeType , glTFPbrMetallicRoughness , hasTextureCoords ) . then ( ( metallicRoughness ) => { return this . setMetallicRoughnessPbrMaterial ( metallicRoughness , babylonPBRMaterial , glTFMaterial , glTFPbrMetallicRoughness , mimeType , hasTextureCoords ) ; } ) ; } else { return this . _convertSpecGlossFactorsToMetallicRoughnessAsync ( babylonPBRMaterial , mimeType , glTFPbrMetallicRoughness , hasTextureCoords ) . then ( ( metallicRoughness ) => { return this . setMetallicRoughnessPbrMaterial ( metallicRoughness , babylonPBRMaterial , glTFMaterial , glTFPbrMetallicRoughness , mimeType , hasTextureCoords ) ; } ) ; } } private setMetallicRoughnessPbrMaterial ( metallicRoughness , babylonPBRMaterial , glTFMaterial , glTFPbrMetallicRoughness , mimeType , hasTextureCoords ) { const materialMap = this . _exporter . _materialMap ; const materials = this . _exporter . _materials ; let promises = [ ] ; if ( metallicRoughness ) { _GLTFMaterialExporter . _SetAlphaMode ( glTFMaterial , ( babylonPBRMaterial as PBRMaterial ) ) ; if ( ! ( _GLTFMaterialExporter . FuzzyEquals ( metallicRoughness . baseColor , Color3 . White ( ) , _GLTFMaterialExporter . _Epsilon ) && babylonPBRMaterial . alpha >= _GLTFMaterialExporter . _Epsilon ) ) { glTFPbrMetallicRoughness . baseColorFactor = [ metallicRoughness . baseColor . r , metallicRoughness . baseColor . g , metallicRoughness . baseColor . b , babylonPBRMaterial . alpha ] ; } if ( metallicRoughness . metallic != null && metallicRoughness . metallic !== 1 ) { glTFPbrMetallicRoughness . metallicFactor = metallicRoughness . metallic ; } if ( metallicRoughness . roughness != null && metallicRoughness . roughness !== 1 ) { glTFPbrMetallicRoughness . roughnessFactor = metallicRoughness . roughness ; } if ( babylonPBRMaterial . backFaceCulling != null && ! babylonPBRMaterial . backFaceCulling ) { if ( ! babylonPBRMaterial . _twoSidedLighting ) { Tools . Warn ( babylonPBRMaterial . name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF." ) ; } glTFMaterial . doubleSided = true ; } if ( hasTextureCoords ) { const bumpTexture = babylonPBRMaterial . _bumpTexture ; if ( bumpTexture ) { let promise = this . _exportTextureAsync ( bumpTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFMaterial . normalTexture = glTFTexture ; if ( bumpTexture . level !== 1 ) { glTFMaterial . normalTexture . scale = bumpTexture . level ; } } } ) ; promises . push ( promise ) ; } const ambientTexture = babylonPBRMaterial . _ambientTexture ; if ( ambientTexture ) { let promise = this . _exportTextureAsync ( ambientTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { let occlusionTexture = { index : glTFTexture . index , texCoord : glTFTexture . texCoord } ; glTFMaterial . occlusionTexture = occlusionTexture ; const ambientTextureStrength = babylonPBRMaterial . _ambientTextureStrength ; if ( ambientTextureStrength ) { occlusionTexture . strength = ambientTextureStrength ; } } } ) ; promises . push ( promise ) ; } const emissiveTexture = babylonPBRMaterial . _emissiveTexture ; if ( emissiveTexture ) { let promise = this . _exportTextureAsync ( emissiveTexture , mimeType ) . then ( ( glTFTexture ) => { if ( glTFTexture ) { glTFMaterial . emissiveTexture = glTFTexture ; } } ) ; promises . push ( promise ) ; } } const emissiveColor = babylonPBRMaterial . _emissiveColor ; if ( ! _GLTFMaterialExporter . FuzzyEquals ( emissiveColor , Color3 . Black ( ) , _GLTFMaterialExporter . _Epsilon ) ) { glTFMaterial . emissiveFactor = emissiveColor . asArray ( ) ; } glTFMaterial . pbrMetallicRoughness = glTFPbrMetallicRoughness ; materials . push ( glTFMaterial ) ; materialMap [ babylonPBRMaterial . uniqueId ] = materials . length - 1 ; } return this . _finishMaterial ( promises , glTFMaterial , babylonPBRMaterial , mimeType ) ; } private getPixelsFromTexture ( babylonTexture ) { const pixels = babylonTexture . textureType === Constants . TEXTURETYPE_UNSIGNED_INT ? babylonTexture . readPixels ( ) as Promise < Uint8Array > : babylonTexture . readPixels ( ) as Promise < Float32Array > ; return pixels ; } param babylonTexture param mimeType return public _exportTextureAsync ( babylonTexture , mimeType ) { const extensionPromise = this . _exporter . _extensionsPreExportTextureAsync ( "exporter" , babylonTexture as Texture , mimeType ) ; if ( ! extensionPromise ) { return this . _exportTextureInfoAsync ( babylonTexture , mimeType ) ; } return extensionPromise . then ( ( texture ) => { if ( ! texture ) { return this . _exportTextureInfoAsync ( babylonTexture , mimeType ) ; } return this . _exportTextureInfoAsync ( texture , mimeType ) ; } ) ; } public _exportTextureInfoAsync ( babylonTexture , mimeType ) { return Promise . resolve ( ) . then ( async ( ) => { const textureUid = babylonTexture . uid ; if ( textureUid in this . _textureMap ) { return this . _textureMap [ textureUid ] ; } else { const pixels = await this . getPixelsFromTexture ( babylonTexture ) ; if ( ! pixels ) { return null ; } const samplers = this . _exporter . _samplers ; const sampler = this . _getGLTFTextureSampler ( babylonTexture ) ; let samplerIndex = null ; let foundSamplerIndex = null ; for ( let i = 0 ; i < samplers . length ; ++ i ) { let s = samplers [ i ] ; if ( s . minFilter === sampler . minFilter && s . magFilter === sampler . magFilter && s . wrapS === sampler . wrapS && s . wrapT === sampler . wrapT ) { foundSamplerIndex = i ; break ; } } if ( foundSamplerIndex == null ) { samplers . push ( sampler ) ; samplerIndex = samplers . length - 1 ; } else { samplerIndex = foundSamplerIndex ; } const size = babylonTexture . getSize ( ) ; if ( ( babylonTexture as Texture ) . mimeType ) { switch ( ( babylonTexture as Texture ) . mimeType ) { case "image/jpeg" : mimeType = ImageMimeType . JPEG ; break ; case "image/png" : mimeType = ImageMimeType . PNG ; break ; } } return this . _createBase64FromCanvasAsync ( pixels , size . width , size . height , mimeType ) . then ( ( base64Data ) => { 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 ; } ) ; } } ) ; } param base64Texture param baseTextureName param mimeType param images param textures param imageData returns private _getTextureInfoFromBase64 ( base64Texture , baseTextureName , mimeType , texCoordIndex , samplerIndex ) { const textures = this . _exporter . _textures ; const images = this . _exporter . _images ; const imageData = this . _exporter . _imageData ; let textureInfo = null ; const glTFTexture = { source : images . length , name : baseTextureName } ; if ( samplerIndex != null ) { glTFTexture . sampler = samplerIndex ; } const binStr = atob ( base64Texture . split ( ',' ) [ 1 ] ) ; let arrBuff = new ArrayBuffer ( binStr . length ) ; const arr = new Uint8Array ( arrBuff ) ; for ( let i = 0 , length = binStr . length ; i < length ; ++ i ) { arr [ i ] = binStr . charCodeAt ( i ) ; } const imageValues = { data : arr , mimeType : mimeType } ; let extension = mimeType === ImageMimeType . JPEG ? '.jpeg' : '.png' ; let textureName = baseTextureName + extension ; let originalTextureName = textureName ; if ( textureName in imageData ) { textureName = `${ baseTextureName }_${ Tools . RandomId ( ) }${ extension }` ; } imageData [ textureName ] = imageValues ; if ( mimeType === ImageMimeType . JPEG || mimeType === ImageMimeType . PNG ) { const glTFImage = { name : baseTextureName , uri : textureName } ; let foundIndex = null ; for ( let i = 0 ; i < images . length ; ++ i ) { if ( images [ i ] . uri === originalTextureName ) { foundIndex = i ; break ; } } if ( foundIndex == null ) { images . push ( glTFImage ) ; glTFTexture . source = images . length - 1 ; } else { glTFTexture . source = foundIndex ; } textures . push ( glTFTexture ) ; textureInfo = { index : textures . length - 1 } ; if ( texCoordIndex != null ) { textureInfo . texCoord = texCoordIndex ; } } else { Tools . Error ( `Unsupported texture mime type ${ mimeType }` ) ; } return textureInfo ; } }