From 087f29c6c9643a86fbc9736ee179d8ea76ff7866 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 19 Oct 2021 15:42:45 -0700 Subject: [PATCH] Added support for DPCF --- RELEASE_NOTES.md | 2 + .../src/main/cpp/LightManager.cpp | 5 +- .../google/android/filament/LightManager.java | 10 +- .../com/google/android/filament/View.java | 7 +- filament/include/filament/LightManager.h | 6 + filament/include/filament/Options.h | 3 +- filament/src/Froxelizer.cpp | 2 +- filament/src/PerViewUniforms.cpp | 13 +- filament/src/PerViewUniforms.h | 5 + filament/src/RenderPass.cpp | 3 +- filament/src/RenderPass.h | 1 + filament/src/Renderer.cpp | 6 +- filament/src/ShadowMap.cpp | 15 +- filament/src/ShadowMapManager.cpp | 33 +-- filament/src/ShadowMapManager.h | 3 +- filament/src/View.cpp | 20 +- filament/src/components/LightManager.h | 4 +- filament/src/details/View.h | 3 +- .../include/private/filament/UibStructs.h | 19 +- libs/filamat/src/UibGenerator.cpp | 6 +- libs/viewer/src/Settings.cpp | 2 + libs/viewer/src/SimpleViewer.cpp | 37 ++-- shaders/src/common_types.fs | 2 + shaders/src/getters.fs | 9 +- shaders/src/light_punctual.fs | 2 +- shaders/src/main.vs | 4 +- shaders/src/shadowing.fs | 205 ++++++++++++++++-- 27 files changed, 335 insertions(+), 92 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0115c2061d..a9753afc07 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created. ## v1.15.1 (currently main branch) +- engine: add support for DPCF (PCF shadows with contact hardening) + ## v1.15.0 - engine: Fix spotlights normal bias calculation [⚠️ **Material breakage**]. diff --git a/android/filament-android/src/main/cpp/LightManager.cpp b/android/filament-android/src/main/cpp/LightManager.cpp index 4e1cd23d4a..d467fec4ac 100644 --- a/android/filament-android/src/main/cpp/LightManager.cpp +++ b/android/filament-android/src/main/cpp/LightManager.cpp @@ -79,7 +79,7 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env, jfloat shadowFarHint, jboolean stable, jfloat polygonOffsetConstant, jfloat polygonOffsetSlope, jboolean screenSpaceContactShadows, jint stepCount, - jfloat maxShadowDistance, jint vsmMsaaSamples, jfloat blurWidth) { + jfloat maxShadowDistance, jint vsmMsaaSamples, jfloat blurWidth, jfloat shadowBulbRadius) { LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder; LightManager::ShadowOptions shadowOptions { .mapSize = (uint32_t)mapSize, @@ -98,7 +98,8 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv* env, .vsm = { .msaaSamples = (uint8_t) vsmMsaaSamples, .blurWidth = blurWidth - } + }, + .shadowBulbRadius = shadowBulbRadius }; jfloat *nativeSplits = env->GetFloatArrayElements(splitPositions, NULL); const jsize splitCount = std::min((jsize) 3, env->GetArrayLength(splitPositions)); diff --git a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java index 44c1ed8084..d83c891c50 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/LightManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/LightManager.java @@ -351,6 +351,12 @@ public class LightManager { * The maximum value is 125. */ public float blurWidth = 0.0f; + + /** + * Light bulb radius used for soft shadows. Currently this is only used when DPCF is + * enabled. (2cm by default). + */ + public float shadowBulbRadius = 0.02f; } public static class ShadowCascades { @@ -494,7 +500,7 @@ public class LightManager { options.polygonOffsetConstant, options.polygonOffsetSlope, options.screenSpaceContactShadows, options.stepCount, options.maxShadowDistance, options.vsmMsaaSamples, - options.blurWidth); + options.blurWidth, options.shadowBulbRadius); return this; } @@ -1152,7 +1158,7 @@ public class LightManager { private static native void nDestroyBuilder(long nativeBuilder); private static native boolean nBuilderBuild(long nativeBuilder, long nativeEngine, int entity); private static native void nBuilderCastShadows(long nativeBuilder, boolean enable); - private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, int cascades, float[] splitPositions, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, float polygonOffsetConstant, float polygonOffsetSlope, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance, int vsmMsaaSamples, float blurWidth); + private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, int cascades, float[] splitPositions, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, float polygonOffsetConstant, float polygonOffsetSlope, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance, int vsmMsaaSamples, float blurWidth, float shadowBulbRadius); private static native void nBuilderCastLight(long nativeBuilder, boolean enabled); private static native void nBuilderPosition(long nativeBuilder, float x, float y, float z); private static native void nBuilderDirection(long nativeBuilder, float x, float y, float z); diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index 2351b7a352..112122a3e2 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -736,7 +736,12 @@ public class View { /** * Variance shadows. */ - VSM + VSM, + + /** + * Percentage-closer filtered shadows, with contact hardening simulation. + */ + DPCF } /** diff --git a/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index 46e5de4d0e..ce6ded7f54 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -334,6 +334,12 @@ public: */ float blurWidth = 0.0f; } vsm; + + /** + * Light bulb radius used for soft shadows. Currently this is only used when DPCF is + * enabled. (2cm by default). + */ + float shadowBulbRadius = 0.02f; }; struct ShadowCascades { diff --git a/filament/include/filament/Options.h b/filament/include/filament/Options.h index 7911dcffd6..b7d326f6d6 100644 --- a/filament/include/filament/Options.h +++ b/filament/include/filament/Options.h @@ -339,7 +339,8 @@ enum class Dithering : uint8_t { */ enum class ShadowType : uint8_t { PCF, //!< percentage-closer filtered shadows (default) - VSM //!< variance shadows + VSM, //!< variance shadows + DPCF //!< PCF with contact hardening simulation }; /** diff --git a/filament/src/Froxelizer.cpp b/filament/src/Froxelizer.cpp index 69a9406228..346fd1592f 100644 --- a/filament/src/Froxelizer.cpp +++ b/filament/src/Froxelizer.cpp @@ -339,7 +339,7 @@ bool Froxelizer::update() noexcept { /* - * Now compute the bounding sphere of each froxel, which is needed for spot-lights + * Now compute the bounding sphere of each froxel, which is needed for spotlights * We intersect 3 planes of the frustum to find each 8 corners. * Currently the bounding sphere is computed from the bounding-box, which is probably, * not the best. diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index c8a12625b7..879cd5970c 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -260,6 +260,7 @@ void PerViewUniforms::prepareShadowMapping(ShadowMappingUniforms const& shadowMa s.lightFromWorldMatrix = shadowMappingUniforms.lightFromWorldMatrix; s.cascadeSplits = shadowMappingUniforms.cascadeSplits; + s.shadowBulbRadiusLs = shadowMappingUniforms.shadowBulbRadiusLs; s.shadowBias = shadowMappingUniforms.shadowBias; s.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance; s.directionalShadows = shadowMappingUniforms.directionalShadows; @@ -277,6 +278,8 @@ void PerViewUniforms::prepareShadowVSM(Handle texture, VsmShadowOptio .filterMin = filterMin, .anisotropyLog2 = options.anisotropy, }}); + auto& s = mPerViewUb.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_VSM; } void PerViewUniforms::prepareShadowPCF(Handle texture) noexcept { @@ -284,11 +287,17 @@ void PerViewUniforms::prepareShadowPCF(Handle texture) noexcept { texture, { .filterMag = SamplerMagFilter::LINEAR, .filterMin = SamplerMinFilter::LINEAR, - .compareMode = SamplerCompareMode::COMPARE_TO_TEXTURE, // ignored for VSM - .compareFunc = SamplerCompareFunc::GE // ignored for VSM + .compareMode = SamplerCompareMode::COMPARE_TO_TEXTURE, + .compareFunc = SamplerCompareFunc::GE }}); } +void PerViewUniforms::prepareShadowDPCF(Handle texture) noexcept { + mPerViewSb.setSampler(PerViewSib::SHADOW_MAP, { texture, { }}); + auto& s = mPerViewUb.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_DPCF; +} + void PerViewUniforms::commit(backend::DriverApi& driver) noexcept { if (mPerViewUb.isDirty()) { driver.updateBufferObject(mPerViewUbh, mPerViewUb.toBufferDescriptor(driver), 0); diff --git a/filament/src/PerViewUniforms.h b/filament/src/PerViewUniforms.h index 64fd1cb3a6..0e177e4777 100644 --- a/filament/src/PerViewUniforms.h +++ b/filament/src/PerViewUniforms.h @@ -51,6 +51,10 @@ class PerViewUniforms { using LightManagerInstance = utils::EntityInstance; using TextureHandle = backend::Handle; + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCF = 0u; + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_VSM = 1u; + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_DPCF = 2u; + public: explicit PerViewUniforms(FEngine& engine) noexcept; @@ -79,6 +83,7 @@ public: // maybe these should have their own UBO, they're needed only when GENERATING the shadowmaps void prepareShadowVSM(TextureHandle texture, VsmShadowOptions const& options) noexcept; void prepareShadowPCF(TextureHandle texture) noexcept; + void prepareShadowDPCF(TextureHandle texture) noexcept; // update local data into GPU UBO void commit(backend::DriverApi& driver) noexcept; diff --git a/filament/src/RenderPass.cpp b/filament/src/RenderPass.cpp index 28dd05cb1e..7e24e66bfc 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -286,7 +286,8 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags, materialVariant.setDirectionalLighting(renderFlags & HAS_DIRECTIONAL_LIGHT); materialVariant.setDynamicLighting(renderFlags & HAS_DYNAMIC_LIGHTING); materialVariant.setFog(renderFlags & HAS_FOG); - materialVariant.setVsm((renderFlags & HAS_VSM) && hasShadowing); + // DPCF uses the same color pass shader as VSM (but not for depth) + materialVariant.setVsm(((renderFlags & HAS_VSM) || (renderFlags & HAS_DPCF)) && hasShadowing); materialVariant.setShadowReceiver(false); // this is set per Renderable Command cmdColor; diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 19ecbf068d..38cccd21ce 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -240,6 +240,7 @@ public: static constexpr RenderFlags HAS_FOG = 0x10; static constexpr RenderFlags HAS_VSM = 0x20; static constexpr RenderFlags HAS_PICKING = 0x40; + static constexpr RenderFlags HAS_DPCF = 0x80; // Arena used for commands using Arena = utils::Arena< diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index a37ee35905..49e4b2a275 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -292,7 +292,11 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { if (view.isFrontFaceWindingInverted()) baseRenderFlags |= RenderPass::HAS_INVERSE_FRONT_FACES; RenderPass::RenderFlags colorRenderFlags = baseRenderFlags; - if (view.hasVsm()) colorRenderFlags |= RenderPass::HAS_VSM; + switch (view.getShadowType()) { + case ShadowType::PCF: break; + case ShadowType::VSM: colorRenderFlags |= RenderPass::HAS_VSM; break; + case ShadowType::DPCF: colorRenderFlags |= RenderPass::HAS_DPCF; break; + } RenderPass::RenderFlags structureRenderFlags = baseRenderFlags; if (view.hasPicking()) structureRenderFlags |= RenderPass::HAS_PICKING; diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index 69e819e23b..87e1a26e86 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -343,9 +343,10 @@ void ShadowMap::updateDirectional(const FScene::LightSoa& lightData, size_t inde const mat4 MbMt = getTextureCoordsMapping(); const mat4f St = mat4f(MbMt * S); - // note: in texelSizeWorldSpace() below, we could use Mb * Mt * F * W because - // L * Mp * Mv is a rigid transform (for directional lights) - if (USE_LISPSM) { + // note: in texelSizeWorldSpace() below, we can use Mb * Mt * F * W because + // L * Mp * Mv is a rigid transform for directional lights, and doesn't matter. + // if Wp[3][1] is 0, then LISPSM was cancelled. + if (USE_LISPSM && Wp[3][1] != 0.0f) { mTexelSizeAtOneMeterWs = texelSizeWorldSpace(Wp, mat4f(MbMt * F)); } else { // We know we're using an ortho projection @@ -945,8 +946,8 @@ float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture) const no // orthographic projections. We just need to inverse worldToShadowTexture, // which is guaranteed to be orthographic. // The two first columns give us how a texel maps in world-space. - const float ures = 1.0f / mShadowMapInfo.shadowDimension; - const float vres = 1.0f / mShadowMapInfo.shadowDimension; + const float ures = 1.0f / float(mShadowMapInfo.shadowDimension); + const float vres = 1.0f / float(mShadowMapInfo.shadowDimension); const mat3f shadowTextureToWorld(inverse(worldToShadowTexture)); const float3 Jx = shadowTextureToWorld[0]; const float3 Jy = shadowTextureToWorld[1]; @@ -969,8 +970,8 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF) const // It might be better to do this computation in the vertex shader. float3 p = {0.5, 0.5, 0.0}; - const float ures = 1.0f / mShadowMapInfo.shadowDimension; - const float vres = 1.0f / mShadowMapInfo.shadowDimension; + const float ures = 1.0f / float(mShadowMapInfo.shadowDimension); + const float vres = 1.0f / float(mShadowMapInfo.shadowDimension); const float dres = 1.0f / 65536.0f; constexpr bool JACOBIAN_ESTIMATE = false; diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 6c32e8a814..abb4c992ce 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -163,7 +163,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp .depth = textureRequirements.layers, .levels = textureRequirements.levels, .type = SamplerType::SAMPLER_2D_ARRAY, - .format = view.hasVsm() ? vsmTextureFormat : mTextureFormat + .format = view.hasVSM() ? vsmTextureFormat : mTextureFormat }); }, [=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { }); @@ -191,14 +191,14 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp auto& shadowPass = fg.addPass("Shadow Pass", [&](FrameGraph::Builder& builder, auto& data) { - const bool blur = view.hasVsm() && options->vsm.blurWidth > 0.0f; + const bool blur = view.hasVSM() && options->vsm.blurWidth > 0.0f; FrameGraphRenderPass::Descriptor renderTargetDesc{}; auto attachment = builder.createSubresource(prepareShadowPass->shadows, "Shadowmap Layer", { .layer = layer }); - if (view.hasVsm()) { + if (view.hasVSM()) { // Each shadow pass has its own sample count, but textures are created with // a default count of 1 because we're using "magic resolve" (sample count is // set on the render target). @@ -270,7 +270,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp shadowMap.render(*scene, entry.range, entry.visibilityMask, cameraInfo, &entryPass); const auto& executor = entryPass.getExecutor(); - const bool blur = view.hasVsm() && options->vsm.blurWidth > 0.0f; + const bool blur = view.hasVSM() && options->vsm.blurWidth > 0.0f; view.prepareCamera(cameraInfo); @@ -305,7 +305,7 @@ void ShadowMapManager::render(FrameGraph& fg, FEngine& engine, backend::DriverAp // now emit the blurring passes - if (view.hasVsm()) { + if (view.hasVSM()) { const float blurWidth = options->vsm.blurWidth; if (blurWidth > 0.0f) { const float sigma = (blurWidth + 1.0f) / 6.0f; @@ -349,10 +349,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng .atlasDimension = mTextureAtlasRequirements.size, .textureDimension = uint16_t(options.mapSize), .shadowDimension = uint16_t(options.mapSize - 2u), - .vsm = view.hasVsm(), + .vsm = view.hasVSM(), .polygonOffset = { // handle reversed Z - .slope = view.hasVsm() ? 0.0f : -params.options.polygonOffsetSlope, - .constant = view.hasVsm() ? 0.0f : -params.options.polygonOffsetConstant + .slope = view.hasVSM() ? 0.0f : -params.options.polygonOffsetSlope, + .constant = view.hasVSM() ? 0.0f : -params.options.polygonOffsetConstant } }; @@ -375,7 +375,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng // Texel size is constant for directional light (although that's not true when LISPSM // is used, but in that case we're pretending it is). const float wsTexelSize = shadowMap.getTexelSizAtOneMeterWs(); - mShadowMappingUniforms.shadowBias = float3{ 0, normalBias * wsTexelSize, 0 }; + mShadowMappingUniforms.shadowBias = normalBias * wsTexelSize; + mShadowMappingUniforms.shadowBulbRadiusLs = options.shadowBulbRadius / wsTexelSize; } // Adjust the near and far planes to tightly bound the scene. @@ -498,10 +499,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine .textureDimension = uint16_t(options->mapSize), .shadowDimension = uint16_t(options->mapSize - 2u), .spotIndex = uint16_t(i), - .vsm = view.hasVsm(), + .vsm = view.hasVSM(), .polygonOffset = { // handle reversed Z - .slope = view.hasVsm() ? 0.0f : -params.options.polygonOffsetSlope, - .constant = view.hasVsm() ? 0.0f : -params.options.polygonOffsetConstant + .slope = view.hasVSM() ? 0.0f : -params.options.polygonOffsetSlope, + .constant = view.hasVSM() ? 0.0f : -params.options.polygonOffsetConstant } }; @@ -537,11 +538,15 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias; auto& s = shadowUb.edit(); + const float n = shadowMap.getCamera().getNear(); + const float f = shadowMap.getCamera().getCullingFar(); s.shadows[i].lightFromWorldMatrix = shadowMap.getLightSpaceMatrix(); s.shadows[i].direction = direction; s.shadows[i].normalBias = normalBias * wsTexelSizeAtOneMeter; s.shadows[i].lightFromWorldZ = shadowMap.getLightFromWorldZ(); s.shadows[i].texelSizeAtOneMeter = wsTexelSizeAtOneMeter; + s.shadows[i].bulbRadiusLs = options->shadowBulbRadius / wsTexelSizeAtOneMeter; + s.shadows[i].nearOverFarMinusNear = n / (f - n); shadowTechnique |= ShadowTechnique::SHADOW_MAP; } @@ -586,8 +591,8 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view // Generate mipmaps for VSM when anisotropy is enabled or when requested auto const& vsmShadowOptions = view.getVsmShadowOptions(); - const bool useMipmapping = view.hasVsm() && - ((vsmShadowOptions.anisotropy > 0) || vsmShadowOptions.mipmapping); + const bool useMipmapping = view.hasVSM() && + ((vsmShadowOptions.anisotropy > 0) || vsmShadowOptions.mipmapping); uint8_t mipLevels = 1u; if (useMipmapping) { diff --git a/filament/src/ShadowMapManager.h b/filament/src/ShadowMapManager.h index 71a3d893fc..d9587bea2c 100644 --- a/filament/src/ShadowMapManager.h +++ b/filament/src/ShadowMapManager.h @@ -52,7 +52,8 @@ class RenderPass; struct ShadowMappingUniforms { std::array lightFromWorldMatrix; math::float4 cascadeSplits; - math::float3 shadowBias; + float shadowBulbRadiusLs; + float shadowBias; float ssContactShadowDistance; uint32_t directionalShadows; uint32_t cascades; diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 1afddb6631..9539a139cf 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -305,11 +305,11 @@ void FView::prepareShadowing(FEngine& engine, DriverApi& driver, mShadowMapManager.setShadowCascades(0, &shadowOptions); } - // Find all shadow-casting spot lights. + // Find all shadow-casting spotlights. size_t shadowCastingSpotCount = 0; // We allow a max of CONFIG_MAX_SHADOW_CASTING_SPOTS spot light shadows. Any additional - // shadow-casting spot lights are ignored. + // shadow-casting spotlights are ignored. for (size_t l = FScene::DIRECTIONAL_LIGHTS_COUNT; l < lightData.size(); l++) { // when we get here all the lights should be visible @@ -456,7 +456,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena, * Gather all information needed to render this scene. Apply the world origin to all * objects in the scene. */ - scene->prepare(worldOriginScene, hasVsm()); + scene->prepare(worldOriginScene, hasVSM()); /* * Light culling: runs in parallel with Renderable culling (below) @@ -666,10 +666,16 @@ void FView::prepareStructure(Handle structure) const noexcept { } void FView::prepareShadow(Handle texture) const noexcept { - if (hasVsm()) { - mPerViewUniforms.prepareShadowVSM(texture, mVsmShadowOptions); - } else { - mPerViewUniforms.prepareShadowPCF(texture); + switch (mShadowType) { + case filament::ShadowType::PCF: + mPerViewUniforms.prepareShadowPCF(texture); + break; + case filament::ShadowType::VSM: + mPerViewUniforms.prepareShadowVSM(texture, mVsmShadowOptions); + break; + case filament::ShadowType::DPCF: + mPerViewUniforms.prepareShadowDPCF(texture); + break; } } diff --git a/filament/src/components/LightManager.h b/filament/src/components/LightManager.h index c938a2397a..c683f5156e 100644 --- a/filament/src/components/LightManager.h +++ b/filament/src/components/LightManager.h @@ -90,7 +90,7 @@ public: CANDELA // intensity specified in candela (only applicable to punctual lights) }; - struct ShadowParams { + struct ShadowParams { // TODO: get rid of this struct LightManager::ShadowOptions options; }; @@ -239,7 +239,7 @@ private: DIRECTION, // direction in local-space (i.e. pre-transform) COLOR, // color SHADOW_PARAMS, // state needed for shadowing - SPOT_PARAMS, // state needed for spot lights + SPOT_PARAMS, // state needed for spotlights SUN_ANGULAR_RADIUS, // state for the directional light sun SUN_HALO_SIZE, // state for the directional light sun SUN_HALO_FALLOFF, // state for the directional light sun diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 9342e976ea..cbe13d48ef 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -186,7 +186,8 @@ public: bool hasShadowing() const noexcept { return mHasShadowing; } bool needsShadowMap() const noexcept { return mNeedsShadowMap; } bool hasFog() const noexcept { return mFogOptions.enabled && mFogOptions.density > 0.0f; } - bool hasVsm() const noexcept { return mShadowType == ShadowType::VSM; } + bool hasVSM() const noexcept { return mShadowType == ShadowType::VSM; } + bool hasDPCF() const noexcept { return mShadowType == ShadowType::DPCF; } bool hasPicking() const noexcept { return mActivePickingQueriesList != nullptr; } void renderShadowMaps(FrameGraph& fg, FEngine& engine, FEngine::DriverApi& driver, diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index 5f9e45235b..1227319177 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -70,7 +70,9 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) math::float3 lightDirection; uint32_t fParamsX; // stride-x - math::float3 shadowBias; // unused, normal bias, unused + float shadowBulbRadiusLs; // light radius in light-space + float shadowBias; // normal bias + float reserved; float oneOverFroxelDimensionY; math::float4 zParams; // froxel Z parameters @@ -126,7 +128,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float vsmExponent; float vsmDepthScale; float vsmLightBleedReduction; - float vsmReserved0; + uint32_t shadowSamplingType; // 0: vsm, 1: dpcf float lodBias; float oneOverFarMinusNear; // 1 / (f-n), always positive @@ -142,7 +144,7 @@ static_assert(sizeof(PerViewUib) == sizeof(math::float4) * 128, "PerViewUib should be exactly 2KiB"); // PerRenderableUib must have an alignment of 256 to be compatible with all versions of GLES. -struct alignas(256) PerRenderableUib { +struct alignas(256) PerRenderableUib { // NOLINT(cppcoreguidelines-pro-type-member-init) static constexpr utils::StaticString _name{ "ObjectUniforms" }; math::mat4f worldFromModelMatrix; math::mat3f worldFromModelNormalMatrix; // this gets expanded to 48 bytes during the copy to the UBO @@ -161,7 +163,7 @@ struct alignas(256) PerRenderableUib { }; static_assert(sizeof(PerRenderableUib) % 256 == 0, "sizeof(Transform) should be a multiple of 256"); -struct LightsUib { +struct LightsUib { // NOLINT(cppcoreguidelines-pro-type-member-init) static constexpr utils::StaticString _name{ "LightsUniforms" }; math::float4 positionFalloff; // { float3(pos), 1/falloff^2 } math::float3 direction; // dir @@ -183,7 +185,7 @@ struct LightsUib { static_assert(sizeof(LightsUib) == 64, "the actual UBO is an array of 256 mat4"); // UBO for punctual (spot light) shadows. -struct ShadowUib { +struct ShadowUib { // NOLINT(cppcoreguidelines-pro-type-member-init) static constexpr utils::StaticString _name{ "ShadowUniforms" }; struct alignas(16) ShadowData { math::mat4f lightFromWorldMatrix; @@ -192,19 +194,22 @@ struct ShadowUib { math::float4 lightFromWorldZ; float texelSizeAtOneMeter; + float bulbRadiusLs; + float nearOverFarMinusNear; }; ShadowData shadows[CONFIG_MAX_SHADOW_CASTING_SPOTS]; }; static_assert(sizeof(ShadowUib) <= 16384, "ShadowUib exceed max UBO size"); // UBO froxel record buffer. -struct FroxelRecordUib { +struct FroxelRecordUib { // NOLINT(cppcoreguidelines-pro-type-member-init) static constexpr utils::StaticString _name{ "FroxelRecordUniforms" }; math::uint4 records[1024]; }; +static_assert(sizeof(FroxelRecordUib) == 16384, "FroxelRecordUib should be exactly 16KiB"); // This is not the UBO proper, but just an element of a bone array. -struct PerRenderableUibBone { +struct PerRenderableUibBone { // NOLINT(cppcoreguidelines-pro-type-member-init) static constexpr utils::StaticString _name{ "BonesUniforms" }; math::quatf q = { 1, 0, 0, 0 }; math::float4 t = {}; diff --git a/libs/filamat/src/UibGenerator.cpp b/libs/filamat/src/UibGenerator.cpp index e14249362c..ee2816e17a 100644 --- a/libs/filamat/src/UibGenerator.cpp +++ b/libs/filamat/src/UibGenerator.cpp @@ -56,7 +56,9 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { .add("lightDirection", 1, UniformInterfaceBlock::Type::FLOAT3) .add("fParamsX", 1, UniformInterfaceBlock::Type::UINT) // shadow - .add("shadowBias", 1, UniformInterfaceBlock::Type::FLOAT3) + .add("shadowBulbRadiusLs", 1, UniformInterfaceBlock::Type::FLOAT) + .add("shadowBias", 1, UniformInterfaceBlock::Type::FLOAT) + .add("reserved", 1, UniformInterfaceBlock::Type::FLOAT) .add("oneOverFroxelDimensionY", 1, UniformInterfaceBlock::Type::FLOAT) // froxels .add("zParams", 1, UniformInterfaceBlock::Type::FLOAT4) @@ -107,7 +109,7 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { .add("vsmExponent", 1, UniformInterfaceBlock::Type::FLOAT) .add("vsmDepthScale", 1, UniformInterfaceBlock::Type::FLOAT) .add("vsmLightBleedReduction", 1, UniformInterfaceBlock::Type::FLOAT) - .add("vsmReserved0", 1, UniformInterfaceBlock::Type::FLOAT) + .add("shadowSamplingType", 1, UniformInterfaceBlock::Type::UINT) .add("lodBias", 1, UniformInterfaceBlock::Type::FLOAT) .add("oneOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH) diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index f822519676..68fd20e3e1 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -212,6 +212,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, Ditherin static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ShadowType* out) { if (0 == compare(tokens[i], jsonChunk, "PCF")) { *out = ShadowType::PCF; } else if (0 == compare(tokens[i], jsonChunk, "VSM")) { *out = ShadowType::VSM; } + else if (0 == compare(tokens[i], jsonChunk, "DPCF")) { *out = ShadowType::DPCF; } else { slog.w << "Invalid ShadowType: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; } @@ -1130,6 +1131,7 @@ static std::ostream& operator<<(std::ostream& out, ShadowType in) { switch (in) { case ShadowType::PCF: return out << "\"PCF\""; case ShadowType::VSM: return out << "\"VSM\""; + case ShadowType::DPCF: return out << "\"DPCF\""; } return out << "\"INVALID\""; } diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 9baf01dbcb..3dfa68a73a 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -767,26 +767,29 @@ void SimpleViewer::updateUserInterface() { light.shadowOptions.mapSize = mapSize; - bool enableVsm = mSettings.view.shadowType == ShadowType::VSM; - ImGui::Checkbox("Enable VSM", &enableVsm); - mSettings.view.shadowType = enableVsm ? ShadowType::VSM : ShadowType::PCF; + int shadowType = (int)mSettings.view.shadowType; + ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0\0"); + mSettings.view.shadowType = (ShadowType)shadowType; - char label[32]; - snprintf(label, 32, "%d", 1 << mVsmMsaaSamplesLog2); - ImGui::SliderInt("VSM MSAA samples", &mVsmMsaaSamplesLog2, 0, 3, label); - light.shadowOptions.vsm.msaaSamples = static_cast(1u << mVsmMsaaSamplesLog2); + if (mSettings.view.shadowType == ShadowType::VSM) { + char label[32]; + snprintf(label, 32, "%d", 1 << mVsmMsaaSamplesLog2); + ImGui::SliderInt("VSM MSAA samples", &mVsmMsaaSamplesLog2, 0, 3, label); + light.shadowOptions.vsm.msaaSamples = + static_cast(1u << mVsmMsaaSamplesLog2); - int vsmAnisotropy = mSettings.view.vsmShadowOptions.anisotropy; - snprintf(label, 32, "%d", 1 << vsmAnisotropy); - ImGui::SliderInt("VSM anisotropy", &vsmAnisotropy, 0, 3, label); - mSettings.view.vsmShadowOptions.anisotropy = vsmAnisotropy; - ImGui::Checkbox("VSM mipmapping", &mSettings.view.vsmShadowOptions.mipmapping); - ImGui::SliderFloat("VSM blur", &light.shadowOptions.vsm.blurWidth, 0.0f, 125.0f); + int vsmAnisotropy = mSettings.view.vsmShadowOptions.anisotropy; + snprintf(label, 32, "%d", 1 << vsmAnisotropy); + ImGui::SliderInt("VSM anisotropy", &vsmAnisotropy, 0, 3, label); + mSettings.view.vsmShadowOptions.anisotropy = vsmAnisotropy; + ImGui::Checkbox("VSM mipmapping", &mSettings.view.vsmShadowOptions.mipmapping); + ImGui::SliderFloat("VSM blur", &light.shadowOptions.vsm.blurWidth, 0.0f, 125.0f); - // These are not very useful in practice (defaults are good), but we keep them here for debugging - //ImGui::SliderFloat("VSM exponent", &mSettings.view.vsmShadowOptions.exponent, 0.0, 6.0f); - //ImGui::SliderFloat("VSM Light bleed", &mSettings.view.vsmShadowOptions.lightBleedReduction, 0.0, 1.0f); - //ImGui::SliderFloat("VSM min variance scale", &mSettings.view.vsmShadowOptions.minVarianceScale, 0.0, 10.0f); + // These are not very useful in practice (defaults are good), but we keep them here for debugging + //ImGui::SliderFloat("VSM exponent", &mSettings.view.vsmShadowOptions.exponent, 0.0, 6.0f); + //ImGui::SliderFloat("VSM Light bleed", &mSettings.view.vsmShadowOptions.lightBleedReduction, 0.0, 1.0f); + //ImGui::SliderFloat("VSM min variance scale", &mSettings.view.vsmShadowOptions.minVarianceScale, 0.0, 10.0f); + } int shadowCascades = light.shadowOptions.shadowCascades; ImGui::SliderInt("Cascades", &shadowCascades, 1, 4); diff --git a/shaders/src/common_types.fs b/shaders/src/common_types.fs index 6a05208049..7c638247ca 100644 --- a/shaders/src/common_types.fs +++ b/shaders/src/common_types.fs @@ -31,4 +31,6 @@ struct ShadowData { float normalBias; highp vec4 lightFromWorldZ; float texelSizeAtOneMeter; + float bulbRadiusLs; + float nearOverFarMinusNear; }; diff --git a/shaders/src/getters.fs b/shaders/src/getters.fs index bdf161838d..de67b3a4c6 100644 --- a/shaders/src/getters.fs +++ b/shaders/src/getters.fs @@ -99,15 +99,14 @@ highp vec3 getNormalizedViewportCoord2() { } #if defined(HAS_SHADOWING) && defined(HAS_DYNAMIC_LIGHTING) -highp vec4 getSpotLightSpacePosition(uint index) { +highp vec4 getSpotLightSpacePosition(uint index, highp float zLight) { highp mat4 lightFromWorldMatrix = shadowUniforms.shadows[index].lightFromWorldMatrix; highp vec3 dir = shadowUniforms.shadows[index].direction; // for spotlights, the bias depends on z - highp float z = dot(shadowUniforms.shadows[index].lightFromWorldZ, vertex_worldPosition); - float bias = shadowUniforms.shadows[index].normalBias * z; + float bias = shadowUniforms.shadows[index].normalBias * zLight; - return computeLightSpacePosition(vertex_worldPosition.xyz, + return computeLightSpacePosition(getWorldPosition(), vertex_worldNormal, dir, bias, lightFromWorldMatrix); } #endif @@ -140,7 +139,7 @@ highp vec4 getCascadeLightSpacePosition(uint cascade) { } return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(), - frameUniforms.lightDirection, frameUniforms.shadowBias.y, + frameUniforms.lightDirection, frameUniforms.shadowBias, frameUniforms.lightFromWorldMatrix[cascade]); } diff --git a/shaders/src/light_punctual.fs b/shaders/src/light_punctual.fs index 870151e433..752458832a 100644 --- a/shaders/src/light_punctual.fs +++ b/shaders/src/light_punctual.fs @@ -143,7 +143,7 @@ Light getLight(const uint index) { highp uint channels = floatBitsToUint(data[3][3]); // poition-to-light vector - highp vec3 worldPosition = vertex_worldPosition.xyz; + highp vec3 worldPosition = getWorldPosition(); highp vec3 posToLight = positionFalloff.xyz - worldPosition; // and populate the Light structure diff --git a/shaders/src/main.vs b/shaders/src/main.vs index 25cf121c15..32637d1c71 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -114,7 +114,7 @@ void main() { #if defined(HAS_SHADOWING) && defined(HAS_DIRECTIONAL_LIGHTING) vertex_lightSpacePosition = computeLightSpacePosition( vertex_worldPosition.xyz, vertex_worldNormal, - frameUniforms.lightDirection, frameUniforms.shadowBias.y, getLightFromWorldMatrix()); + frameUniforms.lightDirection, frameUniforms.shadowBias, getLightFromWorldMatrix()); #endif #endif // !defined(USE_OPTIMIZED_DEPTH_VERTEX_SHADER) @@ -145,7 +145,7 @@ void main() { // (see ShadowMap.cpp). // Use vertex_worldPosition.w which is otherwise not used to store the interpolated // light-space depth. - highp float z = (frameUniforms.viewFromWorldMatrix * vec4(material.worldPosition.xyz, 1.0)).z; + highp float z = (getViewFromWorldMatrix() * getWorldPosition(material)).z; // rescale [near, far] to [0, 1] highp float depth = -z * frameUniforms.oneOverFarMinusNear - frameUniforms.nearOverFarMinusNear; diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 3108e98c5d..63b1de2a3b 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -1,3 +1,12 @@ +//------------------------------------------------------------------------------ +// Shadow Sampling Types +//------------------------------------------------------------------------------ + +// Keep this in sync with PerViewUniforms.h +#define SHADOW_SAMPLING_RUNTIME_PCF 0u +#define SHADOW_SAMPLING_RUNTIME_VSM 1u +#define SHADOW_SAMPLING_RUNTIME_DPCF 2u + //------------------------------------------------------------------------------ // PCF Shadow Sampling //------------------------------------------------------------------------------ @@ -12,13 +21,17 @@ float sampleDepth(const mediump sampler2DArrayShadow map, const uint layer, // use hardware assisted PCF float ShadowSample_PCF(const mediump sampler2DArrayShadow map, - const uint layer, const highp vec3 position) { + const uint layer, const highp vec4 shadowPosition) { + highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); + // note: shadowPosition.z is in the [1, 0] range (reversed Z) return sampleDepth(map, layer, position.xy, position.z); } // use manual PCF float ShadowSample_PCF(const mediump sampler2DArray shadowMap, - const uint layer, const highp vec3 position) { + const uint layer, const highp vec4 shadowPosition) { + highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); + // note: shadowPosition.z is in the [1, 0] range (reversed Z) highp vec2 size = vec2(textureSize(shadowMap, 0)); highp vec2 st = position.xy * size - 0.5; vec4 d; @@ -31,11 +44,154 @@ float ShadowSample_PCF(const mediump sampler2DArray shadowMap, d[2] = texelFetchOffset(shadowMap, tc, 0, ivec2(1, 0)).r; d[3] = texelFetchOffset(shadowMap, tc, 0, ivec2(0, 0)).r; #endif - vec4 pcf = step(0.0, d - position.zzzz); + vec4 pcf = step(0.0, position.zzzz - d); highp vec2 grad = fract(st); return mix(mix(pcf.w, pcf.z, grad.x), mix(pcf.x, pcf.y, grad.x), grad.y); } +//------------------------------------------------------------------------------ +// DPCF sampling +//------------------------------------------------------------------------------ + +const uint DPCF_SHADOW_TAP_COUNT = 12u; // 12 max +const vec2 poissonDisk[12] = vec2[12]( + vec2(-0.326,-0.406), vec2(-0.840,-0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621), + vec2( 0.962,-0.195), vec2( 0.473,-0.480), vec2( 0.519, 0.767), vec2( 0.185,-0.893), + vec2( 0.507, 0.064), vec2( 0.896, 0.412), vec2(-0.322,-0.933), vec2(-0.792,-0.598) +); + +float random(const highp vec2 w) { + const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189); + return fract(m.z * fract(dot(w, m.xy))); +} + +float hardenedKernel(float x) { + // this is basically a stronger smoothstep() + x = 2.0 * x - 1.0; + float s = sign(x); + x = 1.0 - s * x; + x = x * x * x; + x = s - x * s; + return 0.5 * x + 0.5; +} + +highp vec2 computeReceiverPlaneDepthBias(const highp vec3 position) { + // see: GDC '06: Shadow Mapping: GPU-based Tips and Techniques + // Chain rule to compute dz/du and dz/dv + // |dz/du| |du/dx du/dy|^-T |dz/dx| + // |dz/dv| = |dv/dx dv/dy| * |dz/dy| + highp vec3 duvz_dx = dFdx(position); + highp vec3 duvz_dy = dFdy(position); + highp vec2 dz_duv = inverse(transpose(mat2(duvz_dx.xy, duvz_dy.xy))) * vec2(duvz_dx.z, duvz_dy.z); + return dz_duv; +} + +mat2 getRandomRotationMatrix(highp vec2 fragCoord) { + // rotate the poisson disk randomly + fragCoord += vec2(frameUniforms.temporalNoise); // 0 when TAA is not used + float randomAngle = random(fragCoord) * (2.0 * PI); + vec2 randomBase = vec2(cos(randomAngle), sin(randomAngle)); + mat2 R = mat2(randomBase.x, randomBase.y, -randomBase.y, randomBase.x); + return R; +} + +float getPenumbra(const bool DIRECTIONAL, const uint index, const highp float zLight) { + float penumbra; + // This conditional is resolved at compile time + if (DIRECTIONAL) { + penumbra = frameUniforms.shadowBulbRadiusLs; + } else { + // the penumbra radius depends on the light-space z for spotlights + penumbra = shadowUniforms.shadows[index].bulbRadiusLs / zLight; + } + return penumbra; +} + +float getPenumbraRatio(const bool DIRECTIONAL, const uint index, + float z_receiver, float z_blocker) { + // z_receiver/z_blocker are not linear depths (i.e. they're not distances) + // Penumbra ratio for PCSS is given by: pr = (d_receiver - d_blocker) / d_blocker + float penumbraRatio; + if (DIRECTIONAL) { + // TODO: take lispsm into account + // For directional lights, the depths are linear but depend on the position (because of LiSPSM). + // With: z_linear = f + z * (n - f) + // We get: (r-b)/b ==> (f/(n-f) + r_linear) / (f/(n-f) + b_linear) - 1 + // Assuming f>>n and ignoring LISPSM, we get: + penumbraRatio = (z_blocker - z_receiver) / (1.0 - z_blocker); + } else { + // For spotlights, the depths are congruent to 1/z, specifically: + // z_linear = (n * f) / (n + z * (f - n)) + // replacing in (r - b) / b gives: + float nearOverFarMinusNear = shadowUniforms.shadows[index].nearOverFarMinusNear; + penumbraRatio = (nearOverFarMinusNear + z_blocker) / (nearOverFarMinusNear + z_receiver) - 1.0; + } + return penumbraRatio; +} + +/* + * DPCF, PCF with contact hardenning simulation. + * see "Shadow of Cold War", A scalable approach to shadowing -- by Kevin Myers + */ +float ShadowSample_DPCF(const bool DIRECTIONAL, + const mediump sampler2DArray map, const uint layer, const uint index, + const highp vec4 shadowPosition, const highp float zLight) { + highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); + highp vec2 texelSize = vec2(1.0) / vec2(textureSize(map, 0)); + + // We need to use the shadow receiver plane depth bias to combat shadow acne due to the + // large kernel. + highp vec2 dz_duv = computeReceiverPlaneDepthBias(position); + + float penumbra = getPenumbra(DIRECTIONAL, index, zLight); + + // rotate the poisson disk randomly + mat2 R = getRandomRotationMatrix(gl_FragCoord.xy); + + float occludedCount = 0.0; + float z_occSum = 0.0; + for (uint i = 0u; i < DPCF_SHADOW_TAP_COUNT; i++) { + highp vec2 duv = R * poissonDisk[i] * (texelSize * penumbra); + float z_occ = textureLod(map, vec3(position.xy + duv, layer), 0.0).r; + + // note: z_occ and z_rec are not necessarily linear here, comparing them is always okay for + // the regular PCF, but the "distance" is meaningless unless they are actually linear + // (e.g.: for the directional light). + // Either way, if we assume that all the samples are close to each other we can take their + // average regardless, and the average depth value of the occluders + // becomes: z_occSum / occludedCount. + + // receiver plane depth bias + float z_bias = dot(dz_duv, duv); + float dz = z_occ - position.z; // dz>0 when blocker is between receiver and light + float occluded = step(z_bias, dz); + occludedCount += occluded; + z_occSum += z_occ * occluded; + } + + // early exit if there is no occluders at all, also avoids a divide-by-zero below. + if (z_occSum == 0.0) { + return 1.0; + } + + float penumbraRatio = getPenumbraRatio(DIRECTIONAL, index, position.z, z_occSum / occludedCount); + + // The main way we're diverging from PCSS is that we're not going to sample again, instead + // we're going to reuse the blocker search samples and we're going to use the penumbra ratio + // as a parameter to lerp between a hardened PCF kernel and the search PCF kernel. + // We need a parameter to blend between the the "hardened" kernel and the "soft" kernel, + // to this end clamp the penumbra ratio between 0 (blocker is close to the receiver) and + // 1 (blocker is close to the light). + penumbraRatio = saturate(penumbraRatio); + + // regular PCF weight (i.e. average of samples in shadow) + float percentageOccluded = occludedCount * (1.0 / float(DPCF_SHADOW_TAP_COUNT)); + + // now we just need to lerp between hardened PCF and regular PCF based on alpha + percentageOccluded = mix(hardenedKernel(percentageOccluded), percentageOccluded, penumbraRatio); + return 1.0 - percentageOccluded; +} + //------------------------------------------------------------------------------ // Screen-space Contact Shadows //------------------------------------------------------------------------------ @@ -147,7 +303,13 @@ float evaluateShadowVSM(const highp vec2 moments, const highp float depth) { } float ShadowSample_VSM(const mediump sampler2DArray shadowMap, - const uint layer, const highp vec3 position) { + const uint layer, const highp vec4 shadowPosition) { + + // note: shadowPosition.z is in linear light-space normalized to [0, 1] + // see: ShadowMap::computeVsmLightSpaceMatrix() in ShadowMap.cpp + // see: computeLightSpacePosition() in common_shadowing.fs + highp vec3 position = vec3(shadowPosition.xy * (1.0 / shadowPosition.w), shadowPosition.z); + // Read the shadow map with all available filtering highp vec4 moments = texture(shadowMap, vec3(position.xy, layer)); highp float depth = position.z; @@ -186,21 +348,21 @@ float shadow(const bool DIRECTIONAL, #endif } else { #if defined(HAS_DYNAMIC_LIGHTING) - shadowPosition = getSpotLightSpacePosition(index); + highp float zLight = dot(shadowUniforms.shadows[index].lightFromWorldZ, vec4(getWorldPosition(), 1.0)); + shadowPosition = getSpotLightSpacePosition(index, zLight); #endif } - highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); - // note: shadowPosition.z is in the [1, 0] range (reversed Z) - return ShadowSample_PCF(shadowMap, layer, position); + return ShadowSample_PCF(shadowMap, layer, shadowPosition); } -// VSM sampling +// VSM or DPCF sampling float shadow(const bool DIRECTIONAL, const mediump sampler2DArray shadowMap, const uint layer, const uint index, const uint cascade) { highp vec4 shadowPosition; + highp float zLight = 0.0; // This conditional is resolved at compile time if (DIRECTIONAL) { @@ -209,13 +371,26 @@ float shadow(const bool DIRECTIONAL, #endif } else { #if defined(HAS_DYNAMIC_LIGHTING) - shadowPosition = getSpotLightSpacePosition(index); + zLight = dot(shadowUniforms.shadows[index].lightFromWorldZ, vec4(getWorldPosition(), 1.0)); + shadowPosition = getSpotLightSpacePosition(index, zLight); #endif } - // note: shadowPosition.z is in linear light-space normalized to [0, 1] - // see: ShadowMap::computeVsmLightSpaceMatrix() in ShadowMap.cpp - // see: computeLightSpacePosition() in common_shadowing.fs - highp vec3 position = vec3(shadowPosition.xy * (1.0 / shadowPosition.w), shadowPosition.z); - return ShadowSample_VSM(shadowMap, layer, position); + if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_VSM) { + return ShadowSample_VSM(shadowMap, layer, shadowPosition); + } + + if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_DPCF) { + return ShadowSample_DPCF(DIRECTIONAL, shadowMap, layer, index, shadowPosition, zLight); + } + + if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_PCF) { + // This is here mostly for debugging at this point. + // Note: In this codepath, the normal bias is not applied because we're in the VSM variant. + // (see: get{Cascade|Spot}LightSpacePosition) + return ShadowSample_PCF(shadowMap, layer, shadowPosition); + } + + // should not happen + return 0.0; }