diff --git a/filament/src/Material.cpp b/filament/src/Material.cpp index ce48ed43c3..91842c63e2 100644 --- a/filament/src/Material.cpp +++ b/filament/src/Material.cpp @@ -347,7 +347,7 @@ backend::Handle FMaterial::getSurfaceProgramSlow(uint8_t var UibGenerator::getPerRenderableBonesUib().getName()); } - addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(), mSamplerBindings); + addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(variantKey), mSamplerBindings); addSamplerGroup(pb, BindingPoints::PER_MATERIAL_INSTANCE, mSamplerInterfaceBlock, mSamplerBindings); return createAndCacheProgram(std::move(pb), variantKey); diff --git a/libs/filabridge/include/private/filament/SibGenerator.h b/libs/filabridge/include/private/filament/SibGenerator.h index 23ab7b7ad3..0edb35d030 100644 --- a/libs/filabridge/include/private/filament/SibGenerator.h +++ b/libs/filabridge/include/private/filament/SibGenerator.h @@ -29,8 +29,8 @@ class SamplerInterfaceBlock; class SibGenerator { public: - static SamplerInterfaceBlock const& getPerViewSib() noexcept; - static SamplerInterfaceBlock const* getSib(uint8_t bindingPoint) noexcept; + static SamplerInterfaceBlock const& getPerViewSib(uint8_t variantKey) noexcept; + static SamplerInterfaceBlock const* getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept; }; struct PerViewSib { diff --git a/libs/filabridge/include/private/filament/Variant.h b/libs/filabridge/include/private/filament/Variant.h index 601721d6e8..f0bf781a67 100644 --- a/libs/filabridge/include/private/filament/Variant.h +++ b/libs/filabridge/include/private/filament/Variant.h @@ -44,7 +44,7 @@ namespace filament { // Variant 0 | VSM | FOG | DEP | SKN | SRE | DYN | DIR | // ...-----+-----+-----+-----+-----+-----+-----+-----+ // Reserved variants: - // Vertex depth 0 0 1 X 0 0 0 + // Vertex depth X 0 1 X 0 0 0 // Fragment depth X 0 1 0 0 0 0 // Reserved X X 1 X X X X // Reserved X X 0 X 1 0 0 @@ -132,6 +132,10 @@ namespace filament { static constexpr uint8_t filterVariantVertex(uint8_t variantKey) noexcept { // filter out vertex variants that are not needed. For e.g. fog doesn't affect the // vertex shader. + if (variantKey & DEPTH) { + // VSM affects the vertex shader, but only for DEPTH variants. + return variantKey & (VERTEX_MASK | VSM); + } return variantKey & VERTEX_MASK; } diff --git a/libs/filabridge/src/SamplerBindingMap.cpp b/libs/filabridge/src/SamplerBindingMap.cpp index ca9c83e443..5d30746d1f 100644 --- a/libs/filabridge/src/SamplerBindingMap.cpp +++ b/libs/filabridge/src/SamplerBindingMap.cpp @@ -26,6 +26,9 @@ namespace filament { void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib, const char* materialName) { + // We assume material variant 0 here, which is sufficient for calculating the binding map. + // The material variant currently only affects sampler formats (for VSM), not offsets. + const uint8_t variantKey = 0; uint8_t offset = 0; size_t maxSamplerIndex = backend::MAX_SAMPLER_COUNT - 1; bool overflow = false; @@ -35,7 +38,7 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib, if (blockIndex == filament::BindingPoints::PER_MATERIAL_INSTANCE) { sib = perMaterialSib; } else { - sib = filament::SibGenerator::getSib(blockIndex); + sib = filament::SibGenerator::getSib(blockIndex, variantKey); } if (sib) { auto sibFields = sib->getSamplerInfoList(); @@ -66,7 +69,7 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib, if (blockIndex == filament::BindingPoints::PER_MATERIAL_INSTANCE) { sib = perMaterialSib; } else { - sib = filament::SibGenerator::getSib(blockIndex); + sib = filament::SibGenerator::getSib(blockIndex, variantKey); } if (sib) { auto sibFields = sib->getSamplerInfoList(); diff --git a/libs/filabridge/src/SamplerInterfaceBlock.cpp b/libs/filabridge/src/SamplerInterfaceBlock.cpp index 0befe381f0..d75ab76379 100644 --- a/libs/filabridge/src/SamplerInterfaceBlock.cpp +++ b/libs/filabridge/src/SamplerInterfaceBlock.cpp @@ -76,7 +76,7 @@ SamplerInterfaceBlock::Builder::~Builder() noexcept = default; SamplerInterfaceBlock::SamplerInterfaceBlock() = default; SamplerInterfaceBlock::SamplerInterfaceBlock(const SamplerInterfaceBlock& rhs) = default; -SamplerInterfaceBlock::SamplerInterfaceBlock(SamplerInterfaceBlock&& rhs) noexcept /* = default */ {}; +SamplerInterfaceBlock::SamplerInterfaceBlock(SamplerInterfaceBlock&& rhs) noexcept = default; SamplerInterfaceBlock& SamplerInterfaceBlock::operator=(const SamplerInterfaceBlock& rhs) = default; SamplerInterfaceBlock& SamplerInterfaceBlock::operator=(SamplerInterfaceBlock&& rhs) /*noexcept*/ = default; SamplerInterfaceBlock::~SamplerInterfaceBlock() noexcept = default; diff --git a/libs/filabridge/src/SibGenerator.cpp b/libs/filabridge/src/SibGenerator.cpp index d8b2059d79..98ba311012 100644 --- a/libs/filabridge/src/SibGenerator.cpp +++ b/libs/filabridge/src/SibGenerator.cpp @@ -16,39 +16,61 @@ #include "private/filament/SibGenerator.h" +#include "private/filament/Variant.h" + #include #include namespace filament { -SamplerInterfaceBlock const& SibGenerator::getPerViewSib() noexcept { +SamplerInterfaceBlock const& SibGenerator::getPerViewSib(uint8_t variantKey) noexcept { using Type = SamplerInterfaceBlock::Type; using Format = SamplerInterfaceBlock::Format; using Precision = SamplerInterfaceBlock::Precision; - // TODO: ideally we'd want this to be constexpr, this is a compile time structure - static SamplerInterfaceBlock sib = SamplerInterfaceBlock::Builder() - .name("Light") - .add("shadowMap", Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM) - .add("records", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM) - .add("froxels", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM) - .add("iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) - .add("iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM) - .add("ssao", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) - .add("ssr", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) - .add("structure", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) + auto buildSib = [] (bool hasVsm) { + auto builder = SamplerInterfaceBlock::Builder(); + + builder + .name("Light"); + + if (hasVsm) { + builder.add("shadowMap", Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH); + } else { + builder.add("shadowMap", Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM); + } + + return builder + .add("records", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM) + .add("froxels", Type::SAMPLER_2D, Format::UINT, Precision::MEDIUM) + .add("iblDFG", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) + .add("iblSpecular", Type::SAMPLER_CUBEMAP, Format::FLOAT, Precision::MEDIUM) + .add("ssao", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) + .add("ssr", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) + .add("structure", Type::SAMPLER_2D, Format::FLOAT, Precision::MEDIUM) .build(); + }; - assert(sib.getSize() == PerViewSib::SAMPLER_COUNT); + // TODO: ideally we'd want these to be constexpr, these are compile time structures. - return sib; + static SamplerInterfaceBlock sibPcf = buildSib(false); + static SamplerInterfaceBlock sibVsm = buildSib(true); + + // SamplerBindingMap relies the assumption that Sibs have the same names and offsets + // regardless of variant. + assert(sibPcf.getSize() == PerViewSib::SAMPLER_COUNT); + assert(sibVsm.getSize() == PerViewSib::SAMPLER_COUNT); + + Variant v(variantKey); + + return v.hasVsm() ? sibVsm : sibPcf; } -SamplerInterfaceBlock const* SibGenerator::getSib(uint8_t bindingPoint) noexcept { +SamplerInterfaceBlock const* SibGenerator::getSib(uint8_t bindingPoint, uint8_t variantKey) noexcept { switch (bindingPoint) { case BindingPoints::PER_VIEW: - return &getPerViewSib(); + return &getPerViewSib(variantKey); case BindingPoints::PER_RENDERABLE: return nullptr; case BindingPoints::LIGHTS: diff --git a/libs/filamat/src/shaders/ShaderGenerator.cpp b/libs/filamat/src/shaders/ShaderGenerator.cpp index 5813be5773..2de04ad1e8 100644 --- a/libs/filamat/src/shaders/ShaderGenerator.cpp +++ b/libs/filamat/src/shaders/ShaderGenerator.cpp @@ -193,6 +193,7 @@ std::string ShaderGenerator::createVertexProgram(filament::backend::ShaderModel cg.generateDefine(vs, "HAS_SHADOWING", litVariants && variant.hasShadowReceiver()); cg.generateDefine(vs, "HAS_SHADOW_MULTIPLIER", material.hasShadowMultiplier); cg.generateDefine(vs, "HAS_SKINNING_OR_MORPHING", variant.hasSkinningOrMorphing()); + cg.generateDefine(vs, "HAS_VSM", variant.hasVsm()); cg.generateDefine(vs, getShadingDefine(material.shading), true); generateMaterialDefines(vs, cg, mProperties, mDefines); @@ -420,7 +421,7 @@ std::string ShaderGenerator::createFragmentProgram(filament::backend::ShaderMode cg.generateSeparator(fs); cg.generateSamplers(fs, material.samplerBindings.getBlockOffset(BindingPoints::PER_VIEW), - SibGenerator::getPerViewSib()); + SibGenerator::getPerViewSib(variantKey)); cg.generateSamplers(fs, material.samplerBindings.getBlockOffset(BindingPoints::PER_MATERIAL_INSTANCE), material.sib); diff --git a/shaders/src/common_lighting.fs b/shaders/src/common_lighting.fs index 4338114c66..27368505ef 100644 --- a/shaders/src/common_lighting.fs +++ b/shaders/src/common_lighting.fs @@ -3,6 +3,7 @@ struct Light { vec3 l; float attenuation; float NoL; + vec3 worldPosition; bool castsShadows; bool contactShadows; uint shadowIndex; diff --git a/shaders/src/depth_main.fs b/shaders/src/depth_main.fs index 7cb5418100..0512dfc58e 100644 --- a/shaders/src/depth_main.fs +++ b/shaders/src/depth_main.fs @@ -13,4 +13,22 @@ void main() { discard; } #endif + +#if defined(HAS_VSM) + // Since we're rendering from the perspective of the light, frameUniforms.cameraPosition is the + // light position, in world space. + // We use "distance to the light" as the depth metric, which works for both directional and spot + // lights. + highp float depth = length(frameUniforms.cameraPosition.xyz - vertex_worldPosition); + + highp float dx = dFdx(depth); + highp float dy = dFdy(depth); + + // Output the first and second depth moments. + // The first moment is mean depth. + // The second moment is mean depth squared. + // These values are retrieved when sampling the shadow map to compute variance. + highp float bias = 0.25 * (dx * dx + dy * dy); + fragColor = vec4(depth, depth * depth + bias, 0.0, 0.0); +#endif } diff --git a/shaders/src/depth_main.vs b/shaders/src/depth_main.vs index e589040c2a..17ff6abf96 100644 --- a/shaders/src/depth_main.vs +++ b/shaders/src/depth_main.vs @@ -3,15 +3,27 @@ void materialVertex(inout MaterialVertexInputs m) { } void main() { -#if defined(VERTEX_DOMAIN_DEVICE) - gl_Position = getPosition(); -#else + +// World position is used to compute gl_Position, except for vertices already in the device domain. +// Regardless of vertex domain, if VSM is turned on, then we need to compute world position to pass +// to the fragment shader. +#if !defined(VERTEX_DOMAIN_DEVICE) || defined(HAS_VSM) + // Run initMaterialVertex to compute material.worldPosition. MaterialVertexInputs material; initMaterialVertex(material); materialVertex(material); +#endif + +#if defined(VERTEX_DOMAIN_DEVICE) + gl_Position = getPosition(); +#else gl_Position = getClipFromWorldMatrix() * getWorldPosition(material); #endif +#if defined(HAS_VSM) + vertex_worldPosition = material.worldPosition.xyz; +#endif + #if defined(TARGET_VULKAN_ENVIRONMENT) // In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up. gl_Position.y = -gl_Position.y; diff --git a/shaders/src/light_directional.fs b/shaders/src/light_directional.fs index 5788805c44..4f8674d045 100644 --- a/shaders/src/light_directional.fs +++ b/shaders/src/light_directional.fs @@ -45,7 +45,12 @@ void evaluateDirectionalLight(const MaterialInputs material, bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1u); if (hasDirectionalShadows && cascadeHasVisibleShadows) { uint layer = cascade; +#if defined(HAS_VSM) + highp float fragDepth = length(vertex_worldPosition - frameUniforms.lightPosition.xyz); + visibility = shadowVsm(light_shadowMap, layer, getCascadeLightSpacePosition(cascade), fragDepth); +#else visibility = shadow(light_shadowMap, layer, getCascadeLightSpacePosition(cascade)); +#endif } if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) { if (objectUniforms.screenSpaceContactShadows != 0u) { diff --git a/shaders/src/light_punctual.fs b/shaders/src/light_punctual.fs index 5b228811bf..7f5f9a5e99 100644 --- a/shaders/src/light_punctual.fs +++ b/shaders/src/light_punctual.fs @@ -130,6 +130,7 @@ Light getLight(const uint index) { light.l = normalize(posToLight); light.attenuation = getDistanceAttenuation(posToLight, positionFalloff.w); light.NoL = saturate(dot(shading_normal, light.l)); + light.worldPosition = positionFalloff.xyz; uint type = floatBitsToUint(scaleOffsetShadowType.w); if (type == LIGHT_TYPE_SPOT) { @@ -170,8 +171,14 @@ void evaluatePunctualLights(const PixelParams pixel, inout vec3 color) { #if defined(HAS_SHADOWING) if (light.NoL > 0.0){ if (light.castsShadows) { +#if defined(HAS_VSM) + highp float fragDepth = length(vertex_worldPosition - light.worldPosition); + visibility = shadowVsm(light_shadowMap, light.shadowLayer, + getSpotLightSpacePosition(light.shadowIndex), fragDepth); +#else visibility = shadow(light_shadowMap, light.shadowLayer, - getSpotLightSpacePosition(light.shadowIndex)); + getSpotLightSpacePosition(light.shadowIndex)); +#endif } if (light.contactShadows && visibility > 0.0) { if (objectUniforms.screenSpaceContactShadows != 0u) { diff --git a/shaders/src/shading_unlit.fs b/shaders/src/shading_unlit.fs index 96f184f02c..03843f8aec 100644 --- a/shaders/src/shading_unlit.fs +++ b/shaders/src/shading_unlit.fs @@ -45,7 +45,11 @@ vec4 evaluateMaterial(const MaterialInputs material) { if ((frameUniforms.directionalShadows & 1u) != 0u) { uint cascade = getShadowCascade(); uint layer = cascade; +#if defined(HAS_VSM) + // TODO: VSM shadow multiplier +#else visibility = shadow(light_shadowMap, layer, getCascadeLightSpacePosition(cascade)); +#endif } if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) { if (objectUniforms.screenSpaceContactShadows != 0u) { diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 3c8cda15cd..21a08d9c0c 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -306,6 +306,32 @@ float screenSpaceContactShadow(vec3 lightDirection) { return occlusion; } +//------------------------------------------------------------------------------ +// VSM +//------------------------------------------------------------------------------ + +highp float linstep(const highp float a, const highp float b, const highp float v) { + return clamp((v - a) / (b - a), 0.0, 1.0); +} + +highp float reduceLightBleed(const highp float pMax, const highp float amount) { + return linstep(amount, 1.0, pMax); +} + +highp float chebyshevUpperBound(const highp vec2 moments, const highp float mean, + const highp float minVariance, const highp float lightBleedReduction) { + // Donnelly and Lauritzen 2006, "Variance Shadow Maps" + + highp float variance = moments.y - (moments.x * moments.x); + variance = max(variance, minVariance); + + const highp float d = mean - moments.x; + highp float pMax = variance / (variance + d * d); + pMax = reduceLightBleed(pMax, lightBleedReduction); + + return mean <= moments.x ? 1.0 : pMax; +} + //------------------------------------------------------------------------------ // Shadow sampling dispatch //------------------------------------------------------------------------------ @@ -327,3 +353,15 @@ float shadow(const lowp sampler2DArrayShadow shadowMap, const uint layer, const return ShadowSample_PCF_High(shadowMap, layer, size, shadowPosition); #endif } + +float shadowVsm(const highp sampler2DArray shadowMap, const uint layer, const highp vec3 shadowPosition, + const highp float fragDepth) { + const highp vec2 moments = texture(shadowMap, vec3(shadowPosition.xy, layer)).xy; + + // TODO: bias and lightBleedReduction should be uniforms + const float bias = 0.01; + const float lightBleedReduction = 0.2; + + const float minVariance = bias * 0.01; + return chebyshevUpperBound(moments, fragDepth, minVariance, lightBleedReduction); +}