diff --git a/packages/dev/core/src/Materials/Node/Blocks/PBR/subSurfaceBlock.ts b/packages/dev/core/src/Materials/Node/Blocks/PBR/subSurfaceBlock.ts index 6daee5662e9..ea6cb27a65a 100644 --- a/packages/dev/core/src/Materials/Node/Blocks/PBR/subSurfaceBlock.ts +++ b/packages/dev/core/src/Materials/Node/Blocks/PBR/subSurfaceBlock.ts @@ -37,6 +37,7 @@ export class SubSurfaceBlock extends NodeMaterialBlock { NodeMaterialBlockTargets.Fragment, new NodeMaterialConnectionPointCustomObject("refraction", this, NodeMaterialConnectionPointDirection.Input, RefractionBlock, "RefractionBlock") ); + this.registerInput("dispersion", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment); this.registerOutput( "subsurface", @@ -55,6 +56,7 @@ export class SubSurfaceBlock extends NodeMaterialBlock { state._excludeVariableName("vThicknessParam"); state._excludeVariableName("vTintColor"); state._excludeVariableName("vSubSurfaceIntensity"); + state._excludeVariableName("dispersion"); } /** @@ -100,6 +102,13 @@ export class SubSurfaceBlock extends NodeMaterialBlock { return this._inputs[4]; } + /** + * Gets the dispersion input component + */ + public get dispersion(): NodeMaterialConnectionPoint { + return this._inputs[5]; + } + /** * Gets the sub surface object output component */ @@ -127,6 +136,7 @@ export class SubSurfaceBlock extends NodeMaterialBlock { defines.setValue("SS_TRANSLUCENCYINTENSITY_TEXTURE", false, true); defines.setValue("SS_MASK_FROM_THICKNESS_TEXTURE", false, true); defines.setValue("SS_USE_GLTF_TEXTURES", false, true); + defines.setValue("SS_DISPERSION", this.dispersion.isConnected, true); } /** @@ -151,6 +161,8 @@ export class SubSurfaceBlock extends NodeMaterialBlock { const refractionIntensity = refractionBlock?.intensity.isConnected ? refractionBlock.intensity.associatedVariableName : "1."; const refractionView = refractionBlock?.view.isConnected ? refractionBlock.view.associatedVariableName : ""; + const dispersion = ssBlock?.dispersion.isConnected ? ssBlock?.dispersion.associatedVariableName : "0.0"; + code += refractionBlock?.getCode(state) ?? ""; code += `subSurfaceOutParams subSurfaceOut; @@ -159,7 +171,7 @@ export class SubSurfaceBlock extends NodeMaterialBlock { vec2 vThicknessParam = vec2(0., ${thickness}); vec4 vTintColor = vec4(${tintColor}, ${refractionTintAtDistance}); vec3 vSubSurfaceIntensity = vec3(${refractionIntensity}, ${translucencyIntensity}, 0.); - + float dispersion = ${dispersion}; subSurfaceBlock( vSubSurfaceIntensity, vThicknessParam, @@ -231,6 +243,9 @@ export class SubSurfaceBlock extends NodeMaterialBlock { vRefractionPosition, vRefractionSize, #endif + #ifdef SS_DISPERSION + dispersion, + #endif #endif #ifdef SS_TRANSLUCENCY ${translucencyDiffusionDistance}, diff --git a/packages/dev/core/src/Materials/PBR/pbrSubSurfaceConfiguration.ts b/packages/dev/core/src/Materials/PBR/pbrSubSurfaceConfiguration.ts index 773ae1c4b84..9df90711c33 100644 --- a/packages/dev/core/src/Materials/PBR/pbrSubSurfaceConfiguration.ts +++ b/packages/dev/core/src/Materials/PBR/pbrSubSurfaceConfiguration.ts @@ -33,6 +33,7 @@ export class MaterialSubSurfaceDefines extends MaterialDefines { public SS_TRANSLUCENCY = false; public SS_TRANSLUCENCY_USE_INTENSITY_FROM_TEXTURE = false; public SS_SCATTERING = false; + public SS_DISPERSION = false; public SS_THICKNESSANDMASK_TEXTURE = false; public SS_THICKNESSANDMASK_TEXTUREDIRECTUV = 0; @@ -80,6 +81,14 @@ export class PBRSubSurfaceConfiguration extends MaterialPluginBase { @expandToProperty("_markAllSubMeshesAsTexturesDirty") public isTranslucencyEnabled = false; + private _isDispersionEnabled = false; + /** + * Defines if dispersion is enabled in the material. + */ + @serialize() + @expandToProperty("_markAllSubMeshesAsTexturesDirty") + public isDispersionEnabled = false; + private _isScatteringEnabled = false; /** * Defines if the sub surface scattering is enabled in the material. @@ -253,6 +262,12 @@ export class PBRSubSurfaceConfiguration extends MaterialPluginBase { @serialize() public tintColorAtDistance = 1; + /** + * Defines the Abbe number for the volume. + */ + @serialize() + public dispersion = 0; + /** * Defines how far each channel transmit through the media. * It is defined as a color to simplify it selection. @@ -354,6 +369,7 @@ export class PBRSubSurfaceConfiguration extends MaterialPluginBase { public prepareDefinesBeforeAttributes(defines: MaterialSubSurfaceDefines, scene: Scene): void { if (!this._isRefractionEnabled && !this._isTranslucencyEnabled && !this._isScatteringEnabled) { defines.SUBSURFACE = false; + defines.SS_DISPERSION = false; defines.SS_TRANSLUCENCY = false; defines.SS_SCATTERING = false; defines.SS_REFRACTION = false; @@ -385,6 +401,7 @@ export class PBRSubSurfaceConfiguration extends MaterialPluginBase { if (defines._areTexturesDirty) { defines.SUBSURFACE = true; + defines.SS_DISPERSION = this._isDispersionEnabled; defines.SS_TRANSLUCENCY = this._isTranslucencyEnabled; defines.SS_TRANSLUCENCY_USE_INTENSITY_FROM_TEXTURE = false; defines.SS_SCATTERING = this._isScatteringEnabled; @@ -562,6 +579,8 @@ export class PBRSubSurfaceConfiguration extends MaterialPluginBase { uniformBuffer.updateFloat4("vTintColor", this.tintColor.r, this.tintColor.g, this.tintColor.b, Math.max(0.00001, this.tintColorAtDistance)); uniformBuffer.updateFloat3("vSubSurfaceIntensity", this.refractionIntensity, this.translucencyIntensity, 0); + + uniformBuffer.updateFloat("dispersion", this.dispersion); } // Textures @@ -715,6 +734,7 @@ export class PBRSubSurfaceConfiguration extends MaterialPluginBase { { name: "vRefractionPosition", size: 3, type: "vec3" }, { name: "vRefractionSize", size: 3, type: "vec3" }, { name: "scatteringDiffusionProfile", size: 1, type: "float" }, + { name: "dispersion", size: 1, type: "float" }, ], }; } diff --git a/packages/dev/core/src/Shaders/ShadersInclude/pbrBlockSubSurface.fx b/packages/dev/core/src/Shaders/ShadersInclude/pbrBlockSubSurface.fx index b4933bc9fb9..221f89a781e 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/pbrBlockSubSurface.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/pbrBlockSubSurface.fx @@ -32,6 +32,127 @@ struct subSurfaceOutParams #ifdef SUBSURFACE #define pbr_inline #define inline + #ifdef SS_REFRACTION + vec4 sampleEnvironmentRefraction( + in float ior + , in float thickness + , in float refractionLOD + , in vec3 normalW + , in vec3 vPositionW + , in vec3 viewDirectionW + , in mat4 view + , in vec4 vRefractionInfos + , in mat4 refractionMatrix + , in vec4 vRefractionMicrosurfaceInfos + , in float alphaG + #ifdef SS_REFRACTIONMAP_3D + , in samplerCube refractionSampler + #ifndef LODBASEDMICROSFURACE + , in samplerCube refractionSamplerLow + , in samplerCube refractionSamplerHigh + #endif + #else + , in sampler2D refractionSampler + #ifndef LODBASEDMICROSFURACE + , in sampler2D refractionSamplerLow + , in sampler2D refractionSamplerHigh + #endif + #endif + #ifdef ANISOTROPIC + , in anisotropicOutParams anisotropicOut + #endif + #ifdef REALTIME_FILTERING + , in vec2 vRefractionFilteringInfo + #endif + #ifdef SS_USE_LOCAL_REFRACTIONMAP_CUBIC + , in vec3 refractionPosition + , in vec3 refractionSize + #endif + ) { + vec4 environmentRefraction = vec4(0., 0., 0., 0.); + #ifdef ANISOTROPIC + vec3 refractionVector = refract(-viewDirectionW, anisotropicOut.anisotropicNormal, ior); + #else + vec3 refractionVector = refract(-viewDirectionW, normalW, ior); + #endif + + #ifdef SS_REFRACTIONMAP_OPPOSITEZ + refractionVector.z *= -1.0; + #endif + + // _____________________________ 2D vs 3D Maps ________________________________ + #ifdef SS_REFRACTIONMAP_3D + #ifdef SS_USE_LOCAL_REFRACTIONMAP_CUBIC + refractionVector = parallaxCorrectNormal(vPositionW, refractionVector, refractionSize, refractionPosition); + #endif + refractionVector.y = refractionVector.y * vRefractionInfos.w; + vec3 refractionCoords = refractionVector; + refractionCoords = vec3(refractionMatrix * vec4(refractionCoords, 0)); + #else + #ifdef SS_USE_THICKNESS_AS_DEPTH + vec3 vRefractionUVW = vec3(refractionMatrix * (view * vec4(vPositionW + refractionVector * thickness, 1.0))); + #else + vec3 vRefractionUVW = vec3(refractionMatrix * (view * vec4(vPositionW + refractionVector * vRefractionInfos.z, 1.0))); + #endif + vec2 refractionCoords = vRefractionUVW.xy / vRefractionUVW.z; + refractionCoords.y = 1.0 - refractionCoords.y; + #endif + + #ifdef LODBASEDMICROSFURACE + // Apply environment convolution scale/offset filter tuning parameters to the mipmap LOD selection + refractionLOD = refractionLOD * vRefractionMicrosurfaceInfos.y + vRefractionMicrosurfaceInfos.z; + + #ifdef SS_LODINREFRACTIONALPHA + // Automatic LOD adjustment to ensure that the smoothness-based environment LOD selection + // is constrained to appropriate LOD levels in order to prevent aliasing. + // The environment map is first sampled without custom LOD selection to determine + // the hardware-selected LOD, and this is then used to constrain the final LOD selection + // so that excessive surface smoothness does not cause aliasing (e.g. on curved geometry + // where the normal is varying rapidly). + + // Note: Shader Model 4.1 or higher can provide this directly via CalculateLevelOfDetail(), and + // manual calculation via derivatives is also possible, but for simplicity we use the + // hardware LOD calculation with the alpha channel containing the LOD for each mipmap. + float automaticRefractionLOD = UNPACK_LOD(sampleRefraction(refractionSampler, refractionCoords).a); + float requestedRefractionLOD = max(automaticRefractionLOD, refractionLOD); + #else + float requestedRefractionLOD = refractionLOD; + #endif + + #if defined(REALTIME_FILTERING) && defined(SS_REFRACTIONMAP_3D) + environmentRefraction = vec4(radiance(alphaG, refractionSampler, refractionCoords, vRefractionFilteringInfo), 1.0); + #else + environmentRefraction = sampleRefractionLod(refractionSampler, refractionCoords, requestedRefractionLOD); + #endif + #else + float lodRefractionNormalized = saturate(refractionLOD / log2(vRefractionMicrosurfaceInfos.x)); + float lodRefractionNormalizedDoubled = lodRefractionNormalized * 2.0; + + vec4 environmentRefractionMid = sampleRefraction(refractionSampler, refractionCoords); + if (lodRefractionNormalizedDoubled < 1.0){ + environmentRefraction = mix( + sampleRefraction(refractionSamplerHigh, refractionCoords), + environmentRefractionMid, + lodRefractionNormalizedDoubled + ); + } else { + environmentRefraction = mix( + environmentRefractionMid, + sampleRefraction(refractionSamplerLow, refractionCoords), + lodRefractionNormalizedDoubled - 1.0 + ); + } + #endif + #ifdef SS_RGBDREFRACTION + environmentRefraction.rgb = fromRGBD(environmentRefraction); + #endif + + #ifdef SS_GAMMAREFRACTION + environmentRefraction.rgb = toLinearSpace(environmentRefraction.rgb); + #endif + return environmentRefraction; + } + #endif void subSurfaceBlock( in vec3 vSubSurfaceIntensity, in vec2 vThicknessParam, @@ -112,6 +233,9 @@ struct subSurfaceOutParams in vec3 refractionPosition, in vec3 refractionSize, #endif + #ifdef SS_DISPERSION + in float dispersion, + #endif #endif #ifdef SS_TRANSLUCENCY in vec3 vDiffusionDistance, @@ -192,34 +316,6 @@ struct subSurfaceOutParams #ifdef SS_REFRACTION vec4 environmentRefraction = vec4(0., 0., 0., 0.); - #ifdef ANISOTROPIC - vec3 refractionVector = refract(-viewDirectionW, anisotropicOut.anisotropicNormal, vRefractionInfos.y); - #else - vec3 refractionVector = refract(-viewDirectionW, normalW, vRefractionInfos.y); - #endif - - #ifdef SS_REFRACTIONMAP_OPPOSITEZ - refractionVector.z *= -1.0; - #endif - - // _____________________________ 2D vs 3D Maps ________________________________ - #ifdef SS_REFRACTIONMAP_3D - #ifdef SS_USE_LOCAL_REFRACTIONMAP_CUBIC - refractionVector = parallaxCorrectNormal(vPositionW, refractionVector, refractionSize, refractionPosition); - #endif - refractionVector.y = refractionVector.y * vRefractionInfos.w; - vec3 refractionCoords = refractionVector; - refractionCoords = vec3(refractionMatrix * vec4(refractionCoords, 0)); - #else - #ifdef SS_USE_THICKNESS_AS_DEPTH - vec3 vRefractionUVW = vec3(refractionMatrix * (view * vec4(vPositionW + refractionVector * thickness, 1.0))); - #else - vec3 vRefractionUVW = vec3(refractionMatrix * (view * vec4(vPositionW + refractionVector * vRefractionInfos.z, 1.0))); - #endif - vec2 refractionCoords = vRefractionUVW.xy / vRefractionUVW.z; - refractionCoords.y = 1.0 - refractionCoords.y; - #endif - // vRefractionInfos.y is the IOR of the volume. // vRefractionMicrosurfaceInfos.w is the IOR of the surface. #ifdef SS_HAS_THICKNESS @@ -243,58 +339,45 @@ struct subSurfaceOutParams float refractionLOD = getLodFromAlphaG(vRefractionMicrosurfaceInfos.x, refractionAlphaG); #endif - #ifdef LODBASEDMICROSFURACE - // Apply environment convolution scale/offset filter tuning parameters to the mipmap LOD selection - refractionLOD = refractionLOD * vRefractionMicrosurfaceInfos.y + vRefractionMicrosurfaceInfos.z; - - #ifdef SS_LODINREFRACTIONALPHA - // Automatic LOD adjustment to ensure that the smoothness-based environment LOD selection - // is constrained to appropriate LOD levels in order to prevent aliasing. - // The environment map is first sampled without custom LOD selection to determine - // the hardware-selected LOD, and this is then used to constrain the final LOD selection - // so that excessive surface smoothness does not cause aliasing (e.g. on curved geometry - // where the normal is varying rapidly). - - // Note: Shader Model 4.1 or higher can provide this directly via CalculateLevelOfDetail(), and - // manual calculation via derivatives is also possible, but for simplicity we use the - // hardware LOD calculation with the alpha channel containing the LOD for each mipmap. - float automaticRefractionLOD = UNPACK_LOD(sampleRefraction(refractionSampler, refractionCoords).a); - float requestedRefractionLOD = max(automaticRefractionLOD, refractionLOD); - #else - float requestedRefractionLOD = refractionLOD; - #endif - - #if defined(REALTIME_FILTERING) && defined(SS_REFRACTIONMAP_3D) - environmentRefraction = vec4(radiance(alphaG, refractionSampler, refractionCoords, vRefractionFilteringInfo), 1.0); - #else - environmentRefraction = sampleRefractionLod(refractionSampler, refractionCoords, requestedRefractionLOD); - #endif - #else - float lodRefractionNormalized = saturate(refractionLOD / log2(vRefractionMicrosurfaceInfos.x)); - float lodRefractionNormalizedDoubled = lodRefractionNormalized * 2.0; - - vec4 environmentRefractionMid = sampleRefraction(refractionSampler, refractionCoords); - if (lodRefractionNormalizedDoubled < 1.0){ - environmentRefraction = mix( - sampleRefraction(refractionSamplerHigh, refractionCoords), - environmentRefractionMid, - lodRefractionNormalizedDoubled - ); - } else { - environmentRefraction = mix( - environmentRefractionMid, - sampleRefraction(refractionSamplerLow, refractionCoords), - lodRefractionNormalizedDoubled - 1.0 + #ifdef SS_DISPERSION + float realIOR = 1.0 / ior; + // The 0.04 value is completely empirical + float iorDispersionSpread = 0.04 * dispersion * (realIOR - 1.0); + vec3 iors = vec3(1.0/(realIOR - iorDispersionSpread), ior, 1.0/(realIOR + iorDispersionSpread)); + for (int i = 0; i < 3; i++) { + ior = iors[i]; + #endif + vec4 envSample = sampleEnvironmentRefraction(ior, thickness, refractionLOD, normalW, vPositionW, viewDirectionW, view, vRefractionInfos, refractionMatrix, vRefractionMicrosurfaceInfos, alphaG + #ifdef SS_REFRACTIONMAP_3D + , refractionSampler + #ifndef LODBASEDMICROSFURACE + , refractionSamplerLow + , refractionSamplerHigh + #endif + #else + , refractionSampler + #ifndef LODBASEDMICROSFURACE + , refractionSamplerLow + , refractionSamplerHigh + #endif + #endif + #ifdef ANISOTROPIC + , anisotropicOut + #endif + #ifdef REALTIME_FILTERING + , vRefractionFilteringInfo + #endif + #ifdef SS_USE_LOCAL_REFRACTIONMAP_CUBIC + , refractionPosition + , refractionSize + #endif ); + + #ifdef SS_DISPERSION + environmentRefraction[i] = envSample[i]; } - #endif - - #ifdef SS_RGBDREFRACTION - environmentRefraction.rgb = fromRGBD(environmentRefraction); - #endif - - #ifdef SS_GAMMAREFRACTION - environmentRefraction.rgb = toLinearSpace(environmentRefraction.rgb); + #else + environmentRefraction = envSample; #endif // _____________________________ Levels _____________________________________ diff --git a/packages/dev/core/src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx b/packages/dev/core/src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx index c62b8ff9864..e5f379b8b52 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/pbrFragmentDeclaration.fx @@ -164,6 +164,9 @@ uniform mat4 view; #ifdef REALTIME_FILTERING uniform vec2 vRefractionFilteringInfo; #endif + #ifdef SS_DISPERSION + uniform float dispersion; + #endif #endif #ifdef SS_THICKNESSANDMASK_TEXTURE diff --git a/packages/dev/core/src/Shaders/pbr.fragment.fx b/packages/dev/core/src/Shaders/pbr.fragment.fx index 5b8666988f6..c3f5c2457ee 100644 --- a/packages/dev/core/src/Shaders/pbr.fragment.fx +++ b/packages/dev/core/src/Shaders/pbr.fragment.fx @@ -586,6 +586,9 @@ void main(void) { vRefractionPosition, vRefractionSize, #endif + #ifdef SS_DISPERSION + dispersion, + #endif #endif #ifdef SS_TRANSLUCENCY vDiffusionDistance, diff --git a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx index 235dfd23be6..36cd81acd63 100644 --- a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx +++ b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/pbrMaterialPropertyGridComponent.tsx @@ -882,6 +882,27 @@ export class PBRMaterialPropertyGridComponent extends React.Component )} + this.forceUpdate()} + onPropertyChangedObservable={this.props.onPropertyChangedObservable} + /> + {material.subSurface.isDispersionEnabled && ( +
+ +
+ )} { isSelected={() => extensionStates["KHR_materials_volume"].enabled} onSelect={(value) => (extensionStates["KHR_materials_volume"].enabled = value)} /> + extensionStates["KHR_materials_dispersion"].enabled} + onSelect={(value) => (extensionStates["KHR_materials_dispersion"].enabled = value)} + /> extensionStates["KHR_mesh_quantization"].enabled} diff --git a/packages/dev/inspector/src/components/globalState.ts b/packages/dev/inspector/src/components/globalState.ts index 2853bdff98f..b31c1bf17d5 100644 --- a/packages/dev/inspector/src/components/globalState.ts +++ b/packages/dev/inspector/src/components/globalState.ts @@ -54,6 +54,7 @@ export class GlobalState { KHR_materials_transmission: { enabled: true }, KHR_materials_translucency: { enabled: true }, KHR_materials_volume: { enabled: true }, + KHR_materials_dispersion: { enabled: true }, KHR_lights_punctual: { enabled: true }, KHR_texture_basisu: { enabled: true }, KHR_texture_transform: { enabled: true }, diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.data.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.data.ts index b4e84ceebf7..fc5df6c87a4 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.data.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.data.ts @@ -161,6 +161,9 @@ const materialsTree = { attenuationDistance: [new MaterialAnimationPropertyInfo(Animation.ANIMATIONTYPE_FLOAT, "subSurface.tintColorAtDistance", getFloat, () => 1)], thicknessFactor: [new MaterialAnimationPropertyInfo(Animation.ANIMATIONTYPE_FLOAT, "subSurface.maximumThickness", getFloat, () => 1)], }, + KHR_materials_dispersion: { + dispersion: [new MaterialAnimationPropertyInfo(Animation.ANIMATIONTYPE_FLOAT, "subSurface.dispersion", getFloat, () => 1)], + }, KHR_materials_iridescence: { iridescenceFactor: [new MaterialAnimationPropertyInfo(Animation.ANIMATIONTYPE_FLOAT, "iridescence.intensity", getFloat, () => 1)], iridescenceIor: [new MaterialAnimationPropertyInfo(Animation.ANIMATIONTYPE_FLOAT, "iridescence.indexOfRefraction", getFloat, () => 1)], diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts new file mode 100644 index 00000000000..e36607a6088 --- /dev/null +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type { Nullable } from "core/types"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { Material } from "core/Materials/material"; +import type { IMaterial } from "../glTFLoaderInterfaces"; +import type { IGLTFLoaderExtension } from "../glTFLoaderExtension"; +import { GLTFLoader } from "../glTFLoader"; +import type { IKHRMaterialsDispersion } from "babylonjs-gltf2interface"; + +const NAME = "KHR_materials_dispersion"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/87bd64a7f5e23c84b6aef2e6082069583ed0ddb4/extensions/2.0/Khronos/KHR_materials_dispersion/README.md) + * @experimental + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_dispersion 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); + } + + /** @internal */ + public dispose() { + (this._loader as any) = null; + } + + /** + * @internal + */ + public loadMaterialPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material): Nullable> { + return GLTFLoader.LoadExtensionAsync(context, material, this.name, (extensionContext, extension) => { + const promises = new Array>(); + promises.push(this._loader.loadMaterialBasePropertiesAsync(context, material, babylonMaterial)); + promises.push(this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial)); + promises.push(this._loadDispersionPropertiesAsync(extensionContext, material, babylonMaterial, extension)); + return Promise.all(promises).then(() => {}); + }); + } + + private _loadDispersionPropertiesAsync(context: string, material: IMaterial, babylonMaterial: Material, extension: IKHRMaterialsDispersion): Promise { + if (!(babylonMaterial instanceof PBRMaterial)) { + throw new Error(`${context}: Material type not supported`); + } + + // If transparency isn't enabled already, this extension shouldn't do anything. + // i.e. it requires either the KHR_materials_transmission or KHR_materials_translucency extensions. + if (!babylonMaterial.subSurface.isRefractionEnabled || !extension.dispersion) { + return Promise.resolve(); + } + babylonMaterial.subSurface.isDispersionEnabled = true; + babylonMaterial.subSurface.dispersion = extension.dispersion; + return Promise.resolve(); + } +} + +GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_dispersion(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 4c6068b79a6..3cc4b9fff16 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts @@ -17,6 +17,7 @@ export * from "./KHR_materials_variants"; export * from "./KHR_materials_transmission"; export * from "./KHR_materials_translucency"; export * from "./KHR_materials_volume"; +export * from "./KHR_materials_dispersion"; export * from "./KHR_mesh_quantization"; export * from "./KHR_texture_basisu"; export * from "./KHR_texture_transform"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts new file mode 100644 index 00000000000..16601bc3821 --- /dev/null +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts @@ -0,0 +1,69 @@ +import type { IMaterial, IKHRMaterialsDispersion } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { _Exporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; + +const NAME = "KHR_materials_dispersion"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/87bd64a7f5e23c84b6aef2e6082069583ed0ddb4/extensions/2.0/Khronos/KHR_materials_dispersion/README.md) + * @experimental + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_dispersion 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; + + /** Constructor */ + constructor() {} + + /** */ + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + private _isExtensionEnabled(mat: PBRMaterial): boolean { + // This extension must not be used on a material that also uses KHR_materials_unlit + if (mat.unlit) { + return false; + } + const subs = mat.subSurface; + // this extension requires refraction to be enabled. + if (!subs.isRefractionEnabled && !subs.isDispersionEnabled) { + return false; + } + return true; + } + + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { + this._wasUsed = true; + + const subs = babylonMaterial.subSurface; + const dispersion = subs.dispersion; + + const dispersionInfo: IKHRMaterialsDispersion = { + dispersion: dispersion, + }; + node.extensions = node.extensions || {}; + node.extensions[NAME] = dispersionInfo; + } + resolve(node); + }); + } +} + +_Exporter.RegisterExtension(NAME, () => new KHR_materials_dispersion()); 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 292e1f5e6cc..b992ff21e63 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts @@ -8,6 +8,7 @@ export * from "./KHR_materials_unlit"; export * from "./KHR_materials_ior"; export * from "./KHR_materials_specular"; export * from "./KHR_materials_volume"; +export * from "./KHR_materials_dispersion"; export * from "./KHR_materials_transmission"; export * from "./EXT_mesh_gpu_instancing"; export * from "./KHR_materials_emissive_strength"; diff --git a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts index a6558fe036d..cc3747cf5b4 100644 --- a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts +++ b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts @@ -1088,6 +1088,15 @@ declare module BABYLON.GLTF2 { attenuationColor?: number[]; } + /** + * Interfaces from the KHR_materials_dispersion extension + */ + + /** @internal */ + interface IKHRMaterialsDispersion extends IMaterialExtension { + dispersion?: number; + } + /** * Interfaces from the KHR_materials_specular extension */