diff --git a/packages/dev/core/src/Materials/PBR/index.ts b/packages/dev/core/src/Materials/PBR/index.ts index 1137c63f604..efa54a72600 100644 --- a/packages/dev/core/src/Materials/PBR/index.ts +++ b/packages/dev/core/src/Materials/PBR/index.ts @@ -5,7 +5,7 @@ export * from "./pbrBRDFConfiguration"; export * from "./pbrClearCoatConfiguration"; export * from "./pbrIridescenceConfiguration"; export * from "./pbrMaterial"; -export * from "./openPbrMaterial"; +export * from "./openpbrMaterial"; export * from "./pbrMetallicRoughnessMaterial"; export * from "./pbrSpecularGlossinessMaterial"; export * from "./pbrSheenConfiguration"; diff --git a/packages/dev/core/src/Materials/PBR/openPbrMaterial.ts b/packages/dev/core/src/Materials/PBR/openpbrMaterial.ts similarity index 91% rename from packages/dev/core/src/Materials/PBR/openPbrMaterial.ts rename to packages/dev/core/src/Materials/PBR/openpbrMaterial.ts index cbb99dfd1b2..6ea8b86aaa6 100644 --- a/packages/dev/core/src/Materials/PBR/openPbrMaterial.ts +++ b/packages/dev/core/src/Materials/PBR/openpbrMaterial.ts @@ -1,13 +1,13 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { serialize, expandToProperty, addAccessorsForMaterialProperty } from "../../Misc/decorators"; -import { GetEnvironmentBRDFTexture } from "../../Misc/brdfTextureTools"; +import { GetEnvironmentBRDFTexture, GetEnvironmentFuzzBRDFTexture } from "../../Misc/brdfTextureTools"; import type { Nullable } from "../../types"; import { Scene } from "../../scene"; import type { Color4 } from "../../Maths/math.color"; import { Color3 } from "../../Maths/math.color"; import { ImageProcessingConfiguration } from "../imageProcessingConfiguration"; import type { BaseTexture } from "../../Materials/Textures/baseTexture"; -import { Texture } from "../../Materials/Textures/texture"; +import { Texture } from "../Textures/texture"; import { RegisterClass } from "../../Misc/typeStore"; import { Material } from "../material"; import { SerializationHelper } from "../../Misc/decorators.serialization"; @@ -236,6 +236,7 @@ export class OpenPBRMaterialDefines extends ImageProcessingDefinesMixin(OpenPBRM public ENVIRONMENTBRDF = false; public ENVIRONMENTBRDF_RGBD = false; + public FUZZENVIRONMENTBRDF = false; public NORMAL = false; public TANGENT = false; @@ -244,12 +245,43 @@ export class OpenPBRMaterialDefines extends ImageProcessingDefinesMixin(OpenPBRM public PARALLAX_RHS = false; public PARALLAXOCCLUSION = false; public NORMALXYSCALE = true; - public ANISOTROPIC = false; // Enables anisotropic logic. Still needed because it's used in pbrHelperFunctions - public ANISOTROPIC_OPENPBR = true; // Tells the shader to use OpenPBR's anisotropic roughness remapping - public ANISOTROPIC_BASE = false; // Tells the shader to apply anisotropy to the base layer - public ANISOTROPIC_COAT = false; // Tells the shader to apply anisotropy to the coat layer - public THIN_FILM = false; // Enables thin film layer - public IRIDESCENCE = false; // Enables iridescence layer + /** + * Enables anisotropic logic. Still needed because it's used in pbrHelperFunctions + */ + public ANISOTROPIC = false; + /** + * Tells the shader to use OpenPBR's anisotropic roughness remapping + */ + public ANISOTROPIC_OPENPBR = true; + /** + * Tells the shader to apply anisotropy to the base layer + */ + public ANISOTROPIC_BASE = false; + /** + * Tells the shader to apply anisotropy to the coat layer + */ + public ANISOTROPIC_COAT = false; + + /** + * Number of samples to use for the fuzz IBL lighting calculations + */ + public FUZZ_IBL_SAMPLES = 6; + + /** + * Tells the shader to enable the fuzz layer + */ + public FUZZ = false; + + /** + * Tells the shader to enable the thin film layer + */ + public THIN_FILM = false; + + /** + * Tells the shader to enable the legacy iridescence code + * Iridescence is the name of thin film interference in the PBR material. + */ + public IRIDESCENCE = false; public REFLECTION = false; public REFLECTIONMAP_3D = false; @@ -665,6 +697,60 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { */ public useCoatRoughnessFromWeightTexture: boolean = false; + /** + * Defines the weight of the fuzz layer on the surface. + * See OpenPBR's specs for fuzz_weight + */ + public fuzzWeight: number; + @addAccessorsForMaterialProperty("_markAllSubMeshesAsTexturesDirty", "fuzzWeight") + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private _fuzzWeight: Property = new Property("fuzz_weight", 0.0, "vFuzzWeight", 1, 0); + + /** + * Weight texture of the fuzz layer. + * See OpenPBR's specs for fuzz_weight + */ + public fuzzWeightTexture: Nullable; + @addAccessorsForMaterialProperty("_markAllSubMeshesAsTexturesDirty", "fuzzWeightTexture") + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private _fuzzWeightTexture: Sampler = new Sampler("fuzz_weight", "fuzzWeight", "FUZZ_WEIGHT"); + + /** + * Defines the color of the fuzz layer on the surface. + * See OpenPBR's specs for fuzz_color + */ + public fuzzColor: Color3; + @addAccessorsForMaterialProperty("_markAllSubMeshesAsTexturesDirty", "fuzzColor") + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private _fuzzColor: Property = new Property("fuzz_color", Color3.White(), "vFuzzColor", 3, 0); + + /** + * Color texture of the fuzz layer. + * See OpenPBR's specs for fuzz_color + */ + public fuzzColorTexture: Nullable; + @addAccessorsForMaterialProperty("_markAllSubMeshesAsTexturesDirty", "fuzzColorTexture") + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private _fuzzColorTexture: Sampler = new Sampler("fuzz_color", "fuzzColor", "FUZZ_COLOR"); + + /** + * Defines the roughness of the fuzz layer on the surface. + * See OpenPBR's specs for fuzz_roughness + */ + public fuzzRoughness: number; + @addAccessorsForMaterialProperty("_markAllSubMeshesAsTexturesDirty", "fuzzRoughness") + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private _fuzzRoughness: Property = new Property("fuzz_roughness", 0.5, "vFuzzRoughness", 1, 0); + + /** + * Roughness texture of the fuzz layer. + * See OpenPBR's specs for fuzz_roughness + */ + public fuzzRoughnessTexture: Nullable; + @addAccessorsForMaterialProperty("_markAllSubMeshesAsTexturesDirty", "fuzzRoughnessTexture") + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private _fuzzRoughnessTexture: Sampler = new Sampler("fuzz_roughness", "fuzzRoughness", "FUZZ_ROUGHNESS"); + /** * Defines the normal of the material's geometry. * See OpenPBR's specs for geometry_normal @@ -1291,6 +1377,14 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { */ public _environmentBRDFTexture: Nullable = null; + /** + * Specifies the environment BRDF texture used to compute the scale and offset roughness values + * from cos theta and roughness for the fuzz layer: + * https://github.com/tizian/ltc-sheen?tab=readme-ov-file + * @internal + */ + public _environmentFuzzBRDFTexture: Nullable = null; + /** * Force the shader to compute irradiance in the fragment shader in order to take normal mapping into account. * @internal @@ -1321,6 +1415,19 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { this.markAsDirty(Constants.MATERIAL_TextureDirtyFlag); } + private _fuzzSampleNumber: number = 4; + + /** + * The number of samples used to compute the fuzz IBL lighting. + */ + public get fuzzSampleNumber(): number { + return this._fuzzSampleNumber; + } + public set fuzzSampleNumber(n: number) { + this._fuzzSampleNumber = n; + this.markAsDirty(Constants.MATERIAL_TextureDirtyFlag); + } + /** * Can this material render to several textures at once */ @@ -1441,6 +1548,7 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { }; this._environmentBRDFTexture = GetEnvironmentBRDFTexture(this.getScene()); + this._environmentFuzzBRDFTexture = GetEnvironmentFuzzBRDFTexture(this.getScene()); this.prePassConfiguration = new PrePassConfiguration(); // Build the internal property list that can be used to generate and update the uniform buffer @@ -1513,6 +1621,12 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { this._coatIor; this._coatDarkening; this._coatDarkeningTexture; + this._fuzzWeight; + this._fuzzWeightTexture; + this._fuzzColor; + this._fuzzColorTexture; + this._fuzzRoughness; + this._fuzzRoughnessTexture; this._geometryNormalTexture; this._geometryTangent; this._geometryTangentTexture; @@ -1763,6 +1877,13 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { } } + if (this._environmentFuzzBRDFTexture && MaterialFlags.ReflectionTextureEnabled) { + // This is blocking. + if (!this._environmentFuzzBRDFTexture.isReady()) { + return false; + } + } + if (OpenPBRMaterial._noiseTextures[scene.uniqueId]) { if (!OpenPBRMaterial._noiseTextures[scene.uniqueId].isReady()) { return false; @@ -2005,7 +2126,11 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { ubo.setTexture("environmentBrdfSampler", this._environmentBRDFTexture); } - if (defines.ANISOTROPIC) { + if (defines.FUZZENVIRONMENTBRDF) { + ubo.setTexture("environmentFuzzBrdfSampler", this._environmentFuzzBRDFTexture); + } + + if (defines.ANISOTROPIC || defines.FUZZ) { ubo.setTexture("blueNoiseSampler", OpenPBRMaterial._noiseTextures[this.getScene().uniqueId]); } } @@ -2152,6 +2277,9 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { if (this._environmentBRDFTexture && this.getScene().environmentBRDFTexture !== this._environmentBRDFTexture) { this._environmentBRDFTexture.dispose(); } + if (this._environmentFuzzBRDFTexture && this.getScene().environmentFuzzBRDFTexture !== this._environmentFuzzBRDFTexture) { + this._environmentFuzzBRDFTexture.dispose(); + } // Loop through samplers and dispose the textures for (const key in this._samplersList) { @@ -2335,6 +2463,10 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { "areaLightsLTC2Sampler", ]; + if (defines.FUZZENVIRONMENTBRDF) { + samplers.push("environmentFuzzBrdfSampler"); + } + for (const key in this._samplersList) { const sampler = this._samplersList[key]; samplers.push(sampler.samplerName); @@ -2516,6 +2648,12 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { defines.ENVIRONMENTBRDF_RGBD = false; } + if (this._environmentFuzzBRDFTexture) { + defines.FUZZENVIRONMENTBRDF = true; + } else { + defines.FUZZENVIRONMENTBRDF = false; + } + if (this._shouldUseAlphaFromBaseColorTexture()) { defines.ALPHA_FROM_BASE_COLOR_TEXTURE = true; } else { @@ -2590,6 +2728,20 @@ export class OpenPBRMaterial extends OpenPBRMaterialBase { defines.THIN_FILM = this.thinFilmWeight > 0.0; defines.IRIDESCENCE = this.thinFilmWeight > 0.0; + defines.FUZZ = this.fuzzWeight > 0 && MaterialFlags.ReflectionTextureEnabled; + if (defines.FUZZ) { + if (!mesh.isVerticesDataPresent(VertexBuffer.TangentKind)) { + defines._needUVs = true; + defines.MAINUV1 = true; + } + this._environmentFuzzBRDFTexture = GetEnvironmentFuzzBRDFTexture(this.getScene()); + defines.FUZZ_IBL_SAMPLES = this.fuzzSampleNumber; + } else { + this._environmentFuzzBRDFTexture = null; + defines.FUZZENVIRONMENTBRDF = false; + defines.FUZZ_IBL_SAMPLES = 0; + } + // Misc. if (defines._areMiscDirty) { PrepareDefinesForMisc( diff --git a/packages/dev/core/src/Misc/brdfTextureTools.ts b/packages/dev/core/src/Misc/brdfTextureTools.ts index 0bb740618cf..1ba1246b45d 100644 --- a/packages/dev/core/src/Misc/brdfTextureTools.ts +++ b/packages/dev/core/src/Misc/brdfTextureTools.ts @@ -7,29 +7,31 @@ import { RGBDTextureTools } from "./rgbdTextureTools"; const _environmentBRDFBase64Texture = "data:image/png;base64,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"; +// Multiple-Scattering Sheen BRDF LUT from https://github.com/tizian/ltc-sheen +// eslint-disable-next-line @typescript-eslint/naming-convention +const _environmentFuzzBRDFBase64Texture = + "data:image/png;base64,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"; + let InstanceNumber = 0; /** - * Gets a default environment BRDF for MS-BRDF Height Correlated BRDF + * Internal function to load and setup BRDF textures * @param scene defines the hosting scene + * @param textureData defines the base64 texture data + * @param textureProperty defines the scene property name to store the texture + * @param textureName defines the name for the texture * @returns the environment BRDF texture */ -export const GetEnvironmentBRDFTexture = (scene: Scene): BaseTexture => { - if (!scene.environmentBRDFTexture) { +// eslint-disable-next-line @typescript-eslint/naming-convention +const loadBRDFTexture = (scene: Scene, textureData: string, textureProperty: string, textureName: string): BaseTexture => { + if (!(scene as any)[textureProperty]) { // Forces Delayed Texture Loading to prevent undefined error whilst setting RGBD values. const useDelayedTextureLoading = scene.useDelayedTextureLoading; scene.useDelayedTextureLoading = false; const previousState = scene._blockEntityCollection; scene._blockEntityCollection = false; - const texture = Texture.CreateFromBase64String( - _environmentBRDFBase64Texture, - "EnvironmentBRDFTexture" + InstanceNumber++, - scene, - true, - false, - Texture.BILINEAR_SAMPLINGMODE - ); + const texture = Texture.CreateFromBase64String(textureData, textureName + InstanceNumber++, scene, true, false, Texture.BILINEAR_SAMPLINGMODE); scene._blockEntityCollection = previousState; // BRDF Texture should not be cached here due to pre processing and redundant scene caches. const texturesCache = scene.getEngine().getLoadedTexturesCache(); @@ -41,7 +43,7 @@ export const GetEnvironmentBRDFTexture = (scene: Scene): BaseTexture => { texture.isRGBD = true; texture.wrapU = Texture.CLAMP_ADDRESSMODE; texture.wrapV = Texture.CLAMP_ADDRESSMODE; - scene.environmentBRDFTexture = texture; + (scene as any)[textureProperty] = texture; scene.useDelayedTextureLoading = useDelayedTextureLoading; @@ -68,7 +70,25 @@ export const GetEnvironmentBRDFTexture = (scene: Scene): BaseTexture => { }); } - return scene.environmentBRDFTexture; + return (scene as any)[textureProperty]; +}; + +/** + * Gets a default environment BRDF for MS-BRDF Height Correlated BRDF + * @param scene defines the hosting scene + * @returns the environment BRDF texture + */ +export const GetEnvironmentBRDFTexture = (scene: Scene): BaseTexture => { + return loadBRDFTexture(scene, _environmentBRDFBase64Texture, "environmentBRDFTexture", "EnvironmentBRDFTexture"); +}; + +/** + * Gets a default environment fuzz BRDF texture + * @param scene defines the hosting scene + * @returns the environment fuzz BRDF texture + */ +export const GetEnvironmentFuzzBRDFTexture = (scene: Scene): BaseTexture => { + return loadBRDFTexture(scene, _environmentFuzzBRDFBase64Texture, "environmentFuzzBRDFTexture", "EnvironmentFuzzBRDFTexture"); }; /** @@ -82,4 +102,12 @@ export const BRDFTextureTools = { */ // eslint-disable-next-line @typescript-eslint/naming-convention GetEnvironmentBRDFTexture, + + /** + * Gets a default environment fuzz BRDF texture + * @param scene defines the hosting scene + * @returns the environment fuzz BRDF texture + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + GetEnvironmentFuzzBRDFTexture, }; diff --git a/packages/dev/core/src/Rendering/IBLShadows/iblShadowsPluginMaterial.ts b/packages/dev/core/src/Rendering/IBLShadows/iblShadowsPluginMaterial.ts index 90c6a465a87..fd0de8ce5dc 100644 --- a/packages/dev/core/src/Rendering/IBLShadows/iblShadowsPluginMaterial.ts +++ b/packages/dev/core/src/Rendering/IBLShadows/iblShadowsPluginMaterial.ts @@ -10,7 +10,7 @@ import { expandToProperty, serialize } from "core/Misc/decorators"; import { RegisterClass } from "core/Misc/typeStore"; import { ShaderLanguage } from "core/Materials/shaderLanguage"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; /** * @internal */ diff --git a/packages/dev/core/src/Rendering/IBLShadows/iblShadowsRenderPipeline.ts b/packages/dev/core/src/Rendering/IBLShadows/iblShadowsRenderPipeline.ts index 6eeb74c406c..9a3e0c4b1a4 100644 --- a/packages/dev/core/src/Rendering/IBLShadows/iblShadowsRenderPipeline.ts +++ b/packages/dev/core/src/Rendering/IBLShadows/iblShadowsRenderPipeline.ts @@ -27,7 +27,7 @@ import type { Material } from "core/Materials/material"; import { Observable } from "core/Misc/observable"; import "../geometryBufferRendererSceneComponent"; import "../iblCdfGeneratorSceneComponent"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; interface IIblShadowsSettings { /** diff --git a/packages/dev/core/src/Rendering/geometryBufferRenderer.ts b/packages/dev/core/src/Rendering/geometryBufferRenderer.ts index b46824e4d68..7ad26db8244 100644 --- a/packages/dev/core/src/Rendering/geometryBufferRenderer.ts +++ b/packages/dev/core/src/Rendering/geometryBufferRenderer.ts @@ -24,7 +24,7 @@ import { BindMorphTargetParameters, BindSceneUniformBuffer, PrepareDefinesAndAtt import "../Engines/Extensions/engine.multiRender"; import { ShaderLanguage } from "core/Materials/shaderLanguage"; -import type { OpenPBRMaterial } from "../Materials/PBR/openPbrMaterial"; +import type { OpenPBRMaterial } from "../Materials/PBR/openpbrMaterial"; /** @internal */ interface ISavedTransformationMatrix { diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrBaseLayerData.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrBaseLayerData.fx index 82cddf8cecb..a85b0b42114 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrBaseLayerData.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrBaseLayerData.fx @@ -71,7 +71,7 @@ vec2 geometry_tangent = vec2(1.0, 0.0); #endif #endif -#ifdef ANISOTROPIC +#if defined(ANISOTROPIC) || defined(FUZZ) vec3 noise = texture2D(blueNoiseSampler, gl_FragCoord.xy / 256.0).xyz; #endif diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrDirectLighting.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrDirectLighting.fx index c6b3c5482ab..11ec7f2eb3a 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrDirectLighting.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrDirectLighting.fx @@ -10,6 +10,7 @@ vec3 slab_coat = vec3(0., 0., 0.); float coatFresnel = 0.0; vec3 slab_fuzz = vec3(0., 0., 0.); + float fuzzFresnel = 0.0; // Diffuse Lobe #ifdef HEMILIGHT{X} @@ -26,6 +27,11 @@ numLights += 1.0; + #ifdef FUZZ + float fuzzNdotH = max(dot(fuzzNormalW, preInfo{X}.H), 0.0); + vec3 fuzzBrdf = getFuzzBRDFLookup(fuzzNdotH, sqrt(fuzz_roughness)); + #endif + // Specular Lobe #if AREALIGHT{X} slab_glossy = computeAreaSpecularLighting(preInfo{X}, light{X}.vLightSpecular.rgb, baseConductorReflectance.F0, baseConductorReflectance.F90); @@ -134,12 +140,22 @@ coatAbsorption = mix(vec3(1.0), colored_transmission * darkened_transmission, coat_weight); } + #ifdef FUZZ + fuzzFresnel = fuzzBrdf.z; + vec3 fuzzNormalW = mix(normalW, coatNormalW, coat_weight); + float fuzzNdotV = max(dot(fuzzNormalW, viewDirectionW.xyz), 0.0); + float fuzzNdotL = max(dot(fuzzNormalW, preInfo{X}.L), 0.0); + slab_fuzz = lightColor{X}.rgb * preInfo{X}.attenuation * evalFuzz(preInfo{X}.L, fuzzNdotL, fuzzNdotV, fuzzTangent, fuzzBitangent, fuzzBrdf); + #else + vec3 fuzz_color = vec3(0.0); + #endif + slab_diffuse *= base_color.rgb; vec3 material_opaque_base = mix(slab_diffuse, slab_subsurface, subsurface_weight); vec3 material_dielectric_base = mix(material_opaque_base, slab_translucent, transmission_weight); vec3 material_dielectric_gloss = material_dielectric_base * (1.0 - specularFresnel) + slab_glossy * specularColoredFresnel; vec3 material_base_substrate = mix(material_dielectric_gloss, slab_metal, base_metalness); vec3 material_coated_base = layer(material_base_substrate, slab_coat, coatFresnel, coatAbsorption, vec3(1.0)); - material_surface_direct += mix(material_coated_base, slab_fuzz, fuzz_weight); + material_surface_direct += layer(material_coated_base, slab_fuzz, fuzzFresnel * fuzz_weight, vec3(1.0), fuzz_color); } #endif \ No newline at end of file diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrEnvironmentLighting.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrEnvironmentLighting.fx index b869e8aadfc..19cff4be583 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrEnvironmentLighting.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrEnvironmentLighting.fx @@ -1,5 +1,10 @@ // _____________________________ Base Diffuse Layer IBL _______________________________________ #ifdef REFLECTION + + #ifdef FUZZ + vec3 environmentFuzzBrdf = getFuzzBRDFLookup(fuzzGeoInfo.NdotV, sqrt(fuzz_roughness)); + #endif + // Pass in a vector to sample the irradiance with. A normal can be used for // diffuse irradiance while a refracted vector can be used for diffuse transmission. vec3 baseDiffuseEnvironmentLight = sampleIrradiance( @@ -100,7 +105,45 @@ ); #endif } - + + #ifdef FUZZ + // _____________________________ Fuzz Layer IBL _______________________________________ + + // From the LUT, the y component represents a slight skewing of the lobe. I'm using this to + // bump the roughness up slightly. + float modifiedFuzzRoughness = clamp(fuzz_roughness * (1.0 - 0.5 * environmentFuzzBrdf.y), 0.0, 1.0); + + // The x component of the LUT, represents the anisotropy of the lobe (0 being anisotropic, 1 being isotropic) + // We'll do a simple approximation by sampling the environment multiple times around an imaginary fiber. + // This will be scaled by the anisotropy value from the LUT so that, for isotropic fuzz, we just use the surface normal. + vec3 fuzzEnvironmentLight = vec3(0.0); + float totalWeight = 0.0; + float fuzzIblFresnel = sqrt(environmentFuzzBrdf.z); + for (int i = 0; i < FUZZ_IBL_SAMPLES; ++i) { + float angle = (float(i) + noise.x) * (3.141592 * 2.0 / float(FUZZ_IBL_SAMPLES)); + // Normal of the fiber is a simple rotation of the tangent and bitangent around the surface normal + vec3 fiberCylinderNormal = normalize(cos(angle) * fuzzTangent + sin(angle) * fuzzBitangent); + // Then, we mix it with the fuzz surface normal based on the anisotropy from the LUT and the fuzz + // roughness. When the fibers are more aligned, we get higher anisotropy. + float fiberBend = min(environmentFuzzBrdf.x * environmentFuzzBrdf.x * modifiedFuzzRoughness, 1.0); + fiberCylinderNormal = normalize(mix(fiberCylinderNormal, fuzzNormalW, fiberBend)); + float sampleWeight = max(dot(viewDirectionW, fiberCylinderNormal), 0.0); + vec3 fuzzReflectionCoords = createReflectionCoords(vPositionW, fiberCylinderNormal); + vec3 radianceSample = sampleRadiance(modifiedFuzzRoughness, vReflectionMicrosurfaceInfos.rgb, vReflectionInfos + , fuzzGeoInfo + , reflectionSampler + , fuzzReflectionCoords + #ifdef REALTIME_FILTERING + , vReflectionFilteringInfo + #endif + ); + // As we get closer to bending the normal back towards the regular surface normal, the fuzz is + // also rougher, so we blend more towards the diffuse environment light. + fuzzEnvironmentLight += sampleWeight * mix(radianceSample, baseDiffuseEnvironmentLight, fiberBend); + totalWeight += sampleWeight; + } + fuzzEnvironmentLight /= totalWeight; + #endif // ______________________________ IBL Fresnel Reflectance ____________________________ // Dielectric IBL Fresnel @@ -130,7 +173,6 @@ coatIblFresnel = getReflectanceFromBRDFLookup(vec3(coatReflectance.F0), vec3(coatReflectance.F90), coatGeoInfo.environmentBrdf).r; } - vec3 slab_diffuse_ibl = vec3(0., 0., 0.); vec3 slab_glossy_ibl = vec3(0., 0., 0.); vec3 slab_metal_ibl = vec3(0., 0., 0.); @@ -181,10 +223,13 @@ coatAbsorption = mix(vec3(1.0), colored_transmission * darkened_transmission, coat_weight); } + #ifdef FUZZ + vec3 slab_fuzz_ibl = fuzzEnvironmentLight * vLightingIntensity.z; + #endif + // TEMP vec3 slab_subsurface_ibl = vec3(0., 0., 0.); vec3 slab_translucent_base_ibl = vec3(0., 0., 0.); - vec3 slab_fuzz_ibl = vec3(0., 0., 0.); slab_diffuse_ibl *= base_color.rgb; @@ -195,6 +240,10 @@ vec3 material_dielectric_gloss_ibl = material_dielectric_base_ibl * (1.0 - dielectricIblFresnel) + slab_glossy_ibl * dielectricIblColoredFresnel; vec3 material_base_substrate_ibl = mix(material_dielectric_gloss_ibl, slab_metal_ibl, base_metalness); vec3 material_coated_base_ibl = layer(material_base_substrate_ibl, slab_coat_ibl, coatIblFresnel, coatAbsorption, vec3(1.0)); - material_surface_ibl = mix(material_coated_base_ibl, slab_fuzz_ibl, fuzz_weight); + #ifdef FUZZ + material_surface_ibl = layer(material_coated_base_ibl, slab_fuzz_ibl, fuzzIblFresnel * fuzz_weight, vec3(1.0), fuzz_color); + #else + material_surface_ibl = material_coated_base_ibl; + #endif #endif \ No newline at end of file diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentDeclaration.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentDeclaration.fx index 66016854476..ce4c4ee629f 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentDeclaration.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentDeclaration.fx @@ -12,6 +12,9 @@ uniform float vCoatRoughness; uniform float vCoatRoughnessAnisotropy; uniform float vCoatIor; uniform float vCoatDarkening; +uniform float vFuzzWeight; +uniform vec3 vFuzzColor; +uniform float vFuzzRoughness; uniform vec2 vGeometryCoatTangent; uniform vec3 vEmissionColor; uniform float vThinFilmWeight; @@ -108,6 +111,18 @@ uniform vec2 vCoatIorInfos; uniform vec2 vCoatDarkeningInfos; #endif +#ifdef FUZZ_WEIGHT +uniform vec2 vFuzzWeightInfos; +#endif + +#ifdef FUZZ_COLOR +uniform vec2 vFuzzColorInfos; +#endif + +#ifdef FUZZ_ROUGHNESS +uniform vec2 vFuzzRoughnessInfos; +#endif + #ifdef GEOMETRY_COAT_TANGENT uniform vec2 vGeometryCoatTangentInfos; #endif diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentSamplersDeclaration.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentSamplersDeclaration.fx index f932f625410..f203634ac0c 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentSamplersDeclaration.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFragmentSamplersDeclaration.fx @@ -11,7 +11,10 @@ #include(_DEFINENAME_,COAT_COLOR,_VARYINGNAME_,CoatColor,_SAMPLERNAME_,coatColor) #include(_DEFINENAME_,COAT_ROUGHNESS,_VARYINGNAME_,CoatRoughness,_SAMPLERNAME_,coatRoughness) #include(_DEFINENAME_,COAT_ROUGHNESS_ANISOTROPY,_VARYINGNAME_,CoatRoughnessAnisotropy,_SAMPLERNAME_,coatRoughnessAnisotropy) -#include (_DEFINENAME_,COAT_DARKENING,_VARYINGNAME_,CoatDarkening,_SAMPLERNAME_,coatDarkening) +#include(_DEFINENAME_,COAT_DARKENING,_VARYINGNAME_,CoatDarkening,_SAMPLERNAME_,coatDarkening) +#include(_DEFINENAME_,FUZZ_WEIGHT,_VARYINGNAME_,FuzzWeight,_SAMPLERNAME_,fuzzWeight) +#include(_DEFINENAME_,FUZZ_COLOR,_VARYINGNAME_,FuzzColor,_SAMPLERNAME_,fuzzColor) +#include(_DEFINENAME_,FUZZ_ROUGHNESS,_VARYINGNAME_,FuzzRoughness,_SAMPLERNAME_,fuzzRoughness) #include(_DEFINENAME_,GEOMETRY_OPACITY,_VARYINGNAME_,GeometryOpacity,_SAMPLERNAME_,geometryOpacity) #include(_DEFINENAME_,GEOMETRY_TANGENT,_VARYINGNAME_,GeometryTangent,_SAMPLERNAME_,geometryTangent) #include(_DEFINENAME_,GEOMETRY_COAT_TANGENT,_VARYINGNAME_,GeometryCoatTangent,_SAMPLERNAME_,geometryCoatTangent) @@ -69,7 +72,11 @@ uniform sampler2D environmentBrdfSampler; #endif -#ifdef ANISOTROPIC +#ifdef FUZZENVIRONMENTBRDF + uniform sampler2D environmentFuzzBrdfSampler; +#endif + +#if defined(ANISOTROPIC) || defined(FUZZ) uniform sampler2D blueNoiseSampler; #endif diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrFuzzLayerData.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFuzzLayerData.fx new file mode 100644 index 00000000000..c0e8c878ca4 --- /dev/null +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrFuzzLayerData.fx @@ -0,0 +1,48 @@ +// This code reads uniforms and samples textures to fill up the fuzz +// layer properties for OpenPBR +float fuzz_weight = 0.0; +vec3 fuzz_color = vec3(1.0); +float fuzz_roughness = 0.0; + +#ifdef FUZZ + +// Sample Fuzz Layer properties from textures +#ifdef FUZZ_WEIGHT + vec4 fuzzWeightFromTexture = texture2D(fuzzWeightSampler, vFuzzWeightUV + uvOffset); +#endif + +#ifdef FUZZ_COLOR + vec4 fuzzColorFromTexture = texture2D(fuzzColorSampler, vFuzzColorUV + uvOffset); +#endif + +#ifdef FUZZ_ROUGHNESS + vec4 fuzzRoughnessFromTexture = texture2D(fuzzRoughnessSampler, vFuzzRoughnessUV + uvOffset); +#endif + +// Initalize fuzz layer properties from uniforms +fuzz_color = vFuzzColor.rgb; +fuzz_weight = vFuzzWeight; +fuzz_roughness = vFuzzRoughness; + +// Apply texture values to fuzz layer properties +#ifdef FUZZ_WEIGHT + fuzz_weight *= fuzzWeightFromTexture.r; +#endif + +#ifdef FUZZ_COLOR + #ifdef FUZZ_COLOR_GAMMA + fuzz_color *= toLinearSpace(fuzzColorFromTexture.rgb); + #else + fuzz_color *= fuzzColorFromTexture.rgb; + #endif + + fuzz_color *= vFuzzColorInfos.y; +#endif + +#if defined(FUZZ_ROUGHNESS) && defined(FUZZ_ROUGHNESS_FROM_COLOR_TEXTURE_ALPHA) + fuzz_roughness *= fuzzRoughnessFromTexture.a; +#elif defined(FUZZ_ROUGHNESS) + fuzz_roughness *= fuzzRoughnessFromTexture.r; +#endif + +#endif \ No newline at end of file diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrGeometryInfo.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrGeometryInfo.fx index 5de7b1832e1..df73eac9504 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrGeometryInfo.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrGeometryInfo.fx @@ -6,7 +6,6 @@ struct geometryInfoOutParams float horizonOcclusion; }; -#ifdef ANISOTROPIC struct geometryInfoAnisoOutParams { float NdotV; @@ -18,7 +17,6 @@ struct geometryInfoAnisoOutParams vec3 anisotropicBitangent; mat3 TBN; }; -#endif #define pbr_inline geometryInfoOutParams geometryInfo( @@ -51,7 +49,6 @@ geometryInfoOutParams geometryInfo( return outParams; } -#ifdef ANISOTROPIC #define pbr_inline geometryInfoAnisoOutParams geometryInfoAniso( in vec3 normalW, in vec3 viewDirectionW, in float roughness, in vec3 geometricNormalW @@ -72,5 +69,4 @@ geometryInfoAnisoOutParams geometryInfoAniso( outParams.TBN = TBN; return outParams; -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragment.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragment.fx index 6679cb67bf4..8ca72fa54e8 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragment.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragment.fx @@ -19,7 +19,7 @@ vec2 TBNUV = gl_FrontFacing ? vDetailUV : -vDetailUV; mat3 TBN = cotangent_frame(normalW * normalScale, vPositionW, TBNUV, vec2(1., 1.)); #endif -#elif defined(ANISOTROPIC) +#elif defined(ANISOTROPIC) || defined(FUZZ) #if defined(TANGENT) && defined(NORMAL) mat3 TBN = vTBN; #else diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx index 98d26854602..8978f6787f1 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx @@ -1,4 +1,4 @@ -#if defined(GEOMETRY_NORMAL) || defined(GEOMETRY_COAT_NORMAL) || defined(ANISOTROPIC) || defined(DETAIL) +#if defined(GEOMETRY_NORMAL) || defined(GEOMETRY_COAT_NORMAL) || defined(ANISOTROPIC) || defined(FUZZ) || defined(DETAIL) #if defined(TANGENT) && defined(NORMAL) varying mat3 vTBN; #endif diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertex.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertex.fx index 66ea1a3a889..f3a1a5bd830 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertex.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertex.fx @@ -1,4 +1,4 @@ -#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(GEOMETRY_COAT_NORMAL) || defined(ANISOTROPIC) +#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(GEOMETRY_COAT_NORMAL) || defined(ANISOTROPIC) || defined(FUZZ) #if defined(TANGENT) && defined(NORMAL) vec3 tbnNormal = normalize(normalUpdated); vec3 tbnTangent = normalize(tangentUpdated.xyz); diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertexDeclaration.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertexDeclaration.fx index 2441ca7d786..6ad6a6a5a3c 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertexDeclaration.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrNormalMapVertexDeclaration.fx @@ -1,4 +1,4 @@ -#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(GEOMETRY_COAT_NORMAL) || defined(ANISOTROPIC) +#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(GEOMETRY_COAT_NORMAL) || defined(ANISOTROPIC) || defined(FUZZ) #if defined(TANGENT) && defined(NORMAL) varying mat3 vTBN; #endif diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrUboDeclaration.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrUboDeclaration.fx index 4d87d0de0a3..9cc841dcbd4 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrUboDeclaration.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrUboDeclaration.fx @@ -50,6 +50,9 @@ uniform Material { float vCoatRoughnessAnisotropy; float vCoatIor; float vCoatDarkening; + float vFuzzWeight; + vec3 vFuzzColor; + float vFuzzRoughness; vec2 vGeometryCoatTangent; vec3 vEmissionColor; float vThinFilmWeight; @@ -82,6 +85,12 @@ uniform Material { mat4 coatRoughnessAnisotropyMatrix; vec2 vCoatDarkeningInfos; mat4 coatDarkeningMatrix; + vec2 vFuzzWeightInfos; + mat4 fuzzWeightMatrix; + vec2 vFuzzColorInfos; + mat4 fuzzColorMatrix; + vec2 vFuzzRoughnessInfos; + mat4 fuzzRoughnessMatrix; vec2 vGeometryNormalInfos; mat4 geometryNormalMatrix; vec2 vGeometryTangentInfos; diff --git a/packages/dev/core/src/Shaders/ShadersInclude/openpbrVertexDeclaration.fx b/packages/dev/core/src/Shaders/ShadersInclude/openpbrVertexDeclaration.fx index 43eaa9e7d92..66e02b8efca 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/openpbrVertexDeclaration.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/openpbrVertexDeclaration.fx @@ -91,6 +91,21 @@ uniform vec2 vCoatDarkeningInfos; uniform mat4 coatDarkeningMatrix; #endif +#ifdef FUZZ_WEIGHT +uniform vec2 vFuzzWeightInfos; +uniform mat4 fuzzWeightMatrix; +#endif + +#ifdef FUZZ_COLOR +uniform vec2 vFuzzColorInfos; +uniform mat4 fuzzColorMatrix; +#endif + +#ifdef FUZZ_ROUGHNESS +uniform vec2 vFuzzRoughnessInfos; +uniform mat4 fuzzRoughnessMatrix; +#endif + #ifdef GEOMETRY_NORMAL uniform vec2 vGeometryNormalInfos; uniform mat4 geometryNormalMatrix; diff --git a/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx b/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx index a6a53279de4..ef44fb0c5b8 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/pbrBRDFFunctions.fx @@ -44,6 +44,25 @@ } #endif +#ifdef FUZZENVIRONMENTBRDF + vec3 getFuzzBRDFLookup(float NdotV, float perceptualRoughness) { + // Indexed on cos(theta) and roughness + vec2 UV = vec2(perceptualRoughness, NdotV); + + // We can find the scale and offset to apply to the specular value. + vec4 brdfLookup = texture(environmentFuzzBrdfSampler, UV); + + const vec2 RiRange = vec2(0.0, 0.75); + const vec2 ARange = vec2(0.005, 0.88); + const vec2 BRange = vec2(-0.18, 0.002); + brdfLookup.r = mix(ARange.x, ARange.y, brdfLookup.r); + brdfLookup.g = mix(BRange.x, BRange.y, brdfLookup.g); + brdfLookup.b = mix(RiRange.x, RiRange.y, brdfLookup.b); + + return brdfLookup.rgb; + } +#endif + #ifdef ENVIRONMENTBRDF vec3 getBRDFLookup(float NdotV, float perceptualRoughness) { // Indexed on cos(theta) and roughness diff --git a/packages/dev/core/src/Shaders/ShadersInclude/pbrDirectLightingFunctions.fx b/packages/dev/core/src/Shaders/ShadersInclude/pbrDirectLightingFunctions.fx index e25837a44aa..f6ceb3b34a8 100644 --- a/packages/dev/core/src/Shaders/ShadersInclude/pbrDirectLightingFunctions.fx +++ b/packages/dev/core/src/Shaders/ShadersInclude/pbrDirectLightingFunctions.fx @@ -128,6 +128,33 @@ vec3 computeProjectionTextureDiffuseLighting(sampler2D projectionLightSampler, m #endif +#ifdef FUZZ +float evalFuzz(vec3 L, float NdotL, float NdotV, vec3 T, vec3 B, vec3 ltcLut) +{ + // Cosine terms + if (NdotL <= 0.0 || NdotV <= 0.0) + return 0.0; + + // === 3. Build LTC transform === + // This matrix warps the hemisphere to match the BRDF shape + mat3 M = mat3( + vec3(ltcLut.r, 0.0, 0.0), + vec3(ltcLut.g, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) + ); + + // === 4. Transform light direction to local tangent space === + vec3 Llocal = vec3(dot(L, T), dot(L, B), NdotL); + + // Apply the LTC transform + vec3 Lwarp = normalize(M * Llocal); + + // === 5. Compute projected cosine term === + float cosThetaWarp = max(Lwarp.z, 0.0); + return cosThetaWarp * NdotL; +} +#endif + #if defined(ANISOTROPIC) && defined(ANISOTROPIC_OPENPBR) // Version used in OpenPBR differs only in that it does not include the Fresnel term. vec3 computeAnisotropicSpecularLighting(preLightingInfo info, vec3 V, vec3 N, vec3 T, vec3 B, float anisotropy, float geometricRoughnessFactor, vec3 lightColor) { diff --git a/packages/dev/core/src/Shaders/openpbr.fragment.fx b/packages/dev/core/src/Shaders/openpbr.fragment.fx index 638691e13b6..78a295371d2 100644 --- a/packages/dev/core/src/Shaders/openpbr.fragment.fx +++ b/packages/dev/core/src/Shaders/openpbr.fragment.fx @@ -96,10 +96,12 @@ void main(void) { #include + // _____________________________ Read Fuzz Layer properties ______________________ + #include + // TEMP float subsurface_weight = 0.0; float transmission_weight = 0.0; - float fuzz_weight = 0.0; #define CUSTOM_FRAGMENT_UPDATE_ALPHA @@ -148,6 +150,17 @@ void main(void) { ); #endif + #ifdef FUZZ + // _____________________________ Compute Geometry info for fuzz layer _________________________ + vec3 fuzzNormalW = normalize(mix(normalW, coatNormalW, coat_weight)); + vec3 fuzzTangent = normalize(TBN[0]); + fuzzTangent = normalize(fuzzTangent - dot(fuzzTangent, fuzzNormalW) * fuzzNormalW); + vec3 fuzzBitangent = cross(fuzzNormalW, fuzzTangent); + geometryInfoOutParams fuzzGeoInfo = geometryInfo( + fuzzNormalW, viewDirectionW.xyz, fuzz_roughness, geometricNormalW + ); + #endif + // _______________________ F0 and F90 Reflectance _______________________________ // Coat diff --git a/packages/dev/core/src/Shaders/openpbr.vertex.fx b/packages/dev/core/src/Shaders/openpbr.vertex.fx index 3806c074c62..76bbfafb3d4 100644 --- a/packages/dev/core/src/Shaders/openpbr.vertex.fx +++ b/packages/dev/core/src/Shaders/openpbr.vertex.fx @@ -47,6 +47,9 @@ attribute vec4 color; #include(_DEFINENAME_,COAT_ROUGHNESS,_VARYINGNAME_,CoatRoughness) #include(_DEFINENAME_,COAT_ROUGHNESS_ANISOTROPY,_VARYINGNAME_,CoatRoughnessAnisotropy) #include(_DEFINENAME_,COAT_DARKENING,_VARYINGNAME_,CoatDarkening) +#include(_DEFINENAME_,FUZZ_WEIGHT,_VARYINGNAME_,FuzzWeight) +#include(_DEFINENAME_,FUZZ_COLOR,_VARYINGNAME_,FuzzColor) +#include(_DEFINENAME_,FUZZ_ROUGHNESS,_VARYINGNAME_,FuzzRoughness) #include(_DEFINENAME_,GEOMETRY_NORMAL,_VARYINGNAME_,GeometryNormal) #include(_DEFINENAME_,GEOMETRY_TANGENT,_VARYINGNAME_,GeometryTangent) #include(_DEFINENAME_,GEOMETRY_COAT_NORMAL,_VARYINGNAME_,GeometryCoatNormal) @@ -236,6 +239,9 @@ void main(void) { #include(_DEFINENAME_,COAT_ROUGHNESS,_VARYINGNAME_,CoatRoughness,_MATRIXNAME_,coatRoughness,_INFONAME_,CoatRoughnessInfos.x) #include(_DEFINENAME_,COAT_ROUGHNESS_ANISOTROPY,_VARYINGNAME_,CoatRoughnessAnisotropy,_MATRIXNAME_,coatRoughnessAnisotropy,_INFONAME_,CoatRoughnessAnisotropyInfos.x) #include(_DEFINENAME_,COAT_DARKENING,_VARYINGNAME_,CoatDarkening,_MATRIXNAME_,coatDarkening,_INFONAME_,CoatDarkeningInfos.x) + #include(_DEFINENAME_,FUZZ_WEIGHT,_VARYINGNAME_,FuzzWeight,_MATRIXNAME_,fuzzWeight,_INFONAME_,FuzzWeightInfos.x) + #include(_DEFINENAME_,FUZZ_COLOR,_VARYINGNAME_,FuzzColor,_MATRIXNAME_,fuzzColor,_INFONAME_,FuzzColorInfos.x) + #include(_DEFINENAME_,FUZZ_ROUGHNESS,_VARYINGNAME_,FuzzRoughness,_MATRIXNAME_,fuzzRoughness,_INFONAME_,FuzzRoughnessInfos.x) #include(_DEFINENAME_,GEOMETRY_NORMAL,_VARYINGNAME_,GeometryNormal,_MATRIXNAME_,geometryNormal,_INFONAME_,GeometryNormalInfos.x) #include(_DEFINENAME_,GEOMETRY_COAT_NORMAL,_VARYINGNAME_,GeometryCoatNormal,_MATRIXNAME_,geometryCoatNormal,_INFONAME_,GeometryCoatNormalInfos.x) #include(_DEFINENAME_,GEOMETRY_OPACITY,_VARYINGNAME_,GeometryOpacity,_MATRIXNAME_,geometryOpacity,_INFONAME_,GeometryOpacityInfos.x) diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.fx index 45192d17bd0..89da67dbfad 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.fx @@ -66,7 +66,7 @@ var geometry_tangent: vec2f = vec2f(1.0, 0.0); #endif #endif -#ifdef ANISOTROPIC +#if defined(ANISOTROPIC) || defined(FUZZ) let noise = textureSample(blueNoiseSampler, blueNoiseSamplerSampler, fragmentInputs.position.xy / 256.0).xyz; #endif diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrDirectLighting.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrDirectLighting.fx index 59204feb58e..66bd9f96796 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrDirectLighting.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrDirectLighting.fx @@ -10,6 +10,9 @@ var slab_coat: vec3f = vec3f(0.f, 0.f, 0.f); var coatFresnel: f32 = 0.0f; var slab_fuzz: vec3f = vec3f(0.f, 0.f, 0.f); + var fuzzFresnel: f32 = 0.0f; + + // _____________________________ Geometry Information _____________________________ // Diffuse Lobe #ifdef HEMILIGHT{X} @@ -26,6 +29,11 @@ numLights += 1.0f; + #ifdef FUZZ + let fuzzNdotH: f32 = max(dot(fuzzNormalW, preInfo{X}.H), 0.0f); + let fuzzBrdf: vec3f = getFuzzBRDFLookup(fuzzNdotH, sqrt(fuzz_roughness)); + #endif + // Specular Lobe #if AREALIGHT{X} slab_glossy = computeAreaSpecularLighting(preInfo{X}, light{X}.vLightSpecular.rgb, baseConductorReflectance.F0, baseConductorReflectance.F90); @@ -132,12 +140,22 @@ coatAbsorption = mix(vec3f(1.0f), colored_transmission * vec3f(darkened_transmission), coat_weight); } + #ifdef FUZZ + fuzzFresnel = fuzzBrdf.z; + let fuzzNormalW = mix(normalW, coatNormalW, coat_weight); + let fuzzNdotV: f32 = max(dot(fuzzNormalW, viewDirectionW.xyz), 0.0f); + let fuzzNdotL: f32 = max(dot(fuzzNormalW, preInfo{X}.L), 0.0); + slab_fuzz = lightColor{X}.rgb * preInfo{X}.attenuation * evalFuzz(preInfo{X}.L, fuzzNdotL, fuzzNdotV, fuzzTangent, fuzzBitangent, fuzzBrdf); + #else + let fuzz_color = vec3f(0.0); + #endif + slab_diffuse *= base_color.rgb; let material_opaque_base: vec3f = mix(slab_diffuse, slab_subsurface, subsurface_weight); let material_dielectric_base: vec3f = mix(material_opaque_base, slab_translucent, transmission_weight); let material_dielectric_gloss: vec3f = material_dielectric_base * (1.0f - specularFresnel) + slab_glossy * specularColoredFresnel; let material_base_substrate: vec3f = mix(material_dielectric_gloss, slab_metal, base_metalness); - let material_coated_base: vec3f = layer(material_base_substrate, slab_coat, coatFresnel, coatAbsorption, vec3f(1.0)); - material_surface_direct += mix(material_coated_base, slab_fuzz, fuzz_weight); + let material_coated_base: vec3f = layer(material_base_substrate, slab_coat, coatFresnel, coatAbsorption, vec3f(1.0f)); + material_surface_direct += layer(material_coated_base, slab_fuzz, fuzzFresnel * fuzz_weight, vec3f(1.0f), fuzz_color); } #endif \ No newline at end of file diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.fx index 450ab3a081f..a9077ccfa20 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.fx @@ -1,5 +1,10 @@ // _____________________________ Base Diffuse Layer IBL _______________________________________ #ifdef REFLECTION + + #ifdef FUZZ + let environmentFuzzBrdf: vec3f = getFuzzBRDFLookup(fuzzGeoInfo.NdotV, sqrt(fuzz_roughness)); + #endif + // Pass in a vector to sample the irradiance with. A normal can be used for // diffuse irradiance while a refracted vector can be used for diffuse transmission. var baseDiffuseEnvironmentLight: vec3f = sampleIrradiance( @@ -107,6 +112,46 @@ ); #endif } + + #ifdef FUZZ + // _____________________________ Fuzz Layer IBL _______________________________________ + + // From the LUT, the y component represents a slight skewing of the lobe. I'm using this to + // bump the roughness up slightly. + let modifiedFuzzRoughness: f32 = clamp(fuzz_roughness * (1.0f - 0.5f * environmentFuzzBrdf.y), 0.0f, 1.0f); + + // The x component of the LUT, represents the anisotropy of the lobe (0 being anisotropic, 1 being isotropic) + // We'll do a simple approximation by sampling the environment multiple times around an imaginary fiber. + // This will be scaled by the anisotropy value from the LUT so that, for isotropic fuzz, we just use the surface normal. + var fuzzEnvironmentLight = vec3f(0.0f, 0.0f, 0.0f); + var totalWeight = 0.0f; + let fuzzIblFresnel: f32 = sqrt(environmentFuzzBrdf.z); + for (var i: i32 = 0; i < i32(FUZZ_IBL_SAMPLES); i++) { + var angle: f32 = (f32(i) + noise.x) * (3.141592f * 2.0f / f32(FUZZ_IBL_SAMPLES)); + // Normal of the fiber is a simple rotation of the tangent and bitangent around the surface normal + var fiberCylinderNormal: vec3f = normalize(cos(angle) * fuzzTangent + sin(angle) * fuzzBitangent); + // Then, we mix it with the fuzz surface normal based on the anisotropy from the LUT and the fuzz + // roughness. When the fibers are more aligned, we get higher anisotropy. + let fiberBend = min(environmentFuzzBrdf.x * environmentFuzzBrdf.x * modifiedFuzzRoughness, 1.0f); + fiberCylinderNormal = normalize(mix(fiberCylinderNormal, fuzzNormalW, fiberBend)); + let sampleWeight = max(dot(viewDirectionW, fiberCylinderNormal), 0.0f); + var fuzzReflectionCoords = createReflectionCoords(fragmentInputs.vPositionW, fiberCylinderNormal); + let radianceSample: vec3f = sampleRadiance(modifiedFuzzRoughness, uniforms.vReflectionMicrosurfaceInfos.rgb, uniforms.vReflectionInfos + , fuzzGeoInfo + , reflectionSampler + , reflectionSamplerSampler + , fuzzReflectionCoords + #ifdef REALTIME_FILTERING + , uniforms.vReflectionFilteringInfo + #endif + ); + // As we get closer to bending the normal back towards the regular surface normal, the fuzz is + // also rougher, so we blend more towards the diffuse environment light. + fuzzEnvironmentLight += sampleWeight * mix(radianceSample, baseDiffuseEnvironmentLight, fiberBend); + totalWeight += sampleWeight; + } + fuzzEnvironmentLight /= totalWeight; + #endif // ______________________________ IBL Fresnel Reflectance ____________________________ @@ -185,10 +230,13 @@ coatAbsorption = mix(vec3f(1.0f), colored_transmission * vec3f(darkened_transmission), coat_weight); } + #ifdef FUZZ + let slab_fuzz_ibl = fuzzEnvironmentLight * uniforms.vLightingIntensity.z; + #endif + // TEMP var slab_subsurface_ibl: vec3f = vec3f(0., 0., 0.); var slab_translucent_base_ibl: vec3f = vec3f(0., 0., 0.); - var slab_fuzz_ibl: vec3f = vec3f(0., 0., 0.); slab_diffuse_ibl *= base_color.rgb; @@ -199,6 +247,10 @@ let material_dielectric_gloss_ibl: vec3f = material_dielectric_base_ibl * (1.0 - dielectricIblFresnel) + slab_glossy_ibl * dielectricIblColoredFresnel; let material_base_substrate_ibl: vec3f = mix(material_dielectric_gloss_ibl, slab_metal_ibl, base_metalness); let material_coated_base_ibl: vec3f = layer(material_base_substrate_ibl, slab_coat_ibl, coatIblFresnel, coatAbsorption, vec3f(1.0f)); - material_surface_ibl = mix(material_coated_base_ibl, slab_fuzz_ibl, fuzz_weight); + #ifdef FUZZ + material_surface_ibl = layer(material_coated_base_ibl, slab_fuzz_ibl, fuzzIblFresnel * fuzz_weight, vec3(1.0), fuzz_color); + #else + material_surface_ibl = material_coated_base_ibl; + #endif #endif \ No newline at end of file diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFragmentSamplersDeclaration.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFragmentSamplersDeclaration.fx index 516de4828fb..90fdbc241c9 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFragmentSamplersDeclaration.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFragmentSamplersDeclaration.fx @@ -11,6 +11,9 @@ #include(_DEFINENAME_,COAT_ROUGHNESS,_VARYINGNAME_,CoatRoughness,_SAMPLERNAME_,coatRoughness) #include(_DEFINENAME_,COAT_ROUGHNESS_ANISOTROPY,_VARYINGNAME_,CoatRoughnessAnisotropy,_SAMPLERNAME_,coatRoughnessAnisotropy) #include(_DEFINENAME_,COAT_DARKENING,_VARYINGNAME_,CoatDarkening,_SAMPLERNAME_,coatDarkening) +#include(_DEFINENAME_,FUZZ_WEIGHT,_VARYINGNAME_,FuzzWeight,_SAMPLERNAME_,fuzzWeight) +#include(_DEFINENAME_,FUZZ_COLOR,_VARYINGNAME_,FuzzColor,_SAMPLERNAME_,fuzzColor) +#include(_DEFINENAME_,FUZZ_ROUGHNESS,_VARYINGNAME_,FuzzRoughness,_SAMPLERNAME_,fuzzRoughness) #include(_DEFINENAME_,GEOMETRY_OPACITY,_VARYINGNAME_,GeometryOpacity,_SAMPLERNAME_,geometryOpacity) #include(_DEFINENAME_,GEOMETRY_TANGENT,_VARYINGNAME_,GeometryTangent,_SAMPLERNAME_,geometryTangent) #include(_DEFINENAME_,GEOMETRY_COAT_TANGENT,_VARYINGNAME_,GeometryCoatTangent,_SAMPLERNAME_,geometryCoatTangent) @@ -72,7 +75,12 @@ var environmentBrdfSampler: texture_2d; #endif -#ifdef ANISOTROPIC +#ifdef FUZZENVIRONMENTBRDF + var environmentFuzzBrdfSamplerSampler: sampler; + var environmentFuzzBrdfSampler: texture_2d; +#endif + +#if defined(ANISOTROPIC) || defined(FUZZ) var blueNoiseSamplerSampler: sampler; var blueNoiseSampler: texture_2d; #endif diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.fx new file mode 100644 index 00000000000..318416bb022 --- /dev/null +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.fx @@ -0,0 +1,48 @@ +// This code reads uniforms and samples textures to fill up the fuzz +// layer properties for OpenPBR +var fuzz_weight: f32 = 0.0f; +var fuzz_color: vec3f = vec3f(1.0); +var fuzz_roughness: f32 = 0.0f; + +#ifdef FUZZ + +// Sample Fuzz Layer properties from textures +#ifdef FUZZ_WEIGHT + let fuzzWeightFromTexture: vec4 = textureSample(fuzzWeightSampler, fuzzWeightSamplerSampler, fragmentInputs.vFuzzWeightUV + uvOffset); +#endif + +#ifdef FUZZ_COLOR + var fuzzColorFromTexture: vec4 = textureSample(fuzzColorSampler, fuzzColorSamplerSampler, fragmentInputs.vFuzzColorUV + uvOffset); +#endif + +#ifdef FUZZ_ROUGHNESS + let fuzzRoughnessFromTexture: vec4 = textureSample(fuzzRoughnessSampler, fuzzRoughnessSamplerSampler, fragmentInputs.vFuzzRoughnessUV + uvOffset); +#endif + +// Initalize fuzz layer properties from uniforms +fuzz_color = uniforms.vFuzzColor.rgb; +fuzz_weight = uniforms.vFuzzWeight; +fuzz_roughness = uniforms.vFuzzRoughness; + +// Apply texture values to fuzz layer properties +#ifdef FUZZ_WEIGHT + fuzz_weight *= fuzzWeightFromTexture.r; +#endif + +#ifdef FUZZ_COLOR + #ifdef FUZZ_COLOR_GAMMA + fuzz_color *= toLinearSpace(fuzzColorFromTexture.rgb); + #else + fuzz_color *= fuzzColorFromTexture.rgb; + #endif + + fuzz_color *= uniforms.vFuzzColorInfos.y; +#endif + +#if defined(FUZZ_ROUGHNESS) && defined(FUZZ_ROUGHNESS_FROM_COLOR_TEXTURE_ALPHA) + fuzz_roughness *= fuzzRoughnessFromTexture.a; +#elif defined(FUZZ_ROUGHNESS) + fuzz_roughness *= fuzzRoughnessFromTexture.r; +#endif + +#endif \ No newline at end of file diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrGeometryInfo.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrGeometryInfo.fx index 7e6835f8722..631c68d77ad 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrGeometryInfo.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrGeometryInfo.fx @@ -6,7 +6,6 @@ struct geometryInfoOutParams horizonOcclusion: f32 }; -#ifdef ANISOTROPIC struct geometryInfoAnisoOutParams { NdotV: f32, @@ -18,7 +17,6 @@ struct geometryInfoAnisoOutParams anisotropicBitangent: vec3f, TBN: mat3x3 }; -#endif fn geometryInfo( normalW: vec3f, viewDirectionW: vec3f, roughness: f32, geometricNormalW: vec3f @@ -50,7 +48,6 @@ fn geometryInfo( return outParams; } -#ifdef ANISOTROPIC fn geometryInfoAniso( normalW: vec3f, viewDirectionW: vec3f, roughness: f32, geometricNormalW: vec3f , vAnisotropy: vec3f, TBN: mat3x3 @@ -71,4 +68,3 @@ fn geometryInfoAniso( return outParams; } -#endif \ No newline at end of file diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.fx index ae857b5abf9..73e0c4e7970 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.fx @@ -20,7 +20,7 @@ var uvOffset: vec2f = vec2f(0.0, 0.0); var TBNUV: vec2f = select(-fragmentInputs.vDetailUV, fragmentInputs.vDetailUV, fragmentInputs.frontFacing); var TBN: mat3x3f = cotangent_frame(normalW * normalScale, input.vPositionW, TBNUV, vec2f(1., 1.)); #endif -#elif defined(ANISOTROPIC) +#elif defined(ANISOTROPIC) || defined(FUZZ) #if defined(TANGENT) && defined(NORMAL) var TBN: mat3x3f = mat3x3(input.vTBN0, input.vTBN1, input.vTBN2); #else diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx index 6036dc2be86..dc5d8aabc6d 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapFragmentMainFunctions.fx @@ -1,4 +1,4 @@ -#if defined(GEOMETRY_NORMAL) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC) || defined(DETAIL) +#if defined(GEOMETRY_NORMAL) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC) || defined(FUZZ) || defined(DETAIL) #if defined(TANGENT) && defined(NORMAL) varying vTBN0: vec3f; varying vTBN1: vec3f; diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertex.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertex.fx index 504955cd129..3caa1a2905d 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertex.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertex.fx @@ -1,4 +1,4 @@ -#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC) +#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC) || defined(FUZZ) #if defined(TANGENT) && defined(NORMAL) var tbnNormal: vec3f = normalize(normalUpdated); var tbnTangent: vec3f = normalize(tangentUpdated.xyz); diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertexDeclaration.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertexDeclaration.fx index 11b840ed96f..e47ee5292e4 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertexDeclaration.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrNormalMapVertexDeclaration.fx @@ -1,4 +1,4 @@ -#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC) +#if defined(GEOMETRY_NORMAL) || defined(PARALLAX) || defined(CLEARCOAT_BUMP) || defined(ANISOTROPIC) || defined(FUZZ) #if defined(TANGENT) && defined(NORMAL) varying vTBN0: vec3f; varying vTBN1: vec3f; diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.fx index 48c3cfc9313..036632a0525 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.fx @@ -48,6 +48,9 @@ uniform vCoatRoughness: f32; uniform vCoatRoughnessAnisotropy: f32; uniform vCoatIor: f32; uniform vCoatDarkening : f32; +uniform vFuzzWeight: f32; +uniform vFuzzColor: vec3f; +uniform vFuzzRoughness: f32; uniform vGeometryCoatTangent: vec2f; uniform vEmissionColor: vec3f; uniform vThinFilmWeight: f32; @@ -80,6 +83,12 @@ uniform vCoatRoughnessAnisotropyInfos: vec2f; uniform coatRoughnessAnisotropyMatrix: mat4x4f; uniform vCoatDarkeningInfos : vec2f; uniform coatDarkeningMatrix : mat4x4f; +uniform vFuzzWeightInfos: vec2f; +uniform fuzzWeightMatrix: mat4x4f; +uniform vFuzzColorInfos: vec2f; +uniform fuzzColorMatrix: mat4x4f; +uniform vFuzzRoughnessInfos: vec2f; +uniform fuzzRoughnessMatrix: mat4x4f; uniform vGeometryNormalInfos: vec2f; uniform geometryNormalMatrix: mat4x4f; uniform vGeometryTangentInfos: vec2f; diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrBRDFFunctions.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrBRDFFunctions.fx index e993e06e80e..e8b40bf7ed8 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrBRDFFunctions.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrBRDFFunctions.fx @@ -44,6 +44,25 @@ } #endif +#ifdef FUZZENVIRONMENTBRDF + fn getFuzzBRDFLookup(NdotV: f32, perceptualRoughness: f32) -> vec3f { + // Indexed on cos(theta) and roughness + let UV: vec2f = vec2f(perceptualRoughness, NdotV); + + // We can find the scale and offset to apply to the specular value. + var brdfLookup: vec4f = textureSample(environmentFuzzBrdfSampler, environmentFuzzBrdfSamplerSampler, UV); + + const RiRange: vec2f = vec2f(0.0f, 0.75f); + const ARange: vec2f = vec2f(0.005f, 0.88f); + const BRange: vec2f = vec2f(-0.18f, 0.002f); + brdfLookup.r = mix(ARange.x, ARange.y, brdfLookup.r); + brdfLookup.g = mix(BRange.x, BRange.y, brdfLookup.g); + brdfLookup.b = mix(RiRange.x, RiRange.y, brdfLookup.b); + + return brdfLookup.rgb; + } +#endif + #ifdef ENVIRONMENTBRDF fn getBRDFLookup(NdotV: f32, perceptualRoughness: f32) -> vec3f { // Indexed on cos(theta) and roughness diff --git a/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrDirectLightingFunctions.fx b/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrDirectLightingFunctions.fx index f1bd228a8f0..a5930b81bf4 100644 --- a/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrDirectLightingFunctions.fx +++ b/packages/dev/core/src/ShadersWGSL/ShadersInclude/pbrDirectLightingFunctions.fx @@ -130,6 +130,34 @@ fn computeProjectionTextureDiffuseLighting(projectionLightTexture: texture_2d f32 +{ + // Cosine terms + if (NdotL <= 0.0f || NdotV <= 0.0f) { + return 0.0f; + } + + // === 3. Build LTC transform === + // This matrix warps the hemisphere to match the BRDF shape + let M = mat3x3f( + vec3f(ltcLut.r, 0.0f, 0.0f), + vec3f(ltcLut.g, 1.0f, 0.0f), + vec3f(0.0f, 0.0f, 1.0f) + ); + + // === 4. Transform light direction to local tangent space === + let Llocal: vec3f = vec3f(dot(L, T), dot(L, B), NdotL); + + // Apply the LTC transform + let Lwarp: vec3f = normalize(M * Llocal); + + // === 5. Compute projected cosine term === + let cosThetaWarp: f32 = max(Lwarp.z, 0.0f); + return cosThetaWarp * NdotL; +} +#endif + #if defined(ANISOTROPIC) && defined(ANISOTROPIC_OPENPBR) fn computeAnisotropicSpecularLighting(info: preLightingInfo, V: vec3f, N: vec3f, T: vec3f, B: vec3f, anisotropy: f32, geometricRoughnessFactor: f32, lightColor: vec3f) -> vec3f { var NdotH: f32 = saturateEps(dot(N, info.H)); diff --git a/packages/dev/core/src/ShadersWGSL/openpbr.fragment.fx b/packages/dev/core/src/ShadersWGSL/openpbr.fragment.fx index d85a7eb82c0..047b7258720 100644 --- a/packages/dev/core/src/ShadersWGSL/openpbr.fragment.fx +++ b/packages/dev/core/src/ShadersWGSL/openpbr.fragment.fx @@ -81,10 +81,12 @@ fn main(input: FragmentInputs) -> FragmentOutputs { #include + // _____________________________ Read Fuzz Layer properties ______________________ + #include + // TEMP var subsurface_weight: f32 = 0.0f; var transmission_weight: f32 = 0.0f; - var fuzz_weight: f32 = 0.0f; #define CUSTOM_FRAGMENT_UPDATE_ALPHA @@ -133,6 +135,18 @@ fn main(input: FragmentInputs) -> FragmentOutputs { ); #endif + #ifdef FUZZ + // _____________________________ Compute Geometry info for fuzz layer _________________________ + let fuzzNormalW = normalize(mix(normalW, coatNormalW, coat_weight)); + var fuzzTangent = normalize(TBN[0]); + fuzzTangent = normalize(fuzzTangent - dot(fuzzTangent, fuzzNormalW) * fuzzNormalW); + let fuzzBitangent = cross(fuzzNormalW, fuzzTangent); + + let fuzzGeoInfo: geometryInfoOutParams = geometryInfo( + fuzzNormalW, viewDirectionW.xyz, fuzz_roughness, geometricNormalW + ); + #endif + // _______________________ F0 and F90 Reflectance _______________________________ // Coat diff --git a/packages/dev/core/src/ShadersWGSL/openpbr.vertex.fx b/packages/dev/core/src/ShadersWGSL/openpbr.vertex.fx index 843b5a0a65e..087925fdd46 100644 --- a/packages/dev/core/src/ShadersWGSL/openpbr.vertex.fx +++ b/packages/dev/core/src/ShadersWGSL/openpbr.vertex.fx @@ -42,6 +42,9 @@ attribute color: vec4f; #include(_DEFINENAME_,COAT_ROUGHNESS,_VARYINGNAME_,CoatRoughness) #include(_DEFINENAME_,COAT_ROUGHNESS_ANISOTROPY,_VARYINGNAME_,CoatRoughnessAnisotropy) #include(_DEFINENAME_,COAT_DARKENING,_VARYINGNAME_,CoatDarkening) +#include(_DEFINENAME_,FUZZ_WEIGHT,_VARYINGNAME_,FuzzWeight) +#include(_DEFINENAME_,FUZZ_COLOR,_VARYINGNAME_,FuzzColor) +#include(_DEFINENAME_,FUZZ_ROUGHNESS,_VARYINGNAME_,FuzzRoughness) #include(_DEFINENAME_,GEOMETRY_NORMAL,_VARYINGNAME_,GeometryNormal) #include(_DEFINENAME_,GEOMETRY_TANGENT,_VARYINGNAME_,GeometryTangent) #include(_DEFINENAME_,GEOMETRY_COAT_NORMAL,_VARYINGNAME_,GeometryCoatNormal) @@ -223,6 +226,9 @@ fn main(input : VertexInputs) -> FragmentInputs { #include(_DEFINENAME_,COAT_ROUGHNESS,_VARYINGNAME_,CoatRoughness,_MATRIXNAME_,coatRoughness,_INFONAME_,CoatRoughnessInfos.x) #include(_DEFINENAME_,COAT_ROUGHNESS_ANISOTROPY,_VARYINGNAME_,CoatRoughnessAnisotropy,_MATRIXNAME_,coatRoughnessAnisotropy,_INFONAME_,CoatRoughnessAnisotropyInfos.x) #include(_DEFINENAME_,COAT_DARKENING,_VARYINGNAME_,CoatDarkening,_MATRIXNAME_,coatDarkening,_INFONAME_,CoatDarkeningInfos.x) + #include(_DEFINENAME_,FUZZ_WEIGHT,_VARYINGNAME_,FuzzWeight,_MATRIXNAME_,fuzzWeight,_INFONAME_,FuzzWeightInfos.x) + #include(_DEFINENAME_,FUZZ_COLOR,_VARYINGNAME_,FuzzColor,_MATRIXNAME_,fuzzColor,_INFONAME_,FuzzColorInfos.x) + #include(_DEFINENAME_,FUZZ_ROUGHNESS,_VARYINGNAME_,FuzzRoughness,_MATRIXNAME_,fuzzRoughness,_INFONAME_,FuzzRoughnessInfos.x) #include(_DEFINENAME_,GEOMETRY_NORMAL,_VARYINGNAME_,GeometryNormal,_MATRIXNAME_,geometryNormal,_INFONAME_,GeometryNormalInfos.x) #include(_DEFINENAME_,GEOMETRY_TANGENT,_VARYINGNAME_,GeometryTangent,_MATRIXNAME_,geometryTangent,_INFONAME_,GeometryTangentInfos.x) #include(_DEFINENAME_,GEOMETRY_COAT_NORMAL,_VARYINGNAME_,GeometryCoatNormal,_MATRIXNAME_,geometryCoatNormal,_INFONAME_,GeometryCoatNormalInfos.x) diff --git a/packages/dev/core/src/scene.ts b/packages/dev/core/src/scene.ts index 55fb8b20b96..a4167f2ba6d 100644 --- a/packages/dev/core/src/scene.ts +++ b/packages/dev/core/src/scene.ts @@ -274,6 +274,11 @@ export class Scene implements IAnimatable, IClipPlanesHolder, IAssetContainer { */ public environmentBRDFTexture: BaseTexture; + /** + * This stores the brdf lookup for the fuzz layer of PBR materials in your scene. + */ + public environmentFuzzBRDFTexture: BaseTexture; + /** * Intensity of the environment (i.e. all indirect lighting) in all pbr material. * This dims or reinforces the indirect lighting overall (reflection and diffuse). diff --git a/packages/dev/inspector-v2/src/components/properties/materials/openpbrMaterialProperties.tsx b/packages/dev/inspector-v2/src/components/properties/materials/openpbrMaterialProperties.tsx new file mode 100644 index 00000000000..ff05a0be432 --- /dev/null +++ b/packages/dev/inspector-v2/src/components/properties/materials/openpbrMaterialProperties.tsx @@ -0,0 +1,389 @@ +import type { FunctionComponent } from "react"; + +import type { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; +import { BoundProperty } from "../boundProperty"; +import { Color3PropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/colorPropertyLine"; +import { SyncedSliderPropertyLine } from "shared-ui-components/fluent/hoc/propertyLines/syncedSliderPropertyLine"; +import { FileUploadLine } from "shared-ui-components/fluent/hoc/fileUploadLine"; +import { ReadFile } from "core/Misc/fileTools"; +import { Texture } from "core/Materials/Textures/texture"; + +// TODO: ryamtrem / gehalper This function is temporal until there is a line control to handle texture links (similar to the old TextureLinkLineComponent) +const UpdateTexture = (file: File, material: OpenPBRMaterial, textureSetter: (texture: BaseTexture) => void) => { + ReadFile( + file, + (data) => { + const blob = new Blob([data], { type: "octet/stream" }); + const url = URL.createObjectURL(blob); + textureSetter(new Texture(url, material.getScene(), false, false)); + }, + undefined, + true + ); +}; + +/** + * Displays the base layer properties of an OpenPBR material. + * @param props - The required properties + * @returns A JSX element representing the base layer properties. + */ +export const OpenPBRMaterialBaseProperties: FunctionComponent<{ material: OpenPBRMaterial }> = (props) => { + const { material } = props; + + return ( + <> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.baseWeightTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.baseColorTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.baseMetalnessTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.baseDiffuseRoughnessTexture = texture)); + } + }} + /> + + ); +}; + +/** + * Displays the specular layer properties of an OpenPBR material. + * @param props - The required properties + * @returns A JSX element representing the specular layer properties. + */ +export const OpenPBRMaterialSpecularProperties: FunctionComponent<{ material: OpenPBRMaterial }> = (props) => { + const { material } = props; + + return ( + <> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.specularWeightTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.specularColorTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.specularRoughnessTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.specularRoughnessAnisotropyTexture = texture)); + } + }} + /> + + + ); +}; + +/** + * Displays the coat layer properties of an OpenPBR material. + * @param props - The required properties + * @returns A JSX element representing the coat layer properties. + */ +export const OpenPBRMaterialCoatProperties: FunctionComponent<{ material: OpenPBRMaterial }> = (props) => { + const { material } = props; + + return ( + <> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.coatWeightTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.coatColorTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.coatRoughnessTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.coatRoughnessAnisotropyTexture = texture)); + } + }} + /> + + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.coatDarkeningTexture = texture)); + } + }} + /> + + ); +}; + +/** + * Displays the fuzz layer properties of an OpenPBR material. + * @param props - The required properties + * @returns A JSX element representing the fuzz layer properties. + */ +export const OpenPBRMaterialFuzzProperties: FunctionComponent<{ material: OpenPBRMaterial }> = (props) => { + const { material } = props; + + return ( + <> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.fuzzWeightTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.fuzzColorTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.fuzzRoughnessTexture = texture)); + } + }} + /> + + + ); +}; + +/** + * Displays the emission properties of an OpenPBR material. + * @param props - The required properties + * @returns A JSX element representing the emission properties. + */ +export const OpenPBRMaterialEmissionProperties: FunctionComponent<{ material: OpenPBRMaterial }> = (props) => { + const { material } = props; + + return ( + <> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.emissionColorTexture = texture)); + } + }} + /> + + + ); +}; + +/** + * Displays the thin film properties of an OpenPBR material. + * @param props - The required properties + * @returns A JSX element representing the thin film properties. + */ +export const OpenPBRMaterialThinFilmProperties: FunctionComponent<{ material: OpenPBRMaterial }> = (props) => { + const { material } = props; + + return ( + <> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.thinFilmWeightTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.thinFilmThicknessTexture = texture)); + } + }} + /> + + + ); +}; + +/** + * Displays the geometry properties of an OpenPBR material. + * @param props - The required properties + * @returns A JSX element representing the geometry properties. + */ +export const OpenPBRMaterialGeometryProperties: FunctionComponent<{ material: OpenPBRMaterial }> = (props) => { + const { material } = props; + + return ( + <> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.geometryOpacityTexture = texture)); + } + }} + /> + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.geometryNormalTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.geometryTangentTexture = texture)); + } + }} + /> + + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.geometryCoatNormalTexture = texture)); + } + }} + /> + { + if (files.length > 0) { + UpdateTexture(files[0], material, (texture) => (material.geometryCoatTangentTexture = texture)); + } + }} + /> + + ); +}; diff --git a/packages/dev/inspector-v2/src/services/panes/properties/materialPropertiesService.tsx b/packages/dev/inspector-v2/src/services/panes/properties/materialPropertiesService.tsx index 9fffe7a1775..96f9ec30faf 100644 --- a/packages/dev/inspector-v2/src/services/panes/properties/materialPropertiesService.tsx +++ b/packages/dev/inspector-v2/src/services/panes/properties/materialPropertiesService.tsx @@ -9,6 +9,7 @@ import { MultiMaterial } from "core/Materials/multiMaterial"; import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; import { PBRBaseSimpleMaterial } from "core/Materials/PBR/pbrBaseSimpleMaterial"; import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; import { StandardMaterial } from "core/Materials/standardMaterial"; import { SkyMaterial } from "materials/sky/skyMaterial"; import { MaterialGeneralProperties, MaterialStencilProperties, MaterialTransparencyProperties } from "../../../components/properties/materials/materialProperties"; @@ -21,6 +22,15 @@ import { PBRBaseMaterialSheenProperties, } from "../../../components/properties/materials/pbrBaseMaterialProperties"; import { PBRMaterialLightingAndColorProperties } from "../../../components/properties/materials/pbrMaterialProperties"; +import { + OpenPBRMaterialBaseProperties, + OpenPBRMaterialCoatProperties, + OpenPBRMaterialEmissionProperties, + OpenPBRMaterialFuzzProperties, + OpenPBRMaterialGeometryProperties, + OpenPBRMaterialSpecularProperties, + OpenPBRMaterialThinFilmProperties, +} from "../../../components/properties/materials/openpbrMaterialProperties"; import { SkyMaterialProperties } from "../../../components/properties/materials/skyMaterialProperties"; import { StandardMaterialLevelsProperties, @@ -122,6 +132,41 @@ export const MaterialPropertiesServiceDefinition: ServiceDefinition<[], [IProper ], }); + const openPBRMaterialPropertiesRegistration = propertiesService.addSectionContent({ + key: "OpenPBR Material Properties", + predicate: (entity: unknown) => entity instanceof OpenPBRMaterial, + content: [ + { + section: "Base", + component: ({ context }) => , + }, + { + section: "Specular", + component: ({ context }) => , + }, + { + section: "Coat", + component: ({ context }) => , + }, + { + section: "Fuzz", + component: ({ context }) => , + }, + { + section: "Emission", + component: ({ context }) => , + }, + { + section: "Thin Film", + component: ({ context }) => , + }, + { + section: "Geometry", + component: ({ context }) => , + }, + ], + }); + const skyMaterialRegistration = propertiesService.addSectionContent({ key: "Sky Material Properties", predicate: (entity: unknown) => entity instanceof SkyMaterial, @@ -151,6 +196,7 @@ export const MaterialPropertiesServiceDefinition: ServiceDefinition<[], [IProper pbrBaseMaterialPropertiesRegistration.dispose(); pbrMaterialPropertiesRegistration.dispose(); pbrMaterialNormalMapsContentRegistration.dispose(); + openPBRMaterialPropertiesRegistration.dispose(); skyMaterialRegistration.dispose(); multiMaterialContentRegistration.dispose(); }, diff --git a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx index 1925afb9c7c..7034bd9a9a6 100644 --- a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx +++ b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx @@ -118,7 +118,7 @@ import { Tags } from "core/Misc/tags"; import { LineContainerComponent } from "shared-ui-components/lines/lineContainerComponent"; import type { RectAreaLight } from "core/Lights/rectAreaLight"; import { FluentToolWrapper } from "shared-ui-components/fluent/hoc/fluentToolWrapper"; -import type { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import type { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; export class PropertyGridTabComponent extends PaneComponent { private _timerIntervalId: number; diff --git a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/openpbrMaterialPropertyGridComponent.tsx b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/openpbrMaterialPropertyGridComponent.tsx index 4fb5591a478..8589c5e3251 100644 --- a/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/openpbrMaterialPropertyGridComponent.tsx +++ b/packages/dev/inspector/src/components/actionTabs/tabs/propertyGrids/materials/openpbrMaterialPropertyGridComponent.tsx @@ -17,7 +17,7 @@ import type { GlobalState } from "../../../../globalState"; import "core/Materials/material.decalMap"; import "core/Rendering/prePassRendererSceneComponent"; import "core/Rendering/subSurfaceSceneComponent"; -import type { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import type { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; interface IOpenPBRMaterialPropertyGridComponentProps { globalState: GlobalState; @@ -197,6 +197,46 @@ export class OpenPBRMaterialPropertyGridComponent extends React.Component + + + + + ); } @@ -543,6 +583,60 @@ export class OpenPBRMaterialPropertyGridComponent extends React.Component + + + + + + + + ) { - // TODO: Implement when OpenPBR fuzz is available - // this._material.fuzzColorTexture = value; + this._material.fuzzColorTexture = value; } /** * Sets the fuzz roughness. - * TODO: Implementation pending OpenPBR fuzz feature availability. * @param value The fuzz roughness value (0-1) */ public set fuzzRoughness(value: number) { - // TODO: Implement when OpenPBR fuzz is available - // this._material.fuzzRoughness = value; + this._material.fuzzRoughness = value; } /** * Sets the fuzz roughness texture. - * TODO: Implementation pending OpenPBR fuzz feature availability. * @param value The fuzz roughness texture or null */ public set fuzzRoughnessTexture(value: Nullable) { - // TODO: Implement when OpenPBR fuzz is available - // this._material.fuzzRoughnessTexture = value; + this._material.fuzzRoughnessTexture = value; } // ======================================== diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts index eacb585b66c..22d66004089 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts @@ -6,7 +6,7 @@ import type { Nullable } from "core/types"; import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { Texture } from "core/Materials/Textures/texture"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; import { Constants } from "core/Engines/constants"; import { Effect } from "core/Materials/effect"; import { ProceduralTexture } from "core/Materials/Textures/Procedurals/proceduralTexture"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts index 2c614e73389..6fe235c95e2 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts @@ -6,7 +6,7 @@ import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import { Tools } from "core/Misc/tools"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; const NAME = "KHR_materials_clearcoat"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_anisotropy.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_anisotropy.ts index 64e99f9c0db..2988a7f8120 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_anisotropy.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_anisotropy.ts @@ -5,7 +5,7 @@ import type { Material } from "core/Materials/material"; import type { Nullable } from "core/types"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { Texture } from "core/Materials/Textures/texture"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; import { Constants } from "core/Engines/constants"; import { Effect } from "core/Materials/effect"; import { ProceduralTexture } from "core/Materials/Textures/Procedurals/proceduralTexture"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_color.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_color.ts index 5ca8d63cdc0..089d10e9c26 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_color.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_color.ts @@ -5,7 +5,7 @@ import type { Material } from "core/Materials/material"; import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; const NAME = "KHR_materials_clearcoat_color"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_darkening.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_darkening.ts index cd8b29c0bee..9e9145e35b4 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_darkening.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_darkening.ts @@ -3,7 +3,7 @@ import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import type { Material } from "core/Materials/material"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; import type { Nullable } from "core/types"; const NAME = "KHR_materials_clearcoat_darkening"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_ior.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_ior.ts index 4c0d161b981..dcd8f2b6fc3 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_ior.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat_ior.ts @@ -2,7 +2,7 @@ import type { IMaterial, IKHRMaterialsClearcoatIor } from "babylonjs-gltf2interf import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import type { Material } from "core/Materials/material"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; import type { Nullable } from "core/types"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.ts index 7aca0b94fd8..ec5064f8401 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_roughness.ts @@ -4,7 +4,7 @@ import { GLTFExporter } from "../glTFExporter"; import type { Material } from "core/Materials/material"; import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; import type { Nullable } from "core/types"; const NAME = "KHR_materials_diffuse_roughness"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts index 7e144e854aa..42bba8a733c 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts @@ -3,7 +3,7 @@ import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import type { Material } from "core/Materials/material"; import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; const NAME = "KHR_materials_iridescence"; diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts index 9e2b89ccfef..136d7c88726 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts @@ -4,7 +4,7 @@ import { GLTFExporter } from "../glTFExporter"; import type { Material } from "core/Materials/material"; import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; const NAME = "KHR_materials_specular"; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index cd5634f9de6..05bc3d89ec4 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -80,7 +80,7 @@ import { Color3, Color4 } from "core/Maths/math.color"; import { TargetCamera } from "core/Cameras/targetCamera"; import { Epsilon } from "core/Maths/math.constants"; import { DataWriter } from "./dataWriter"; -import { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; class ExporterState { // Babylon indices array, start, count, offset, flip -> glTF accessor index diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 9180adc2bc4..859437464a1 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -26,7 +26,7 @@ import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; import { SpecularPowerToRoughness } from "core/Helpers/materialConversionHelper"; import { InternalTextureSource } from "core/Materials/Textures/internalTexture"; import { GetMimeType } from "core/Misc/fileTools"; -import type { OpenPBRMaterial } from "core/Materials/PBR/openPbrMaterial"; +import type { OpenPBRMaterial } from "core/Materials/PBR/openpbrMaterial"; const Epsilon = 1e-6; const DielectricSpecular = new Color3(0.04, 0.04, 0.04) as DeepImmutable; diff --git a/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Coat-Weight-with-Normal-Maps---Analytic-Lights.png b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Coat-Weight-with-Normal-Maps---Analytic-Lights.png new file mode 100644 index 00000000000..6dea2e552c9 Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Coat-Weight-with-Normal-Maps---Analytic-Lights.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Coat-Weight-with-Normal-Maps---IBL.png b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Coat-Weight-with-Normal-Maps---IBL.png new file mode 100644 index 00000000000..3ddf12bbc28 Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Coat-Weight-with-Normal-Maps---IBL.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Analytic-Lights.png b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Analytic-Lights.png new file mode 100644 index 00000000000..039246cd912 Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Analytic-Lights.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Furnace-Test.png b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Furnace-Test.png new file mode 100644 index 00000000000..9f06c60c20d Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Furnace-Test.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---IBL.png b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---IBL.png new file mode 100644 index 00000000000..8398fa545a3 Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---IBL.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Realtime-IBL.png b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Realtime-IBL.png new file mode 100644 index 00000000000..41dcb9d89e4 Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/OpenPBR-Fuzz-Weight-vs-Fuzz-Roughness---Realtime-IBL.png differ diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index 662b73ea8b5..31cfc5dca0d 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -3121,6 +3121,36 @@ "excludedEngines": ["webgl1"], "renderCount": 2 }, + { + "title": "OpenPBR Fuzz Weight vs Fuzz Roughness - IBL", + "playgroundId": "#GRQHVV#111", + "excludedEngines": ["webgl1"] + }, + { + "title": "OpenPBR Fuzz Weight vs Fuzz Roughness - Analytic Lights", + "playgroundId": "#GRQHVV#112", + "excludedEngines": ["webgl1"] + }, + { + "title": "OpenPBR Fuzz Weight vs Coat Weight with Normal Maps - Analytic Lights", + "playgroundId": "#GRQHVV#109", + "excludedEngines": ["webgl1"] + }, + { + "title": "OpenPBR Fuzz Weight vs Coat Weight with Normal Maps - IBL", + "playgroundId": "#GRQHVV#108", + "excludedEngines": ["webgl1"] + }, + { + "title": "OpenPBR Fuzz Weight vs Fuzz Roughness - Realtime IBL", + "playgroundId": "#GRQHVV#110", + "excludedEngines": ["webgl1"] + }, + { + "title": "OpenPBR Fuzz Weight vs Fuzz Roughness - Furnace Test", + "playgroundId": "#GRQHVV#115", + "excludedEngines": ["webgl1"] + }, { "title": "Background material blur", "playgroundId": "#UU7RQ#4458"