diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts index a6385cc040d..b0761e31511 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts @@ -1,23 +1,17 @@ import type { ITextureInfo, IKHRTextureTransform } from "babylonjs-gltf2interface"; import { Tools } from "core/Misc/tools"; import type { Texture } from "core/Materials/Textures/texture"; -import { ProceduralTexture } from "core/Materials/Textures/Procedurals/proceduralTexture"; -import type { Scene } from "core/scene"; - +import type { Nullable } from "core/types"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { _Exporter } from "../glTFExporter"; const NAME = "KHR_texture_transform"; -import "../shaders/textureTransform.fragment"; - /** * @internal */ // eslint-disable-next-line @typescript-eslint/naming-convention export class KHR_texture_transform implements IGLTFExporterExtensionV2 { - private _recordedTextures: ProceduralTexture[] = []; - /** Name of this extension */ public readonly name = NAME; @@ -32,11 +26,7 @@ export class KHR_texture_transform implements IGLTFExporterExtensionV2 { constructor() {} - public dispose() { - for (const texture of this._recordedTextures) { - texture.dispose(); - } - } + public dispose() {} /** @internal */ public get wasUsed() { @@ -85,7 +75,7 @@ export class KHR_texture_transform implements IGLTFExporterExtensionV2 { } } - public preExportTextureAsync(context: string, babylonTexture: Texture): Promise { + public preExportTextureAsync(context: string, babylonTexture: Texture): Promise> { return new Promise((resolve, reject) => { const scene = babylonTexture.getScene(); if (!scene) { @@ -93,68 +83,18 @@ export class KHR_texture_transform implements IGLTFExporterExtensionV2 { return; } - let bakeTextureTransform = false; - /* * The KHR_texture_transform schema only supports rotation around the origin. - * the texture must be baked to preserve appearance. - * see: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform#gltf-schema-updates + * See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform#gltf-schema-updates. */ if ( (babylonTexture.uAng !== 0 || babylonTexture.wAng !== 0 || babylonTexture.vAng !== 0) && (babylonTexture.uRotationCenter !== 0 || babylonTexture.vRotationCenter !== 0) ) { - bakeTextureTransform = true; - } - - if (!bakeTextureTransform) { - resolve(babylonTexture); - return; - } - - return this._textureTransformTextureAsync(babylonTexture, scene) - .then((proceduralTexture) => { - resolve(proceduralTexture); - }) - .catch((e) => { - reject(e); - }); - }); - } - - /** - * Transform the babylon texture by the offset, rotation and scale parameters using a procedural texture - * @param babylonTexture - * @param scene - */ - private _textureTransformTextureAsync(babylonTexture: Texture, scene: Scene): Promise { - return new Promise((resolve) => { - const proceduralTexture = new ProceduralTexture(`${babylonTexture.name}`, babylonTexture.getSize(), "textureTransform", scene); - if (!proceduralTexture) { - Tools.Log(`Cannot create procedural texture for ${babylonTexture.name}!`); - 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()); - - // isReady trigger creation of effect if it doesnt exist yet - if (proceduralTexture.isReady()) { - proceduralTexture.render(); - resolve(proceduralTexture); + Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation not centered at the origin cannot be exported with ${NAME}`); + resolve(null); } else { - proceduralTexture.getEffect().executeWhenCompiled(() => { - proceduralTexture.render(); - resolve(proceduralTexture); - }); + resolve(babylonTexture); } }); } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 0c35d01f165..6e3bc4905ee 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -152,9 +152,9 @@ export class _Exporter { * Stores a map of the image data, where the key is the file name and the value * is the image data */ - public _imageData: { [fileName: string]: { data: Uint8Array; mimeType: ImageMimeType } }; + public _imageData: { [fileName: string]: { data: ArrayBuffer; mimeType: ImageMimeType } }; - protected _orderedImageData: Array<{ data: Uint8Array; mimeType: ImageMimeType }>; + protected _orderedImageData: Array<{ data: ArrayBuffer; mimeType: ImageMimeType }>; /** * Stores a map of the unique id of a node to its index in the node array @@ -1054,7 +1054,7 @@ export class _Exporter { private _generateJSON(shouldUseGlb: boolean, glTFPrefix?: string, prettyPrint?: boolean): string { const buffer: IBuffer = { byteLength: this._totalByteLength }; let imageName: string; - let imageData: { data: Uint8Array; mimeType: ImageMimeType }; + let imageData: { data: ArrayBuffer; mimeType: ImageMimeType }; let bufferView: IBufferView; let byteOffset: number = this._totalByteLength; @@ -1106,8 +1106,8 @@ export class _Exporter { imageData = this._imageData[image.uri]; this._orderedImageData.push(imageData); imageName = image.uri.split(".")[0] + " image"; - bufferView = _GLTFUtilities._CreateBufferView(0, byteOffset, imageData.data.length, undefined, imageName); - byteOffset += imageData.data.buffer.byteLength; + bufferView = _GLTFUtilities._CreateBufferView(0, byteOffset, imageData.data.byteLength, undefined, imageName); + byteOffset += imageData.data.byteLength; this._bufferViews.push(bufferView); image.bufferView = this._bufferViews.length - 1; image.name = imageName; @@ -1281,7 +1281,7 @@ export class _Exporter { // binary data for (let i = 0; i < this._orderedImageData.length; ++i) { - glbData.push(this._orderedImageData[i].data.buffer); + glbData.push(this._orderedImageData[i].data); } glbData.push(binPaddingBuffer); diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index 18f4f43311f..6cf687bfaf9 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -27,7 +27,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param mimeType The mime-type of the generated image * @returns A promise that resolves with the exported texture */ - preExportTextureAsync?(context: string, babylonTexture: Nullable, mimeType: ImageMimeType): Promise; + preExportTextureAsync?(context: string, babylonTexture: Nullable, mimeType: ImageMimeType): Promise>; /** * Define this method to get notified when a texture info is created diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 53b7a366f9d..ac13496d03d 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -1,13 +1,4 @@ -import type { - ITextureInfo, - IMaterial, - IMaterialPbrMetallicRoughness, - IMaterialOcclusionTextureInfo, - ISampler, - ITexture, - IImage, - IMaterialExtension, -} from "babylonjs-gltf2interface"; +import type { ITextureInfo, IMaterial, IMaterialPbrMetallicRoughness, IMaterialOcclusionTextureInfo, ISampler, IMaterialExtension } from "babylonjs-gltf2interface"; import { ImageMimeType, MaterialAlphaMode, TextureMagFilter, TextureMinFilter, TextureWrapMode } from "babylonjs-gltf2interface"; import type { Nullable } from "core/types"; @@ -61,7 +52,7 @@ interface _IPBRMetallicRoughness { */ baseColor: Color3; /** - * Represents the metallness of the material + * Represents the metalness of the material */ metallic: Nullable; /** @@ -69,13 +60,24 @@ interface _IPBRMetallicRoughness { */ roughness: Nullable; /** - * The metallic roughness texture as a base64 string + * The metallic roughness texture data */ - metallicRoughnessTextureBase64?: Nullable; + metallicRoughnessTextureData?: Nullable; /** - * The base color texture as a base64 string + * The base color texture data */ - baseColorTextureBase64?: Nullable; + baseColorTextureData?: Nullable; +} + +function getFileExtensionFromMimeType(mimeType: ImageMimeType): string { + switch (mimeType) { + case ImageMimeType.JPEG: + return ".jpg"; + case ImageMimeType.PNG: + return ".png"; + case ImageMimeType.WEBP: + return ".webp"; + } } /** @@ -98,6 +100,9 @@ export class _GLTFMaterialExporter { */ private _textureMap: { [textureId: string]: ITextureInfo } = {}; + // Mapping of internal textures to images to avoid exporting duplicate images. + private _internalTextureToImage: { [uniqueId: number]: { [mimeType: string]: number } } = {}; + /** * Numeric tolerance value */ @@ -307,57 +312,57 @@ export class _GLTFMaterialExporter { const materialMap = this._exporter._materialMap; const materials = this._exporter._materials; const promises = []; - const glTFPbrMetallicRoughness = this._convertToGLTFPBRMetallicRoughness(babylonStandardMaterial); + const pbrMetallicRoughness = this._convertToGLTFPBRMetallicRoughness(babylonStandardMaterial); - const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name }; + const material: IMaterial = { name: babylonStandardMaterial.name }; if (babylonStandardMaterial.backFaceCulling != null && !babylonStandardMaterial.backFaceCulling) { if (!babylonStandardMaterial.twoSidedLighting) { Tools.Warn(babylonStandardMaterial.name + ": Back-face culling disabled and two-sided lighting disabled is not supported in glTF."); } - glTFMaterial.doubleSided = true; + material.doubleSided = true; } if (hasTextureCoords) { if (babylonStandardMaterial.diffuseTexture) { promises.push( - this._exportTextureAsync(babylonStandardMaterial.diffuseTexture, mimeType).then((glTFTexture) => { - if (glTFTexture) { - glTFPbrMetallicRoughness.baseColorTexture = glTFTexture; + this._exportTextureAsync(babylonStandardMaterial.diffuseTexture, mimeType).then((textureInfo) => { + if (textureInfo) { + pbrMetallicRoughness.baseColorTexture = textureInfo; } }) ); } - if (babylonStandardMaterial.bumpTexture) { + const bumpTexture = babylonStandardMaterial.bumpTexture; + if (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; + this._exportTextureAsync(bumpTexture, mimeType).then((textureInfo) => { + if (textureInfo) { + material.normalTexture = textureInfo; + if (bumpTexture.level !== 1) { + material.normalTexture.scale = bumpTexture.level; } } }) ); } if (babylonStandardMaterial.emissiveTexture) { - glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0]; + material.emissiveFactor = [1.0, 1.0, 1.0]; promises.push( - this._exportTextureAsync(babylonStandardMaterial.emissiveTexture, mimeType).then((glTFEmissiveTexture) => { - if (glTFEmissiveTexture) { - glTFMaterial.emissiveTexture = glTFEmissiveTexture; + this._exportTextureAsync(babylonStandardMaterial.emissiveTexture, mimeType).then((textureInfo) => { + if (textureInfo) { + material.emissiveTexture = textureInfo; } }) ); } if (babylonStandardMaterial.ambientTexture) { promises.push( - this._exportTextureAsync(babylonStandardMaterial.ambientTexture, mimeType).then((glTFTexture) => { - if (glTFTexture) { + this._exportTextureAsync(babylonStandardMaterial.ambientTexture, mimeType).then((textureInfo) => { + if (textureInfo) { const occlusionTexture: IMaterialOcclusionTextureInfo = { - index: glTFTexture.index, + index: textureInfo.index, }; - glTFMaterial.occlusionTexture = occlusionTexture; - occlusionTexture.strength = 1.0; + material.occlusionTexture = occlusionTexture; } }) ); @@ -366,22 +371,22 @@ export class _GLTFMaterialExporter { if (babylonStandardMaterial.alpha < 1.0 || babylonStandardMaterial.opacityTexture) { if (babylonStandardMaterial.alphaMode === Constants.ALPHA_COMBINE) { - glTFMaterial.alphaMode = MaterialAlphaMode.BLEND; + material.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(); + material.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray(); } - glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness; - _GLTFMaterialExporter._SetAlphaMode(glTFMaterial, babylonStandardMaterial); + material.pbrMetallicRoughness = pbrMetallicRoughness; + _GLTFMaterialExporter._SetAlphaMode(material, babylonStandardMaterial); - materials.push(glTFMaterial); + materials.push(material); materialMap[babylonStandardMaterial.uniqueId] = materials.length - 1; - return this._finishMaterial(promises, glTFMaterial, babylonStandardMaterial, mimeType); + return this._finishMaterial(promises, material, babylonStandardMaterial, mimeType); } private _finishMaterial(promises: Promise[], glTFMaterial: IMaterial, babylonMaterial: Material, mimeType: ImageMimeType) { @@ -418,25 +423,20 @@ export class _GLTFMaterialExporter { * @param mimeType mimetype of the image * @returns base64 image string */ - private _createBase64FromCanvasAsync(buffer: Uint8Array | Float32Array, width: number, height: number, mimeType: ImageMimeType): Promise { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const textureType = Constants.TEXTURETYPE_UNSIGNED_INT; + private async _getImageDataAsync(buffer: Uint8Array | Float32Array, width: number, height: number, mimeType: ImageMimeType): Promise { + const textureType = Constants.TEXTURETYPE_UNSIGNED_INT; - const hostingScene = this._exporter._babylonScene; - const engine = hostingScene.getEngine(); + const hostingScene = this._exporter._babylonScene; + const engine = hostingScene.getEngine(); - // Create a temporary texture with the texture buffer data - const tempTexture = engine.createRawTexture(buffer, width, height, Constants.TEXTUREFORMAT_RGBA, false, true, Texture.NEAREST_SAMPLINGMODE, null, textureType); + // Create a temporary texture with the texture buffer data + 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); + await TextureTools.ApplyPostProcess("pass", tempTexture, hostingScene, textureType, Constants.TEXTURE_NEAREST_SAMPLINGMODE, Constants.TEXTUREFORMAT_RGBA); - const data = await engine._readTexturePixels(tempTexture, width, height); + const data = await engine._readTexturePixels(tempTexture, width, height); - const base64: string = await (Tools.DumpDataAsync(width, height, data, mimeType, undefined, true, false) as Promise); - - resolve(base64); - }); + return (await Tools.DumpDataAsync(width, height, data, mimeType, undefined, true, true)) as ArrayBuffer; } /** @@ -533,7 +533,7 @@ export class _GLTFMaterialExporter { factors: _IPBRSpecularGlossiness, mimeType: ImageMimeType ): Promise<_IPBRMetallicRoughness> { - const promises = []; + const promises = new Array>(); if (!(diffuseTexture || specularGlossinessTexture)) { return Promise.reject("_ConvertSpecularGlosinessTexturesToMetallicRoughness: diffuse and specular glossiness textures are not defined!"); } @@ -654,16 +654,18 @@ export class _GLTFMaterialExporter { } if (writeOutMetallicRoughnessTexture) { - const promise = this._createBase64FromCanvasAsync(metallicRoughnessBuffer, width, height, mimeType).then((metallicRoughnessBase64) => { - metallicRoughnessFactors.metallicRoughnessTextureBase64 = metallicRoughnessBase64; - }); - promises.push(promise); + promises.push( + this._getImageDataAsync(metallicRoughnessBuffer, width, height, mimeType).then((data) => { + metallicRoughnessFactors.metallicRoughnessTextureData = data; + }) + ); } if (writeOutBaseColorTexture) { - const promise = this._createBase64FromCanvasAsync(baseColorBuffer, width, height, mimeType).then((baseColorBase64) => { - metallicRoughnessFactors.baseColorTextureBase64 = baseColorBase64; - }); - promises.push(promise); + promises.push( + this._getImageDataAsync(baseColorBuffer, width, height, mimeType).then((data) => { + metallicRoughnessFactors.baseColorTextureData = data; + }) + ); } return Promise.all(promises).then(() => { @@ -777,74 +779,85 @@ export class _GLTFMaterialExporter { }); } - private _getGLTFTextureSampler(texture: BaseTexture): ISampler { - const sampler = this._getGLTFTextureWrapModesSampler(texture); + private _getTextureSampler(texture: Nullable): ISampler { + const sampler: ISampler = {}; + if (!texture || !(texture instanceof Texture)) { + return sampler; + } - const 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; - } + const wrapS = this._getGLTFTextureWrapMode(texture.wrapU); + if (wrapS !== TextureWrapMode.REPEAT) { + sampler.wrapS = wrapS; + } + + const wrapT = this._getGLTFTextureWrapMode(texture.wrapV); + if (wrapT !== TextureWrapMode.REPEAT) { + sampler.wrapT = wrapT; + } + + switch (texture.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; } @@ -866,18 +879,6 @@ export class _GLTFMaterialExporter { } } - private _getGLTFTextureWrapModesSampler(texture: BaseTexture): ISampler { - const wrapS = this._getGLTFTextureWrapMode(texture instanceof Texture ? texture.wrapU : Texture.WRAP_ADDRESSMODE); - const wrapT = this._getGLTFTextureWrapMode(texture instanceof Texture ? texture.wrapV : Texture.WRAP_ADDRESSMODE); - - if (wrapS === TextureWrapMode.REPEAT && wrapT === TextureWrapMode.REPEAT) { - // default wrapping mode in glTF, so omitting - return {}; - } - - return { wrapS: wrapS, wrapT: wrapT }; - } - /** * Convert a PBRMaterial (Specular/Glossiness) to Metallic Roughness factors * @param babylonPBRMaterial BJS PBR Metallic Roughness Material @@ -889,60 +890,32 @@ export class _GLTFMaterialExporter { private _convertSpecGlossFactorsToMetallicRoughnessAsync( babylonPBRMaterial: PBRBaseMaterial, mimeType: ImageMimeType, - glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, + pbrMetallicRoughness: IMaterialPbrMetallicRoughness, hasTextureCoords: boolean ): Promise<_IPBRMetallicRoughness> { 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: _IPBRSpecularGlossiness = { - diffuseColor: diffuseColor, - specularColor: specularColor, - glossiness: glossiness, + diffuseColor: babylonPBRMaterial._albedoColor, + specularColor: babylonPBRMaterial._reflectivityColor, + glossiness: babylonPBRMaterial._microSurface, }; - let samplerIndex: Nullable = 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) { + const samplerIndex = this._exportTextureSampler(albedoTexture || reflectivityTexture); 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; - } + const textures = this._exporter._textures; + if (metallicRoughnessFactors.baseColorTextureData) { + const imageIndex = this._exportImage(`baseColor${textures.length}`, mimeType, metallicRoughnessFactors.baseColorTextureData); + pbrMetallicRoughness.baseColorTexture = this._exportTextureInfo(imageIndex, samplerIndex, albedoTexture?.coordinatesIndex); } - if (metallicRoughnessFactors.metallicRoughnessTextureBase64) { - const glTFMRColorTexture = this._getTextureInfoFromBase64( - metallicRoughnessFactors.metallicRoughnessTextureBase64, - "bjsMetallicRoughnessTexture_" + textures.length + ".png", - mimeType, - reflectivityTexture ? reflectivityTexture.coordinatesIndex : null, - samplerIndex - ); - if (glTFMRColorTexture) { - glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFMRColorTexture; - } + if (metallicRoughnessFactors.metallicRoughnessTextureData) { + const imageIndex = this._exportImage(`metallicRoughness${textures.length}`, mimeType, metallicRoughnessFactors.metallicRoughnessTextureData); + pbrMetallicRoughness.metallicRoughnessTexture = this._exportTextureInfo(imageIndex, samplerIndex, reflectivityTexture?.coordinatesIndex); } return metallicRoughnessFactors; @@ -1100,147 +1073,105 @@ export class _GLTFMaterialExporter { }); } - public _exportTextureInfoAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { - 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; - } + public async _exportTextureInfoAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { + 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: Nullable = null; + const samplerIndex = this._exportTextureSampler(babylonTexture); - // if a pre-existing sampler with identical parameters exists, then reuse the previous sampler - let foundSamplerIndex: Nullable = null; - for (let i = 0; i < samplers.length; ++i) { - const s = samplers[i]; - if (s.minFilter === sampler.minFilter && s.magFilter === sampler.magFilter && s.wrapS === sampler.wrapS && s.wrapT === sampler.wrapT) { - foundSamplerIndex = i; + // Preserve texture mime type if defined + const textureMimeType = (babylonTexture as Texture).mimeType; + if (textureMimeType) { + switch (textureMimeType) { + case "image/jpeg": + case "image/png": + case "image/webp": + mimeType = textureMimeType as ImageMimeType; + break; + default: + Tools.Warn("Unsupported media type: ${textureMimeType}"); break; - } } + } - if (foundSamplerIndex == null) { - samplers.push(sampler); - samplerIndex = samplers.length - 1; - } else { - samplerIndex = foundSamplerIndex; - } + const internalTextureToImage = this._internalTextureToImage; + const internalTextureUniqueId = babylonTexture.getInternalTexture()!.uniqueId; + internalTextureToImage[internalTextureUniqueId] ||= {}; + let imageIndex = internalTextureToImage[internalTextureUniqueId][mimeType]; + if (imageIndex === undefined) { const size = babylonTexture.getSize(); + const data = await this._getImageDataAsync(pixels, size.width, size.height, mimeType); + imageIndex = this._exportImage(babylonTexture.name, mimeType, data); + internalTextureToImage[internalTextureUniqueId][mimeType] = imageIndex; + } - // Preserve texture mime type if defined - if ((babylonTexture as Texture).mimeType) { - switch ((babylonTexture as Texture).mimeType) { - case "image/jpeg": - mimeType = ImageMimeType.JPEG; - break; - case "image/png": - mimeType = ImageMimeType.PNG; - break; - case "image/webp": - mimeType = ImageMimeType.WEBP; - break; - } - } + const textureInfo = this._exportTextureInfo(imageIndex, samplerIndex, babylonTexture.coordinatesIndex); + this._textureMap[textureUid] = textureInfo; + return textureInfo; + } + } - 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); - } + private _exportImage(name: string, mimeType: ImageMimeType, data: ArrayBuffer): number { + const imageData = this._exporter._imageData; - return textureInfo; - }); - } + const baseName = name.replace(/\.\/|\/|\.\\|\\/g, "_"); + const extension = getFileExtensionFromMimeType(mimeType); + let fileName = baseName + extension; + if (fileName in imageData) { + fileName = `${baseName}_${Tools.RandomId()}${extension}`; + } + + imageData[fileName] = { + data: data, + mimeType: mimeType, + }; + + const images = this._exporter._images; + images.push({ + name: name, + uri: fileName, }); + + return images.length - 1; } - /** - * Builds a texture from base64 string - * @param base64Texture base64 texture string - * @param baseTextureName Name to use for the texture - * @param mimeType image mime type for the texture - * @param texCoordIndex - * @param samplerIndex - * @returns glTF texture info, or null if the texture format is not supported - */ - private _getTextureInfoFromBase64( - base64Texture: string, - baseTextureName: string, - mimeType: ImageMimeType, - texCoordIndex: Nullable, - samplerIndex: Nullable - ): Nullable { + private _exportTextureInfo(imageIndex: number, samplerIndex: number, coordinatesIndex?: number): ITextureInfo { const textures = this._exporter._textures; - const images = this._exporter._images; - const imageData = this._exporter._imageData; - let textureInfo: Nullable = null; - - const glTFTexture: ITexture = { - source: images.length, - name: baseTextureName, - }; - if (samplerIndex != null) { - glTFTexture.sampler = samplerIndex; + let textureIndex = textures.findIndex((t) => t.sampler == samplerIndex && t.source === imageIndex); + if (textureIndex === -1) { + textureIndex = textures.length; + textures.push({ + source: imageIndex, + sampler: samplerIndex, + }); } - const binStr = atob(base64Texture.split(",")[1]); - const 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 textureInfo: ITextureInfo = { index: textureIndex }; + if (coordinatesIndex) { + textureInfo.texCoord = coordinatesIndex; } - const imageValues = { data: arr, mimeType: mimeType }; + return textureInfo; + } - const extension = mimeType === ImageMimeType.JPEG ? ".jpeg" : ".png"; - let textureName = baseTextureName + extension; - const originalTextureName = textureName; - if (textureName in imageData) { - textureName = `${baseTextureName}_${Tools.RandomId()}${extension}`; - } + private _exportTextureSampler(texture: Nullable): number { + const sampler = this._getTextureSampler(texture); - imageData[textureName] = imageValues; - if (mimeType === ImageMimeType.JPEG || mimeType === ImageMimeType.PNG || mimeType === ImageMimeType.WEBP) { - const glTFImage: IImage = { - name: baseTextureName, - uri: textureName, - }; - let foundIndex: Nullable = 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}`); + // if a pre-existing sampler with identical parameters exists, then reuse the previous sampler + const samplers = this._exporter._samplers; + const samplerIndex = samplers.findIndex( + (s) => s.minFilter === sampler.minFilter && s.magFilter === sampler.magFilter && s.wrapS === sampler.wrapS && s.wrapT === sampler.wrapT + ); + if (samplerIndex !== -1) { + return samplerIndex; } - return textureInfo; + samplers.push(sampler); + return samplers.length - 1; } } diff --git a/packages/dev/serializers/src/glTF/2.0/shaders/textureTransform.fragment.fx b/packages/dev/serializers/src/glTF/2.0/shaders/textureTransform.fragment.fx deleted file mode 100644 index b4c69acb90a..00000000000 --- a/packages/dev/serializers/src/glTF/2.0/shaders/textureTransform.fragment.fx +++ /dev/null @@ -1,17 +0,0 @@ -precision highp float; - -varying vec2 vUV; - -uniform sampler2D textureSampler; -uniform mat4 textureTransformMat; - -void main(void) { - -#define CUSTOM_FRAGMENT_MAIN_BEGIN - - vec2 uvTransformed = (textureTransformMat * vec4(vUV.xy, 1, 1)).xy; - gl_FragColor = texture2D(textureSampler, uvTransformed); - -#define CUSTOM_FRAGMENT_MAIN_END - -} \ No newline at end of file