diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts index 197260eed21..19a9d46b50e 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts @@ -79,7 +79,7 @@ export class KHR_materials_diffuse_transmission implements IGLTFLoaderExtension const adapter = this._loader._getOrCreateMaterialAdapter(babylonMaterial); adapter.configureSubsurface(); adapter.subsurfaceWeight = extension.diffuseTransmissionFactor ?? 0; - adapter.subsurfaceColor = extension.diffuseTransmissionColorFactor !== undefined ? Color3.FromArray(extension.diffuseTransmissionColorFactor) : Color3.White(); + adapter.subsurfaceConstantTint = extension.diffuseTransmissionColorFactor !== undefined ? Color3.FromArray(extension.diffuseTransmissionColorFactor) : Color3.White(); const promises = new Array>(); @@ -97,7 +97,7 @@ export class KHR_materials_diffuse_transmission implements IGLTFLoaderExtension promises.push( this._loader.loadTextureInfoAsync(`${context}/diffuseTransmissionColorTexture`, extension.diffuseTransmissionColorTexture).then((texture: BaseTexture) => { texture.name = `${babylonMaterial.name} (Diffuse Transmission Color)`; - adapter.subsurfaceColorTexture = texture; + adapter.subsurfaceConstantTintTexture = texture; }) ); } diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_volume.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_volume.ts index 11861236470..789a5d99642 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_volume.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_volume.ts @@ -2,6 +2,7 @@ import type { Nullable } from "core/types"; import type { Material } from "core/Materials/material"; import { Color3 } from "core/Maths/math.color"; +import { Vector3 } from "core/Maths/math.vector"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { IMaterial, ITextureInfo } from "../glTFLoaderInterfaces"; import type { IGLTFLoaderExtension } from "../glTFLoaderExtension"; @@ -89,9 +90,16 @@ export class KHR_materials_volume implements IGLTFLoaderExtension { return Promise.resolve(); } - adapter.transmissionDepth = extension.attenuationDistance !== undefined ? extension.attenuationDistance : Number.MAX_VALUE; - adapter.transmissionColor = - extension.attenuationColor !== undefined && extension.attenuationColor.length == 3 ? Color3.FromArray(extension.attenuationColor) : Color3.White(); + const attenuationDistance = extension.attenuationDistance !== undefined ? extension.attenuationDistance : Number.MAX_VALUE; + const attenuationColor = extension.attenuationColor !== undefined && extension.attenuationColor.length == 3 ? Color3.FromArray(extension.attenuationColor) : Color3.White(); + // Calculate the attenuation coefficient (i.e. extinction coefficient) + const extinctionCoefficient = new Vector3(-Math.log(attenuationColor.r), -Math.log(attenuationColor.g), -Math.log(attenuationColor.b)); + extinctionCoefficient.scaleInPlace(1 / Math.max(attenuationDistance, 0.001)); + adapter.extinctionCoefficient = extinctionCoefficient; + + adapter.transmissionDepth = attenuationDistance; + adapter.transmissionColor = attenuationColor; + adapter.volumeThickness = extension.thicknessFactor ?? 0; const promises = new Array>(); diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_volume_scatter.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_volume_scatter.ts new file mode 100644 index 00000000000..cd36c825547 --- /dev/null +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_volume_scatter.ts @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { Nullable } from "core/types"; +import type { Material } from "core/Materials/material"; +import { Color3 } from "core/Maths/math.color"; +import { Vector3 } from "core/Maths/math.vector"; +import type { IMaterial } from "../glTFLoaderInterfaces"; +import type { IGLTFLoaderExtension } from "../glTFLoaderExtension"; +import { GLTFLoader } from "../glTFLoader"; +import type { IKHRMaterialsVolumeScatter } from "babylonjs-gltf2interface"; +import { registerGLTFExtension, unregisterGLTFExtension } from "../glTFLoaderExtensionRegistry"; + +const NAME = "KHR_materials_volume_scatter"; + +declare module "../../glTFFileLoader" { + // eslint-disable-next-line jsdoc/require-jsdoc, @typescript-eslint/naming-convention + export interface GLTFLoaderExtensionOptions { + /** + * Defines options for the KHR_materials_volume_scatter extension. + */ + // NOTE: Don't use NAME here as it will break the UMD type declarations. + ["KHR_materials_volume_scatter"]: {}; + } +} + +function multiScatterToSingleScatterAlbedo(multiScatter: Color3): Vector3 { + const multiScatterAlbedo = new Vector3(multiScatter.r, multiScatter.g, multiScatter.b); + const s: Vector3 = new Vector3(4.09712, 4.09712, 4.09712); + s.multiplyInPlace(new Vector3(4.20863, 4.20863, 4.20863).multiplyInPlace(multiScatterAlbedo)); + + const p: Vector3 = new Vector3(9.59217, 9.59217, 9.59217); + p.addInPlace(new Vector3(41.6808, 41.6808, 41.6808).multiplyInPlace(multiScatterAlbedo)); + p.addInPlace(new Vector3(17.7126, 17.7126, 17.7126).multiplyInPlace(multiScatterAlbedo.multiply(multiScatterAlbedo))); + s.subtractInPlace(new Vector3(Math.sqrt(p.x), Math.sqrt(p.y), Math.sqrt(p.z))); + return new Vector3(1.0, 1.0, 1.0).subtract(s.multiply(s)); +} + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume_scatter/README.md) + * @since 5.0.0 + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_volume_scatter 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 = 174; + + private _loader: GLTFLoader; + + /** + * @internal + */ + constructor(loader: GLTFLoader) { + this._loader = loader; + this.enabled = this._loader.isExtensionUsed(NAME); + if (this.enabled) { + // We need to disable instance usage because the attenuation factor depends on the node scale of each individual mesh + this._loader._disableInstancedMesh++; + } + } + + /** @internal */ + public dispose() { + if (this.enabled) { + this._loader._disableInstancedMesh--; + } + (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._loadVolumePropertiesAsync(extensionContext, material, babylonMaterial, extension)); + // 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 _loadVolumePropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material, extension: IKHRMaterialsVolumeScatter): Promise { + const adapter = this._loader._getOrCreateMaterialAdapter(babylonMaterial); + + // If transparency isn't enabled already, this extension shouldn't do anything. + // i.e. it requires either the KHR_materials_transmission or KHR_materials_diffuse_transmission extensions. + if (adapter.transmissionWeight === 0 && adapter.subsurfaceWeight === 0) { + return Promise.resolve(); + } + + const scatterColor = extension.multiscatterColor !== undefined && extension.multiscatterColor.length == 3 ? Color3.FromArray(extension.multiscatterColor) : Color3.Black(); + const scatterAnisotropy = extension.scatterAnisotropy !== undefined ? extension.scatterAnisotropy : 0; + + // In glTF, both the translucency volume and subsurface volume use the same input parameters. + // We'll apply them to both, as appropriate. + if (adapter.transmissionWeight > 0) { + const singleScatterAlbedo = multiScatterToSingleScatterAlbedo(scatterColor); + const absorptionCoefficient = adapter.extinctionCoefficient.multiplyByFloats(1.0 - singleScatterAlbedo.x, 1.0 - singleScatterAlbedo.y, 1.0 - singleScatterAlbedo.z); + const scatteringCoefficient = adapter.extinctionCoefficient.multiply(singleScatterAlbedo); + + const maxVal = Math.max(absorptionCoefficient.x, absorptionCoefficient.y, absorptionCoefficient.z); + const absorptionDistance = maxVal !== 0.0 ? 1.0 / maxVal : 1.0; + + adapter.transmissionColor = new Color3( + Math.exp(-absorptionCoefficient.x * absorptionDistance), + Math.exp(-absorptionCoefficient.y * absorptionDistance), + Math.exp(-absorptionCoefficient.z * absorptionDistance) + ); + adapter.transmissionDepth = absorptionDistance; + adapter.transmissionScatter = scatteringCoefficient; + adapter.transmissionScatterAnisotropy = scatterAnisotropy; + } + // Subsurface volume + if (adapter.subsurfaceWeight > 0) { + adapter.subsurfaceScatterAnisotropy = scatterAnisotropy; + adapter.subsurfaceColor = scatterColor; + + const mfp = new Vector3( + adapter.extinctionCoefficient.x !== 0 ? 1.0 / adapter.extinctionCoefficient.x : 1.0, + adapter.extinctionCoefficient.y !== 0 ? 1.0 / adapter.extinctionCoefficient.y : 1.0, + adapter.extinctionCoefficient.z !== 0 ? 1.0 / adapter.extinctionCoefficient.z : 1.0 + ); + + adapter.subsurfaceRadius = Math.max(mfp.x, mfp.y, mfp.z); + adapter.subsurfaceRadiusScale = new Color3(mfp.x / adapter.subsurfaceRadius, mfp.y / adapter.subsurfaceRadius, mfp.z / adapter.subsurfaceRadius); + } + + return Promise.resolve(); + } +} + +unregisterGLTFExtension(NAME); +registerGLTFExtension(NAME, true, (loader) => new KHR_materials_volume_scatter(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 edcf3ae3a0c..4fd29ac8336 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts @@ -25,6 +25,7 @@ export * from "./KHR_materials_variants"; export * from "./KHR_materials_transmission"; export * from "./KHR_materials_diffuse_transmission"; export * from "./KHR_materials_volume"; +export * from "./KHR_materials_volume_scatter"; export * from "./KHR_materials_dispersion"; export * from "./KHR_materials_diffuse_roughness"; export * from "./KHR_mesh_quantization"; diff --git a/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts b/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts index 50f2515cc5c..c973a779e50 100644 --- a/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts +++ b/packages/dev/loaders/src/glTF/2.0/materialLoadingAdapter.ts @@ -2,6 +2,7 @@ import type { Material } from "core/Materials/material"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { Nullable } from "core/types"; import type { Color3 } from "core/Maths/math.color"; +import type { Vector3 } from "core/Maths/math.vector"; /** * Interface for material loading adapters that provides a unified OpenPBR-like interface @@ -56,32 +57,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 +107,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 +137,7 @@ export interface IMaterialLoadingAdapter { specularRoughnessTexture: Nullable; /** - * Sets/gets the specular IOR (OpenPBR: specularIor, PBR: indexOfRefraction) + * Sets/gets the specular IOR */ specularIor: number; @@ -145,12 +146,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 +165,7 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets/gets the ambient occlusion texture (OpenPBR: ambientOcclusionTexture, PBR: ambientTexture) + * Sets/gets the ambient occlusion texture */ ambientOcclusionTexture: Nullable; @@ -183,12 +184,12 @@ 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; @@ -203,12 +204,12 @@ export interface IMaterialLoadingAdapter { 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; @@ -223,17 +224,17 @@ export interface IMaterialLoadingAdapter { 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; @@ -242,25 +243,35 @@ 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; + /** + * Sets the scattering coefficient + */ + transmissionScatter: Vector3; + + /** + * Sets the scattering anisotropy (-1 to 1) + */ + transmissionScatterAnisotropy: number; + /** * Sets the dispersion Abbe number */ @@ -276,12 +287,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; @@ -290,10 +301,16 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Configures subsurface properties for PBR material + * Configures subsurface properties */ configureSubsurface(): void; + /** + * @internal + * Sets/gets the extinction coefficient + */ + extinctionCoefficient: Vector3; + /** * Sets/gets the subsurface weight */ @@ -305,15 +322,40 @@ 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; + /** + * Sets/gets the surface tint of the material (when using subsurface scattering) + */ + subsurfaceConstantTint: Color3; + + /** + * Sets/gets the surface tint texture of the material (when using subsurface scattering) + */ + subsurfaceConstantTintTexture: Nullable; + + /** + * Sets/gets the subsurface radius (used for subsurface scattering) + */ + subsurfaceRadius: number; + + /** + * Sets/gets the subsurface radius scale (used for subsurface scattering) + */ + subsurfaceRadiusScale: Color3; + + /** + * Sets/gets the subsurface scattering anisotropy + */ + subsurfaceScatterAnisotropy: number; + // ======================================== // FUZZ LAYER (Sheen) // ======================================== @@ -324,27 +366,27 @@ 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 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; @@ -353,17 +395,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; @@ -378,32 +420,32 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets the iridescence weight (OpenPBR: iridescenceWeight, PBR: iridescence.intensity) + * Sets the iridescence weight */ iridescenceWeight: number; /** - * Sets the iridescence IOR (OpenPBR: iridescenceIor, PBR: iridescence.indexOfRefraction) + * Sets the iridescence IOR */ iridescenceIor: number; /** - * Sets the iridescence thickness minimum (OpenPBR: iridescenceThicknessMinimum, PBR: iridescence.minimumThickness) + * Sets the iridescence thickness minimum */ iridescenceThicknessMinimum: number; /** - * Sets the iridescence thickness maximum (OpenPBR: iridescenceThicknessMaximum, PBR: iridescence.maximumThickness) + * Sets the iridescence thickness maximum */ iridescenceThicknessMaximum: number; /** - * Sets the iridescence texture (OpenPBR: iridescenceTexture, PBR: iridescence.intensityTexture) + * Sets the iridescence texture */ iridescenceTexture: Nullable; /** - * Sets the iridescence thickness texture (OpenPBR: iridescenceThicknessTexture, PBR: iridescence.thicknessTexture) + * Sets the iridescence thickness texture */ iridescenceThicknessTexture: Nullable; @@ -412,7 +454,7 @@ export interface IMaterialLoadingAdapter { // ======================================== /** - * Sets the unlit flag (OpenPBR: unlit, PBR: unlit) + * Sets the unlit flag */ unlit: boolean; @@ -421,12 +463,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; @@ -438,7 +480,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 48304110f0f..5033eeba44d 100644 --- a/packages/dev/loaders/src/glTF/2.0/openPbrMaterialLoadingAdapter.ts +++ b/packages/dev/loaders/src/glTF/2.0/openPbrMaterialLoadingAdapter.ts @@ -2,7 +2,8 @@ import type { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; import type { Material } from "core/Materials/material"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { Nullable } from "core/types"; -import type { Color3 } from "core/Maths/math.color"; +import { Color3 } from "core/Maths/math.color"; +import { Vector3 } from "core/Maths/math.vector"; import type { IMaterialLoadingAdapter } from "./materialLoadingAdapter"; /** @@ -10,6 +11,7 @@ import type { IMaterialLoadingAdapter } from "./materialLoadingAdapter"; */ export class OpenPBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { private _material: OpenPBRMaterial; + private _extinctionCoefficient: Vector3 = Vector3.Zero(); /** * Creates a new instance of the OpenPBRMaterialLoadingAdapter. @@ -664,6 +666,22 @@ export class OpenPBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { return 0; } + /** + * Sets the transmission scatter coefficient. + * @param value The scatter coefficient as a Vector3 + */ + public set transmissionScatter(value: Vector3) { + // this._material.subSurface.scatterColor = value; + } + + /** + * Sets the transmission scattering anisotropy. + * @param value The anisotropy intensity value (-1 to 1) + */ + public set transmissionScatterAnisotropy(value: number) { + // No direct mapping in PBRMaterial + } + /** * Gets the transmission dispersion Abbe number. * @param value The Abbe number value @@ -735,6 +753,21 @@ export class OpenPBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { // TODO } + /** + * Sets the extinction coefficient of the volume. + * @param value The extinction coefficient as a Vector3 + */ + public set extinctionCoefficient(value: Vector3) { + this._extinctionCoefficient = value; + } + + /** + * Gets the extinction coefficient of the volume. + */ + public get extinctionCoefficient(): Vector3 { + return this._extinctionCoefficient; + } + /** * Sets the subsurface weight */ @@ -770,6 +803,73 @@ export class OpenPBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { // TODO } + /** + * Sets the surface tint of the material (when using subsurface scattering) + */ + public set subsurfaceConstantTint(value: Color3) { + // There is no equivalent in OpenPBR + // Maybe multiply this by subsurfaceColor? + } + + /** + * Gets the surface tint of the material (when using subsurface scattering) + */ + public get subsurfaceConstantTint(): Color3 { + return Color3.White(); + } + + /** + * Sets the surface tint texture of the material (when using subsurface scattering) + */ + public set subsurfaceConstantTintTexture(value: Nullable) { + // There is no equivalent in OpenPBR + // Maybe multiply this by subsurfaceColorTexture? + } + + /** + * Gets the subsurface radius for subsurface scattering. + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + */ + public get subsurfaceRadius(): number { + // TODO + return 0; + } + + /** + * Sets the subsurface radius for subsurface scattering. + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + * @param value The subsurface radius value + */ + public set subsurfaceRadius(value: number) { + // TODO + } + + /** + * Gets the subsurface radius scale for subsurface scattering. + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + */ + public get subsurfaceRadiusScale(): Color3 { + // TODO + return Color3.White(); + } + + /** + * Sets the subsurface radius scale for subsurface scattering. + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + * @param value The subsurface radius scale as a Color3 + */ + public set subsurfaceRadiusScale(value: Color3) { + // TODO + } + + /** + * Sets the subsurface scattering anisotropy. + * @param value The anisotropy intensity value + */ + public set subsurfaceScatterAnisotropy(value: number) { + // TODO + } + // ======================================== // FUZZ LAYER (Sheen) // ======================================== diff --git a/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts b/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts index dc211fe74f5..bf30a4c1466 100644 --- a/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts +++ b/packages/dev/loaders/src/glTF/2.0/pbrMaterialLoadingAdapter.ts @@ -3,6 +3,7 @@ import type { Material } from "core/Materials/material"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { Nullable } from "core/types"; import { Color3 } from "core/Maths/math.color"; +import { Vector3 } from "core/Maths/math.vector"; import { Constants } from "core/Engines/constants"; import type { IMaterialLoadingAdapter } from "./materialLoadingAdapter"; @@ -11,6 +12,7 @@ import type { IMaterialLoadingAdapter } from "./materialLoadingAdapter"; */ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { private _material: PBRMaterial; + private _extinctionCoefficient: Vector3 = Vector3.Zero(); /** * Creates a new instance of the PBRMaterialLoadingAdapter. @@ -673,7 +675,7 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { * @returns The transmission weight value */ public get transmissionWeight(): number { - return this._material.subSurface.refractionIntensity; + return this._material.subSurface.isRefractionEnabled ? this._material.subSurface.refractionIntensity : 0; } /** @@ -692,7 +694,11 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { * @param value The attenuation distance value */ public set transmissionDepth(value: number) { - this._material.subSurface.tintColorAtDistance = value; + if (this.transmissionWeight > 0) { + this._material.subSurface.tintColorAtDistance = value; + } else if (this.subsurfaceWeight > 0) { + this._material.subSurface.diffusionDistance.multiplyInPlace(new Color3(value, value, value)); + } } /** @@ -700,7 +706,27 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { * @param value The attenuation color as a Color3 */ public set transmissionColor(value: Color3) { - this._material.subSurface.tintColor = value; + if (this.transmissionWeight > 0) { + this._material.subSurface.tintColor = value; + } else if (this.subsurfaceWeight > 0) { + this._material.subSurface.diffusionDistance.multiplyInPlace(value); + } + } + + /** + * Sets the transmission scatter coefficient. + * @param value The scatter coefficient as a Vector3 + */ + public set transmissionScatter(value: Vector3) { + // this._material.subSurface.scatterColor = value; + } + + /** + * Sets the transmission scattering anisotropy. + * @param value The anisotropy intensity value (-1 to 1) + */ + public set transmissionScatterAnisotropy(value: number) { + // No direct mapping in PBRMaterial } /** @@ -778,6 +804,21 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { this._material.subSurface.useAlbedoToTintTranslucency = false; } + /** + * Sets the extinction coefficient of the volume. + * @param value The extinction coefficient as a Vector3 + */ + public set extinctionCoefficient(value: Vector3) { + this._extinctionCoefficient = value; + } + + /** + * Gets the extinction coefficient of the volume. + */ + public get extinctionCoefficient(): Vector3 { + return this._extinctionCoefficient; + } + /** * Sets the subsurface weight */ @@ -806,7 +847,19 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { * @param value The subsurface tint color as a Color3 */ public set subsurfaceColor(value: Color3) { - this._material.subSurface.tintColor = value; + // PBRMaterial does not have a direct equivalent for subsurface color, + // We could set the base color to this value, wherever subsurfaceWeight > 0 + // When scatterAnisotropy is 1, I believe we can approximate the subsurface effect quite well with + // Translucency and a diffusion distance + + const absorptionCoeff = this.extinctionCoefficient; + const maxChannel = Math.max(absorptionCoeff.x, Math.max(absorptionCoeff.y, absorptionCoeff.z)); + const attenuationDistance = maxChannel > 0 ? 1.0 / maxChannel : 1; + this._material.subSurface.diffusionDistance = new Color3( + Math.exp(-absorptionCoeff.x * attenuationDistance), + Math.exp(-absorptionCoeff.y * attenuationDistance), + Math.exp(-absorptionCoeff.z * attenuationDistance) + ); } /** @@ -814,9 +867,77 @@ export class PBRMaterialLoadingAdapter implements IMaterialLoadingAdapter { * @param value The subsurface tint texture or null */ public set subsurfaceColorTexture(value: Nullable) { + // PBRMaterial does not have a direct equivalent for subsurface color texture, + } + + /** + * Sets the surface tint of the material (when using subsurface scattering) + */ + public set subsurfaceConstantTint(value: Color3) { + this._material.subSurface.tintColor = value; + } + + /** + * Gets the subsurface constant tint (when using subsurface scattering) + * @returns The subsurface constant tint as a Color3 + */ + public get subsurfaceConstantTint(): Color3 { + return this._material.subSurface.tintColor; + } + + /** + * Sets the subsurface constant tint texture (when using subsurface scattering) + * @param value The subsurface constant tint texture or null + */ + public set subsurfaceConstantTintTexture(value: Nullable) { this._material.subSurface.translucencyColorTexture = value; } + /** + * Gets the subsurface radius (used for subsurface scattering) + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + * @returns The subsurface radius as a Color3 + */ + public get subsurfaceRadius(): number { + return 1.0; + } + + /** + * Sets the subsurface radius (used for subsurface scattering) + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + * @param value The subsurface radius as a number + */ + public set subsurfaceRadius(value: number) { + // + } + + /** + * Gets the subsurface radius scale (used for subsurface scattering) + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + * @returns The subsurface radius scale as a Color3 + */ + public get subsurfaceRadiusScale(): Color3 { + return this._material.subSurface.scatteringDiffusionProfile ?? Color3.White(); + } + + /** + * Sets the subsurface radius scale (used for subsurface scattering) + * subsurfaceRadiusScale * subsurfaceRadius gives the mean free path per color channel. + * @param value The subsurface radius scale as a Color3 + */ + public set subsurfaceRadiusScale(value: Color3) { + this._material.subSurface.scatteringDiffusionProfile = value; + } + + /** + * Sets the subsurface scattering anisotropy. + * Note: PBRMaterial does not have a direct equivalent, so this is a no-op. + * @param value The anisotropy intensity value (ignored for PBR) + */ + public set subsurfaceScatterAnisotropy(value: number) { + // No equivalent in PBRMaterial + } + // ======================================== // FUZZ LAYER (Sheen) // ======================================== diff --git a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts index 62cce7353e3..b78f7269bac 100644 --- a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts +++ b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts @@ -1148,6 +1148,16 @@ declare module BABYLON.GLTF2 { attenuationColor?: number[]; } + /** + * Interfaces from the KHR_materials_volume_scatter extension + */ + + /** @internal */ + interface IKHRMaterialsVolumeScatter { + scatterAnisotropy?: number; + multiscatterColor?: number[]; + } + /** * Interfaces from the KHR_materials_dispersion extension */