diff --git a/packages/dev/core/src/Materials/PBR/openpbrMaterial.ts b/packages/dev/core/src/Materials/PBR/openpbrMaterial.ts index 6ea8b86aaa6..56ec9a05ce8 100644 --- a/packages/dev/core/src/Materials/PBR/openpbrMaterial.ts +++ b/packages/dev/core/src/Materials/PBR/openpbrMaterial.ts @@ -233,6 +233,7 @@ export class OpenPBRMaterialDefines extends ImageProcessingDefinesMixin(OpenPBRM public COAT_ROUGHNESS_ANISOTROPY_FROM_TANGENT_TEXTURE = false; public USE_GLTF_STYLE_ANISOTROPY = false; public THIN_FILM_THICKNESS_FROM_THIN_FILM_TEXTURE = false; + public FUZZ_ROUGHNESS_FROM_TEXTURE_ALPHA = false; public ENVIRONMENTBRDF = false; public ENVIRONMENTBRDF_RGBD = false; @@ -1241,6 +1242,13 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { */ public _useGltfStyleAnisotropy = false; + /** + * Specifies that the fuzz roughness is stored in the alpha channel of the texture. + * This is for compatibility with glTF where the fuzz roughness is often stored in + * the alpha channel of the fuzz color texture. + */ + public _useFuzzRoughnessFromTextureAlpha = false; + /** * This parameters will enable/disable Horizon occlusion to prevent normal maps to look shiny when the normal * makes the reflect vector face the model (under horizon). @@ -2621,6 +2629,7 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { defines.SPECULAR_ROUGHNESS_ANISOTROPY_FROM_TANGENT_TEXTURE = this._useSpecularRoughnessAnisotropyFromTangentTexture; defines.COAT_ROUGHNESS_ANISOTROPY_FROM_TANGENT_TEXTURE = this._useCoatRoughnessAnisotropyFromTangentTexture; defines.ROUGHNESSSTOREINMETALMAPGREEN = this._useRoughnessFromMetallicTextureGreen; + defines.FUZZ_ROUGHNESS_FROM_TEXTURE_ALPHA = this._useFuzzRoughnessFromTextureAlpha; defines.METALLNESSSTOREINMETALMAPBLUE = this._useMetallicFromMetallicTextureBlue; defines.THIN_FILM_THICKNESS_FROM_THIN_FILM_TEXTURE = this._useThinFilmThicknessFromTextureGreen; diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrFuzzLayerData.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFuzzLayerData.fx index c0e8c878ca4..365ec3357fa 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrFuzzLayerData.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFuzzLayerData.fx @@ -39,7 +39,7 @@ fuzz_roughness = vFuzzRoughness; fuzz_color *= vFuzzColorInfos.y; #endif -#if defined(FUZZ_ROUGHNESS) && defined(FUZZ_ROUGHNESS_FROM_COLOR_TEXTURE_ALPHA) +#if defined(FUZZ_ROUGHNESS) && defined(FUZZ_ROUGHNESS_FROM_TEXTURE_ALPHA) fuzz_roughness *= fuzzRoughnessFromTexture.a; #elif defined(FUZZ_ROUGHNESS) fuzz_roughness *= fuzzRoughnessFromTexture.r; diff --git a/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx b/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx index ef44fb0c5b8..8d5de3f2d4b 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx @@ -50,7 +50,7 @@ vec2 UV = vec2(perceptualRoughness, NdotV); // We can find the scale and offset to apply to the specular value. - vec4 brdfLookup = texture(environmentFuzzBrdfSampler, UV); + vec4 brdfLookup = texture2D(environmentFuzzBrdfSampler, UV); const vec2 RiRange = vec2(0.0, 0.75); const vec2 ARange = vec2(0.005, 0.88); diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.fx index 318416bb022..bb1831a91e9 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.fx @@ -39,7 +39,7 @@ fuzz_roughness = uniforms.vFuzzRoughness; fuzz_color *= uniforms.vFuzzColorInfos.y; #endif -#if defined(FUZZ_ROUGHNESS) && defined(FUZZ_ROUGHNESS_FROM_COLOR_TEXTURE_ALPHA) +#if defined(FUZZ_ROUGHNESS) && defined(FUZZ_ROUGHNESS_FROM_TEXTURE_ALPHA) fuzz_roughness *= fuzzRoughnessFromTexture.a; #elif defined(FUZZ_ROUGHNESS) fuzz_roughness *= fuzzRoughnessFromTexture.r; diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_fuzz.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_fuzz.ts new file mode 100644 index 00000000000..3ad902a68c5 --- /dev/null +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_fuzz.ts @@ -0,0 +1,119 @@ +import type { Nullable } from "core/types"; +import type { Material } from "core/Materials/material"; +import type { IMaterial, ITextureInfo } from "../glTFLoaderInterfaces"; +import type { IGLTFLoaderExtension } from "../glTFLoaderExtension"; +import { GLTFLoader } from "../glTFLoader"; +import { Color3 } from "core/Maths/math.color"; +import type { IKHRMaterialsFuzz } from "babylonjs-gltf2interface"; +import { registerGLTFExtension, unregisterGLTFExtension } from "../glTFLoaderExtensionRegistry"; + +const NAME = "KHR_materials_fuzz"; + +declare module "../../glTFFileLoader" { + // eslint-disable-next-line jsdoc/require-jsdoc, @typescript-eslint/naming-convention + export interface GLTFLoaderExtensionOptions { + /** + * Defines options for the KHR_materials_fuzz extension. + */ + // NOTE: Don't use NAME here as it will break the UMD type declarations. + ["KHR_materials_fuzz"]: {}; + } +} + +/** + * [Specification] + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_fuzz implements IGLTFLoaderExtension { + /** + * The name of this extension. + */ + public readonly name = NAME; + + /** + * Defines whether this extension is enabled. + */ + public enabled: boolean; + + /** + * Defines a number that determines the order the extensions are applied. + */ + public order = 190; + + private _loader: GLTFLoader; + + /** + * @internal + */ + constructor(loader: GLTFLoader) { + this._loader = loader; + this.enabled = this._loader.isExtensionUsed(NAME); + } + + /** @internal */ + public dispose() { + (this._loader as any) = null; + } + + /** + * @internal + */ + // eslint-disable-next-line no-restricted-syntax + public loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable> { + return GLTFLoader.LoadExtensionAsync(context, material, this.name, async (extensionContext, extension) => { + const promises = new Array>(); + promises.push(this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial)); + promises.push(this._loadFuzzPropertiesAsync(extensionContext, extension, babylonMaterial)); + // eslint-disable-next-line github/no-then + return await Promise.all(promises).then(() => {}); + }); + } + + // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax + private _loadFuzzPropertiesAsync(context: string, properties: IKHRMaterialsFuzz, babylonMaterial: Material): Promise { + const adapter = this._loader._getOrCreateMaterialAdapter(babylonMaterial); + const promises = new Array>(); + + adapter.configureFuzz(); + + // Set non-texture properties immediately + adapter.fuzzWeight = properties.fuzzFactor !== undefined ? properties.fuzzFactor : 0.0; + adapter.fuzzColor = properties.fuzzColorFactor !== undefined ? Color3.FromArray(properties.fuzzColorFactor) : Color3.White(); + adapter.fuzzRoughness = properties.fuzzRoughnessFactor !== undefined ? properties.fuzzRoughnessFactor : 0.5; + + // Load textures + if (properties.fuzzTexture) { + promises.push( + this._loader.loadTextureInfoAsync(`${context}/fuzzTexture`, properties.fuzzTexture, (texture) => { + texture.name = `${babylonMaterial.name} (Fuzz)`; + adapter.fuzzWeightTexture = texture; + }) + ); + } + + if (properties.fuzzColorTexture) { + promises.push( + this._loader.loadTextureInfoAsync(`${context}/fuzzColorTexture`, properties.fuzzColorTexture, (texture) => { + texture.name = `${babylonMaterial.name} (Fuzz Color)`; + adapter.fuzzColorTexture = texture; + }) + ); + } + + if (properties.fuzzRoughnessTexture) { + (properties.fuzzRoughnessTexture as ITextureInfo).nonColorData = true; + promises.push( + this._loader.loadTextureInfoAsync(`${context}/fuzzRoughnessTexture`, properties.fuzzRoughnessTexture, (texture) => { + texture.name = `${babylonMaterial.name} (Fuzz Roughness)`; + adapter.fuzzRoughnessTexture = texture; + }) + ); + } + + // eslint-disable-next-line github/no-then + return Promise.all(promises).then(() => {}); + } +} + +unregisterGLTFExtension(NAME); +registerGLTFExtension(NAME, true, (loader) => new KHR_materials_fuzz(loader)); diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts index e889e61112a..25799e3d145 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts @@ -16,6 +16,7 @@ export * from "./KHR_materials_iridescence"; export * from "./KHR_materials_anisotropy"; export * from "./KHR_materials_emissive_strength"; export * from "./KHR_materials_sheen"; +export * from "./KHR_materials_fuzz"; export * from "./KHR_materials_specular"; export * from "./KHR_materials_ior"; export * from "./KHR_materials_variants"; diff --git a/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts b/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts index c9579d511d5..ded85f90035 100644 --- a/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts +++ b/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts @@ -56,32 +56,32 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets/gets the base color (OpenPBR: baseColor, PBR: albedoColor) + * Sets/gets the base color */ baseColor: Color3; /** - * Sets/gets the base color texture (OpenPBR: baseColorTexture, PBR: albedoTexture) + * Sets/gets the base color texture */ baseColorTexture: Nullable; /** - * Sets/gets the base diffuse roughness (OpenPBR: baseDiffuseRoughness, PBR: baseDiffuseRoughness) + * Sets/gets the base diffuse roughness */ baseDiffuseRoughness: number; /** - * Sets/gets the base diffuse roughness texture (OpenPBR: baseDiffuseRoughnessTexture, PBR: baseDiffuseRoughnessTexture) + * Sets/gets the base diffuse roughness texture */ baseDiffuseRoughnessTexture: Nullable; /** - * Sets/gets the base metalness (OpenPBR: baseMetalness, PBR: metallic) + * Sets/gets the base metalness */ baseMetalness: number; /** - * Sets/gets the base metalness texture (OpenPBR: baseMetalnessTexture, PBR: metallicTexture) + * Sets/gets the base metalness texture */ baseMetalnessTexture: Nullable; @@ -106,27 +106,27 @@ export interface IMaterialLoadingAdapter { enableSpecularEdgeColor(enableEdgeColor?: boolean): void; /** - * Sets/gets the specular weight (OpenPBR: specularWeight, PBR: metallicF0Factor) + * Sets/gets the specular weight */ specularWeight: number; /** - * Sets/gets the specular weight texture (OpenPBR: specularWeightTexture, PBR: metallicReflectanceTexture) + * Sets/gets the specular weight texture */ specularWeightTexture: Nullable; /** - * Sets/gets the specular color (OpenPBR: specularColor, PBR: reflectance) + * Sets/gets the specular color */ specularColor: Color3; /** - * Sets/gets the specular color texture (OpenPBR: specularColorTexture, PBR: reflectanceTexture) + * Sets/gets the specular color texture */ specularColorTexture: Nullable; /** - * Sets/gets the specular roughness (OpenPBR: specularRoughness, PBR: roughness) + * Sets/gets the specular roughness */ specularRoughness: number; @@ -136,7 +136,7 @@ export interface IMaterialLoadingAdapter { specularRoughnessTexture: Nullable; /** - * Sets/gets the specular IOR (OpenPBR: specularIor, PBR: indexOfRefraction) + * Sets/gets the specular IOR */ specularIor: number; @@ -145,12 +145,12 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets/gets the emissive color (OpenPBR: emissionColor, PBR: emissiveColor) + * Sets/gets the emissive color */ emissionColor: Color3; /** - * Sets/gets the emissive luminance (OpenPBR: emissionLuminance, PBR: emissiveIntensity) + * Sets/gets the emissive luminance */ emissionLuminance: number; @@ -164,7 +164,7 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets/gets the ambient occlusion texture (OpenPBR: ambientOcclusionTexture, PBR: ambientTexture) + * Sets/gets the ambient occlusion texture */ ambientOcclusionTexture: Nullable; @@ -183,32 +183,32 @@ export interface IMaterialLoadingAdapter { configureCoat(): void; /** - * Sets/gets the coat weight (OpenPBR: coatWeight, PBR: clearCoat.intensity) + * Sets/gets the coat weight */ coatWeight: number; /** - * Sets/gets the coat weight texture (OpenPBR: coatWeightTexture, PBR: clearCoat.texture) + * Sets/gets the coat weight texture */ coatWeightTexture: Nullable; /** - * Sets the coat color (OpenPBR: coatColor, no PBR equivalent) + * Sets the coat color */ coatColor: Color3; /** - * Sets the coat color texture (OpenPBR: coatColorTexture, no PBR equivalent) + * Sets the coat color texture */ coatColorTexture: Nullable; /** - * Sets/gets the coat roughness (OpenPBR: coatRoughness, PBR: clearCoat.roughness) + * Sets/gets the coat roughness */ coatRoughness: number; /** - * Sets/gets the coat roughness texture (OpenPBR: coatRoughnessTexture, PBR: clearCoat.textureRoughness) + * Sets/gets the coat roughness texture */ coatRoughnessTexture: Nullable; @@ -218,27 +218,27 @@ export interface IMaterialLoadingAdapter { coatIor: number; /** - * Sets the coat darkening (OpenPBR: coatDarkening, no PBR equivalent) + * Sets the coat darkening */ coatDarkening: number; /** - * Sets the coat darkening texture (OpenPBR: coatDarkeningTexture, no PBR equivalent) + * Sets the coat darkening texture */ coatDarkeningTexture: Nullable; /** - * Sets/gets the coat roughness anisotropy (OpenPBR: coatRoughnessAnisotropy, PBR: clearCoat.anisotropy.intensity) + * Sets/gets the coat roughness anisotropy */ coatRoughnessAnisotropy: number; /** - * Sets the coat tangent angle for anisotropy (OpenPBR: geometryCoatTangentAngle, PBR: clearCoat.anisotropy.angle) + * Sets the coat tangent angle for anisotropy */ geometryCoatTangentAngle: number; /** - * Sets the coat tangent texture for anisotropy (OpenPBR: geometryCoatTangentTexture, PBR: clearCoat.anisotropy.texture) + * Sets the coat tangent texture for anisotropy */ geometryCoatTangentTexture: Nullable; @@ -247,22 +247,22 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets the transmission weight (OpenPBR: transmissionWeight, PBR: subSurface.refractionIntensity) + * Sets the transmission weight */ transmissionWeight: number; /** - * Sets the transmission weight texture (OpenPBR: transmissionWeightTexture, PBR: subSurface.refractionIntensityTexture) + * Sets the transmission weight texture */ transmissionWeightTexture: Nullable; /** - * Sets the attenuation distance (OpenPBR: attenuationDistance, PBR: subSurface.volumeIndexOfRefraction) + * Sets the attenuation distance */ transmissionDepth: number; /** - * Sets the attenuation color (OpenPBR: attenuationColor, PBR: subSurface.tintColor) + * Sets the attenuation color */ transmissionColor: Color3; @@ -281,12 +281,12 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets the thickness texture (OpenPBR: thicknessTexture, PBR: subSurface.thicknessTexture) + * Sets the thickness texture */ volumeThicknessTexture: Nullable; /** - * Sets the thickness factor (OpenPBR: thickness, PBR: subSurface.maximumThickness) + * Sets the thickness factor */ volumeThickness: number; @@ -310,12 +310,12 @@ export interface IMaterialLoadingAdapter { subsurfaceWeightTexture: Nullable; /** - * Sets/gets the subsurface color (OpenPBR: subsurfaceColor, PBR: subSurface.tintColor) + * Sets/gets the subsurface color */ subsurfaceColor: Color3; /** - * Sets/gets the subsurface color texture (OpenPBR: subsurfaceColorTexture, PBR: subSurface.tintColorTexture) + * Sets/gets the subsurface color texture */ subsurfaceColorTexture: Nullable; @@ -329,27 +329,32 @@ export interface IMaterialLoadingAdapter { configureFuzz(): void; /** - * Sets the fuzz weight (OpenPBR: fuzzWeight, PBR: fuzz.intensity) + * Sets the fuzz weight */ fuzzWeight: number; /** - * Sets the fuzz color (OpenPBR: fuzzColor, PBR: fuzz.color) + * Sets the fuzz weight texture + */ + fuzzWeightTexture: Nullable; + + /** + * Sets the fuzz color */ fuzzColor: Color3; /** - * Sets the fuzz color texture (OpenPBR: fuzzColorTexture, PBR: fuzz.texture) + * Sets the fuzz color texture */ fuzzColorTexture: Nullable; /** - * Sets the fuzz roughness (OpenPBR: fuzzRoughness, PBR: fuzz.roughness) + * Sets the fuzz roughness */ fuzzRoughness: number; /** - * Sets the fuzz roughness texture (OpenPBR: fuzzRoughnessTexture, PBR: fuzz.textureRoughness) + * Sets the fuzz roughness texture */ fuzzRoughnessTexture: Nullable; @@ -358,17 +363,17 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets/gets the specular roughness anisotropy (OpenPBR: specularRoughnessAnisotropy, PBR: anisotropy.intensity) + * Sets/gets the specular roughness anisotropy */ specularRoughnessAnisotropy: number; /** - * Sets the anisotropy rotation (OpenPBR: anisotropyRotation, PBR: anisotropy.angle) + * Sets the anisotropy rotation */ geometryTangentAngle: number; /** - * Sets/gets the anisotropy texture (OpenPBR: geometryTangentTexture, PBR: anisotropy.texture) + * Sets/gets the anisotropy texture */ geometryTangentTexture: Nullable; @@ -417,7 +422,7 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets the unlit flag (OpenPBR: unlit, PBR: unlit) + * Sets the unlit flag */ unlit: boolean; @@ -426,12 +431,12 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets/gets the geometry opacity (OpenPBR: geometryOpacity, PBR: alpha) + * Sets/gets the geometry opacity */ geometryOpacity: number; /** - * Sets/gets the geometry normal texture (OpenPBR: geometryNormalTexture, PBR: bumpTexture) + * Sets/gets the geometry normal texture */ geometryNormalTexture: Nullable; @@ -443,7 +448,7 @@ export interface IMaterialLoadingAdapter { setNormalMapInversions(invertX: boolean, invertY: boolean): void; /** - * Sets/gets the coat normal texture (OpenPBR: geometryCoatNormalTexture, PBR: clearCoat.bumpTexture) + * Sets/gets the coat normal texture */ geometryCoatNormalTexture: Nullable; diff --git a/packages/dev/loaders/src/glTF/2.0/openpbrMaterialLoadingAdapter.ts b/packages/dev/loaders/src/glTF/2.0/openpbrMaterialLoadingAdapter.ts index e5337595099..9dadffc7003 100644 --- a/packages/dev/loaders/src/glTF/2.0/openpbrMaterialLoadingAdapter.ts +++ b/packages/dev/loaders/src/glTF/2.0/openpbrMaterialLoadingAdapter.ts @@ -797,6 +797,14 @@ export class OpenPBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { this._material.fuzzWeight = value; } + /** + * Sets the fuzz weight texture. + * @param value The fuzz weight texture or null + */ + public set fuzzWeightTexture(value: Nullable) { + this._material.fuzzWeightTexture = value; + } + /** * Sets the fuzz color. * @param value The fuzz color as a Color3 @@ -827,6 +835,7 @@ export class OpenPBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { */ public set fuzzRoughnessTexture(value: Nullable) { this._material.fuzzRoughnessTexture = value; + this._material._useFuzzRoughnessFromTextureAlpha = true; } // ======================================== diff --git a/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts b/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts index 4ce3344b4c4..2856938980d 100644 --- a/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts +++ b/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts @@ -848,6 +848,20 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { this._material.sheen.intensity = value; } + /** + * Sets the fuzz weight texture. + * @param value The fuzz weight texture or null + */ + public set fuzzWeightTexture(value: Nullable) { + // PBRMaterial sheen supports glTF-style sheen which doesn't + // use a separate texture for intensity. So we'll only set the + // weight texture if none is already assigned. If one's already + // assigned, we assume it contains the sheen color data. + if (!this._material.sheen.texture) { + this._material.sheen.texture = value; + } + } + /** * Sets the sheen color (mapped to PBR sheen.color). * Automatically enables sheen. @@ -864,7 +878,6 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { * @param value The sheen color texture or null */ public set fuzzColorTexture(value: Nullable) { - this._material.sheen.isEnabled = true; this._material.sheen.texture = value; } diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_fuzz.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_fuzz.ts new file mode 100644 index 00000000000..f36324fd8ab --- /dev/null +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_fuzz.ts @@ -0,0 +1,236 @@ +import type { IMaterial, IKHRMaterialsFuzz } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; +import { MergeTexturesAsync, CreateRGBAConfiguration, CreateTextureInput, CreateConstantInput } from "core/Materials/Textures/textureMerger"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; +import type { Nullable } from "core/types"; +import type { InternalTexture } from "core/Materials/Textures/internalTexture"; +import { Texture } from "core/Materials/Textures/texture"; + +const NAME = "KHR_materials_fuzz"; + +/** + * Generate a unique ID for the merged coat textures based on the internal texture data. + * This is used for caching merged textures. + * @param babylonMaterial Source OpenPBR material + * @returns A unique ID string for the merged coat textures + * @internal + */ +function GetFuzzColorTextureId(babylonMaterial: OpenPBRMaterial): string { + const fuzzColorTexture: Nullable = babylonMaterial.fuzzColorTexture; + const fuzzColorId = fuzzColorTexture && fuzzColorTexture.getInternalTexture() ? fuzzColorTexture!.getInternalTexture()!.uniqueId : "NoFuzzColor"; + const fuzzRoughnessTexture: Nullable = babylonMaterial.fuzzRoughnessTexture; + const fuzzRoughnessId = fuzzRoughnessTexture && fuzzRoughnessTexture.getInternalTexture() ? fuzzRoughnessTexture!.getInternalTexture()!.uniqueId : "NoFuzzRoughness"; + return `FuzzColor_${fuzzColorId}_FuzzRoughness_${fuzzRoughnessId}`; +} + +/** + * Using the coat weight and coat roughness textures, create a merged internal texture that can be used + * for multiple textures (with potentially different transforms) on export. + * @param babylonMaterial The source OpenPBR material + * @returns A new, internal texture with the coat weight in the red channel and coat roughness in the green channel + * @internal + */ +async function CreateMergedFuzzInternalTexture(babylonMaterial: OpenPBRMaterial): Promise> { + const scene = babylonMaterial.getScene(); + const fuzzColorTexture: Nullable = babylonMaterial.fuzzColorTexture; + const fuzzRoughnessTexture: Nullable = babylonMaterial.fuzzRoughnessTexture; + // If we don't have any textures, we don't need to generate anything. + if (!(fuzzColorTexture || fuzzRoughnessTexture)) { + return null; + } + + const texture = await MergeTexturesAsync( + "FuzzTexture", + CreateRGBAConfiguration( + fuzzColorTexture ? CreateTextureInput(fuzzColorTexture, 0) : CreateConstantInput(1.0), // fuzz color from red channel + fuzzColorTexture ? CreateTextureInput(fuzzColorTexture, 1) : CreateConstantInput(1.0), // fuzz color from green channel + fuzzColorTexture ? CreateTextureInput(fuzzColorTexture, 2) : CreateConstantInput(1.0), // fuzz color from blue channel + // fuzz roughness goes in the alpha channel but may come from red or alpha channels in the source + fuzzRoughnessTexture ? CreateTextureInput(fuzzRoughnessTexture, babylonMaterial._useFuzzRoughnessFromTextureAlpha ? 3 : 0) : CreateConstantInput(1.0) + ), + scene + ); + + return texture.getInternalTexture(); +} + +/** + * Creates a temporary texture based on the source texture. + * @param internalTexture The source internal texture + * @param sourceTexture The source of the new texture's name, and sampler info + * @returns The new texture + */ +function CreateTempTexture(internalTexture: InternalTexture, sourceTexture: BaseTexture): Texture { + const tempTexture = new Texture(sourceTexture.name, sourceTexture.getScene()); + tempTexture._texture = internalTexture; + tempTexture.coordinatesIndex = sourceTexture.coordinatesIndex; + if (sourceTexture instanceof Texture) { + tempTexture.uOffset = sourceTexture.uOffset; + tempTexture.vOffset = sourceTexture.vOffset; + tempTexture.uScale = sourceTexture.uScale; + tempTexture.vScale = sourceTexture.vScale; + tempTexture.wAng = sourceTexture.wAng; + } + tempTexture.wrapU = sourceTexture.wrapU; + tempTexture.wrapV = sourceTexture.wrapV; + return tempTexture; +} + +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_fuzz 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; + + private _wasUsed = false; + + private _exporter: GLTFExporter; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + /** + * Cache that holds temporary merged textures created during export + */ + private _mergedTexturesMap: Record = {}; + + /** + * Cache that holds internal textures of merged textures created during export + */ + private _cachedInternalTexturesMap: Record = {}; + + public dispose() { + for (const key of Object.keys(this._mergedTexturesMap)) { + const texture = this._mergedTexturesMap[key]; + texture.dispose(); + } + this._mergedTexturesMap = {}; + for (const key of Object.keys(this._cachedInternalTexturesMap)) { + const internalTexture = this._cachedInternalTexturesMap[key]; + internalTexture.dispose(); + } + this._cachedInternalTexturesMap = {}; + } + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + public async postExportMaterialAdditionalTexturesAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { + if (babylonMaterial instanceof OpenPBRMaterial) { + const additionalTextures: BaseTexture[] = []; + if (babylonMaterial.fuzzWeight > 0.0) { + if (babylonMaterial.fuzzWeightTexture) { + additionalTextures.push(babylonMaterial.fuzzWeightTexture); + } + let fuzzTexturesNeedMerge = false; + if (babylonMaterial.fuzzRoughnessTexture) { + if (babylonMaterial._useFuzzRoughnessFromTextureAlpha) { + additionalTextures.push(babylonMaterial.fuzzRoughnessTexture); + } else { + fuzzTexturesNeedMerge = true; + } + } + if (babylonMaterial.fuzzColorTexture && !fuzzTexturesNeedMerge) { + additionalTextures.push(babylonMaterial.fuzzColorTexture); + } + if (fuzzTexturesNeedMerge) { + const texId = GetFuzzColorTextureId(babylonMaterial); + if (!this._cachedInternalTexturesMap[texId]) { + const mergedInternalTexture = await CreateMergedFuzzInternalTexture(babylonMaterial); + if (mergedInternalTexture) { + this._cachedInternalTexturesMap[texId] = mergedInternalTexture; + } + } + if (this._cachedInternalTexturesMap[texId]) { + if (babylonMaterial.fuzzColorTexture) { + this._mergedTexturesMap[babylonMaterial.fuzzColorTexture.uniqueId] = CreateTempTexture( + this._cachedInternalTexturesMap[texId], + babylonMaterial.fuzzColorTexture + ); + additionalTextures.push(this._mergedTexturesMap[babylonMaterial.fuzzColorTexture.uniqueId]); + } + if (babylonMaterial.fuzzRoughnessTexture) { + this._mergedTexturesMap[babylonMaterial.fuzzRoughnessTexture.uniqueId] = CreateTempTexture( + this._cachedInternalTexturesMap[texId], + babylonMaterial.fuzzRoughnessTexture + ); + additionalTextures.push(this._mergedTexturesMap[babylonMaterial.fuzzRoughnessTexture.uniqueId]); + } + } + } + } + return additionalTextures; + } + + return []; + } + + public async postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return await new Promise((resolve) => { + if (babylonMaterial instanceof OpenPBRMaterial) { + if (babylonMaterial.fuzzWeight == 0.0) { + resolve(node); + return; + } + + this._wasUsed = true; + + if (node.extensions == null) { + node.extensions = {}; + } + const fuzzInfo: IKHRMaterialsFuzz = { + fuzzFactor: babylonMaterial.fuzzWeight, + fuzzColorFactor: babylonMaterial.fuzzColor.asArray(), + fuzzRoughnessFactor: babylonMaterial.fuzzRoughness, + }; + + if (babylonMaterial.fuzzWeightTexture) { + fuzzInfo.fuzzTexture = this._exporter._materialExporter.getTextureInfo(babylonMaterial.fuzzWeightTexture) ?? undefined; + } + + let fuzzColorTexture: Nullable = null; + if (babylonMaterial.fuzzColorTexture) { + if (this._mergedTexturesMap[babylonMaterial.fuzzColorTexture.uniqueId]) { + fuzzColorTexture = this._mergedTexturesMap[babylonMaterial.fuzzColorTexture.uniqueId]; + } else { + fuzzColorTexture = babylonMaterial.fuzzColorTexture; + } + fuzzInfo.fuzzColorTexture = this._exporter._materialExporter.getTextureInfo(fuzzColorTexture) ?? undefined; + } + + let fuzzRoughnessTexture: Nullable = null; + if (babylonMaterial.fuzzRoughnessTexture) { + if (this._mergedTexturesMap[babylonMaterial.fuzzRoughnessTexture.uniqueId]) { + fuzzRoughnessTexture = this._mergedTexturesMap[babylonMaterial.fuzzRoughnessTexture.uniqueId]; + } else { + fuzzRoughnessTexture = babylonMaterial.fuzzRoughnessTexture; + } + fuzzInfo.fuzzRoughnessTexture = this._exporter._materialExporter.getTextureInfo(fuzzRoughnessTexture) ?? undefined; + } + + if (fuzzInfo.fuzzColorTexture !== null || fuzzInfo.fuzzRoughnessTexture !== null) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + node.extensions[NAME] = fuzzInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_fuzz(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts index 4744c1f056f..99aa2b6968a 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts @@ -14,6 +14,7 @@ export * from "./KHR_materials_emissive_strength"; export * from "./KHR_materials_ior"; export * from "./KHR_materials_iridescence"; export * from "./KHR_materials_sheen"; +export * from "./KHR_materials_fuzz"; export * from "./KHR_materials_specular"; export * from "./KHR_materials_transmission"; export * from "./KHR_materials_unlit"; diff --git a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts index dabd8be27cc..a8a055bdf90 100644 --- a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts +++ b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts @@ -1233,6 +1233,20 @@ declare module BABYLON.GLTF2 { sheenRoughnessTexture?: ITextureInfo; } + /** + * Interfaces from the KHR_materials_fuzz extension + */ + + /** @internal */ + interface IKHRMaterialsFuzz { + fuzzFactor?: number; + fuzzTexture?: ITextureInfo; + fuzzColorFactor?: number[]; + fuzzColorTexture?: ITextureInfo; + fuzzRoughnessFactor?: number; + fuzzRoughnessTexture?: ITextureInfo; + } + /** * Interfaces from the KHR_materials_diffuse_transmission extension * !!! Experimental Extension Subject to Changes !!!