From 078de4e4222bcd511ec5f79e5a453612b9662a7d Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 2 Dec 2021 12:04:38 -0800 Subject: [PATCH] Add support for basic PCSS --- RELEASE_NOTES.md | 2 + .../filament-android/src/main/cpp/View.cpp | 10 ++ .../com/google/android/filament/View.java | 63 ++++++- filament/include/filament/LightManager.h | 2 +- filament/include/filament/Options.h | 24 ++- filament/include/filament/View.h | 23 +++ filament/src/PerViewUniforms.cpp | 14 +- filament/src/PerViewUniforms.h | 5 +- filament/src/RenderPass.cpp | 4 +- filament/src/RenderPass.h | 2 +- filament/src/Renderer.cpp | 5 +- filament/src/ShadowMapManager.cpp | 8 +- filament/src/ShadowMapManager.h | 19 ++- filament/src/View.cpp | 13 +- filament/src/details/View.h | 14 +- .../include/private/filament/UibStructs.h | 2 +- libs/filamat/src/UibGenerator.cpp | 2 +- libs/viewer/include/viewer/Settings.h | 3 +- libs/viewer/src/AutomationEngine.cpp | 2 +- libs/viewer/src/Settings.cpp | 40 ++++- libs/viewer/src/SimpleViewer.cpp | 7 +- samples/gltf_viewer.cpp | 4 + shaders/src/shadowing.fs | 158 +++++++++++++++--- 23 files changed, 371 insertions(+), 55 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9912d78180..bf930bcdd7 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.2 (currently main branch) +- engine: add support for PCSS (Percentage Closer Soft Shadows) + ## v1.15.1 - engine: add support for DPCF (PCF shadows with contact hardening) diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index ea65587dcc..8ace3f686c 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -160,6 +160,16 @@ Java_com_google_android_filament_View_nSetVsmShadowOptions(JNIEnv*, jclass, jlon view->setVsmShadowOptions(options); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_View_nSetSoftShadowOptions(JNIEnv*, jclass, jlong nativeView, + jfloat penumbraScale, jfloat penumbraRatioScale) { + View* view = (View*) nativeView; + View::SoftShadowOptions options; + options.penumbraScale = penumbraScale; + options.penumbraRatioScale = penumbraRatioScale; + view->setSoftShadowOptions(options); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetRenderQuality(JNIEnv*, jclass, 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 0db17c435a..9565a3f2bc 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 @@ -81,6 +81,7 @@ public class View { private TemporalAntiAliasingOptions mTemporalAntiAliasingOptions; private MultiSampleAntiAliasingOptions mMultiSampleAntiAliasingOptions; private VsmShadowOptions mVsmShadowOptions; + private SoftShadowOptions mSoftShadowOptions; /** * Generic quality level. @@ -741,7 +742,12 @@ public class View { /** * Percentage-closer filtered shadows, with contact hardening simulation. */ - DPCF + DPCF, + + /** + * Percentage-closer soft shadows + */ + PCSS } /** @@ -780,6 +786,29 @@ public class View { public float lightBleedReduction = 0.2f; } + /** + * View-level options for DPCF and PCSS Shadowing. + * + * Warning: This API is still experimental and subject to change. + * + * @see View#setSoftShadowOptions + */ + public static class SoftShadowOptions { + /** + * Globally scales the penumbra of all DPCF and PCSS shadows + * Acceptable values are greater than 0 + */ + public float penumbraScale = 1.0f; + + /** + * Globally scales the computed penumbra ratio of all DPCF and PCSS shadows. + * This effectively controls the strength of contact hardening effect and is useful for + * artistic purposes. Higher values make the shadows become softer faster. + * Acceptable values are equal to or greater than 1. + */ + public float penumbraRatioScale = 1.0f; + } + /** * Used to select buffers. */ @@ -1433,6 +1462,37 @@ public class View { return mVsmShadowOptions; } + /** + * Sets soft shadowing options that apply across the entire View. + * + * Additional light-specific VSM options can be set with + * {@link LightManager.Builder#shadowOptions}. + * + * Only applicable when shadow type is set to ShadowType.DPCF. + * + * Warning: This API is still experimental and subject to change. + * + * @param options Options for shadowing. + * @see #setShadowType + */ + public void setSoftShadowOptions(@NonNull SoftShadowOptions options) { + mSoftShadowOptions = options; + nSetSoftShadowOptions(getNativeObject(), options.penumbraScale, options.penumbraRatioScale); + } + + /** + * Gets soft shadowing options associated with this View. + * @see #setSoftShadowOptions + * @return soft shadow options currently set. + */ + @NonNull + public SoftShadowOptions getSoftShadowOptions() { + if (mSoftShadowOptions == null) { + mSoftShadowOptions = new SoftShadowOptions(); + } + return mSoftShadowOptions; + } + /** * Activates or deactivates ambient occlusion. * @see #setAmbientOcclusionOptions @@ -1699,6 +1759,7 @@ public class View { private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar); private static native void nSetShadowType(long nativeView, int type); private static native void nSetVsmShadowOptions(long nativeView, int anisotropy, boolean mipmapping, float minVarianceScale, float lightBleedReduction); + private static native void nSetSoftShadowOptions(long nativeView, float penumbraScale, float penumbraRatioScale); private static native void nSetColorGrading(long nativeView, long nativeColorGrading); private static native void nSetPostProcessingEnabled(long nativeView, boolean enabled); private static native boolean nIsPostProcessingEnabled(long nativeView); diff --git a/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index ce6ded7f54..fad7d09df3 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -336,7 +336,7 @@ public: } vsm; /** - * Light bulb radius used for soft shadows. Currently this is only used when DPCF is + * Light bulb radius used for soft shadows. Currently this is only used when DPCF or PCSS is * enabled. (2cm by default). */ float shadowBulbRadius = 0.02f; diff --git a/filament/include/filament/Options.h b/filament/include/filament/Options.h index e2ed208a2a..5904bc1e34 100644 --- a/filament/include/filament/Options.h +++ b/filament/include/filament/Options.h @@ -340,7 +340,8 @@ enum class Dithering : uint8_t { enum class ShadowType : uint8_t { PCF, //!< percentage-closer filtered shadows (default) VSM, //!< variance shadows - DPCF //!< PCF with contact hardening simulation + DPCF, //!< PCF with contact hardening simulation + PCSS //!< PCF with soft shadows and contact hardening }; /** @@ -373,6 +374,27 @@ struct VsmShadowOptions { float lightBleedReduction = 0.15f; }; +/** + * View-level options for DPCF and PCSS Shadowing. + * @see setSoftShadowOptions() + * @warning This API is still experimental and subject to change. + */ +struct SoftShadowOptions { + /** + * Globally scales the penumbra of all DPCF and PCSS shadows + * Acceptable values are greater than 0 + */ + float penumbraScale = 1.0f; + + /** + * Globally scales the computed penumbra ratio of all DPCF and PCSS shadows. + * This effectively controls the strength of contact hardening effect and is useful for + * artistic purposes. Higher values make the shadows become softer faster. + * Acceptable values are equal to or greater than 1. + */ + float penumbraRatioScale = 1.0f; +}; + } // namespace filament #endif //TNT_FILAMENT_OPTIONS_H diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h index 2ba9e428e3..ef13813eeb 100644 --- a/filament/include/filament/View.h +++ b/filament/include/filament/View.h @@ -82,6 +82,7 @@ public: using TemporalAntiAliasingOptions = TemporalAntiAliasingOptions; using MultiSampleAntiAliasingOptions = MultiSampleAntiAliasingOptions; using VsmShadowOptions = VsmShadowOptions; + using SoftShadowOptions = SoftShadowOptions; /** * Sets the View's name. Only useful for debugging. @@ -537,6 +538,28 @@ public: */ VsmShadowOptions getVsmShadowOptions() const noexcept; + /** + * Sets soft shadowing options that apply across the entire View. + * + * Additional light-specific soft shadow parameters can be set with LightManager::setShadowOptions. + * + * Only applicable when shadow type is set to ShadowType::DPCF or ShadowType::PCSS. + * + * @param options Options for shadowing. + * + * @see setShadowType + * + * @warning This API is still experimental and subject to change. + */ + void setSoftShadowOptions(SoftShadowOptions const& options) noexcept; + + /** + * Returns the soft shadowing options associated with this View. + * + * @return value set by setSoftShadowOptions(). + */ + SoftShadowOptions getSoftShadowOptions() const noexcept; + /** * Enables or disables post processing. Enabled by default. * diff --git a/filament/src/PerViewUniforms.cpp b/filament/src/PerViewUniforms.cpp index 5000074590..2e93cd3908 100644 --- a/filament/src/PerViewUniforms.cpp +++ b/filament/src/PerViewUniforms.cpp @@ -292,12 +292,24 @@ void PerViewUniforms::prepareShadowPCF(Handle texture) noexcept { .compareMode = SamplerCompareMode::COMPARE_TO_TEXTURE, .compareFunc = SamplerCompareFunc::GE }}); + auto& s = mPerViewUb.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCF; } -void PerViewUniforms::prepareShadowDPCF(Handle texture) noexcept { +void PerViewUniforms::prepareShadowDPCF(Handle texture, + SoftShadowOptions const& options) noexcept { mPerViewSb.setSampler(PerViewSib::SHADOW_MAP, { texture, { }}); auto& s = mPerViewUb.edit(); s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_DPCF; + s.shadowPenumbraRatioScale = options.penumbraRatioScale; +} + +void PerViewUniforms::prepareShadowPCSS(Handle texture, + SoftShadowOptions const& options) noexcept { + mPerViewSb.setSampler(PerViewSib::SHADOW_MAP, { texture, { }}); + auto& s = mPerViewUb.edit(); + s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCSS; + s.shadowPenumbraRatioScale = options.penumbraRatioScale; } void PerViewUniforms::commit(backend::DriverApi& driver) noexcept { diff --git a/filament/src/PerViewUniforms.h b/filament/src/PerViewUniforms.h index 0e177e4777..63d7ac17f7 100644 --- a/filament/src/PerViewUniforms.h +++ b/filament/src/PerViewUniforms.h @@ -35,6 +35,7 @@ namespace filament { struct AmbientOcclusionOptions; struct DynamicResolutionOptions; struct FogOptions; +struct SoftShadowOptions; struct TemporalAntiAliasingOptions; struct VsmShadowOptions; @@ -54,6 +55,7 @@ class PerViewUniforms { 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; + static constexpr uint32_t const SHADOW_SAMPLING_RUNTIME_PCSS = 3u; public: explicit PerViewUniforms(FEngine& engine) noexcept; @@ -83,7 +85,8 @@ 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; + void prepareShadowDPCF(TextureHandle texture, SoftShadowOptions const& options) noexcept; + void prepareShadowPCSS(TextureHandle texture, SoftShadowOptions const& options) 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 7e24e66bfc..708d563ff4 100644 --- a/filament/src/RenderPass.cpp +++ b/filament/src/RenderPass.cpp @@ -286,8 +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); - // DPCF uses the same color pass shader as VSM (but not for depth) - materialVariant.setVsm(((renderFlags & HAS_VSM) || (renderFlags & HAS_DPCF)) && hasShadowing); + // DPCF and PCSS uses the same color pass shader as VSM (but not for depth) + materialVariant.setVsm(((renderFlags & HAS_VSM) || (renderFlags & HAS_DPCF_OR_PCSS)) && hasShadowing); materialVariant.setShadowReceiver(false); // this is set per Renderable Command cmdColor; diff --git a/filament/src/RenderPass.h b/filament/src/RenderPass.h index 38cccd21ce..c1fa00eea2 100644 --- a/filament/src/RenderPass.h +++ b/filament/src/RenderPass.h @@ -240,7 +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; + static constexpr RenderFlags HAS_DPCF_OR_PCSS = 0x80; // Arena used for commands using Arena = utils::Arena< diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 49e4b2a275..b03ea974f1 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -294,8 +294,9 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { RenderPass::RenderFlags colorRenderFlags = baseRenderFlags; switch (view.getShadowType()) { case ShadowType::PCF: break; - case ShadowType::VSM: colorRenderFlags |= RenderPass::HAS_VSM; break; - case ShadowType::DPCF: colorRenderFlags |= RenderPass::HAS_DPCF; break; + case ShadowType::VSM: colorRenderFlags |= RenderPass::HAS_VSM; break; + case ShadowType::DPCF: colorRenderFlags |= RenderPass::HAS_DPCF_OR_PCSS; break; + case ShadowType::PCSS: colorRenderFlags |= RenderPass::HAS_DPCF_OR_PCSS; break; } RenderPass::RenderFlags structureRenderFlags = baseRenderFlags; diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index abb4c992ce..1614e231cc 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -376,7 +376,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng // is used, but in that case we're pretending it is). const float wsTexelSize = shadowMap.getTexelSizAtOneMeterWs(); mShadowMappingUniforms.shadowBias = normalBias * wsTexelSize; - mShadowMappingUniforms.shadowBulbRadiusLs = options.shadowBulbRadius / wsTexelSize; + mShadowMappingUniforms.shadowBulbRadiusLs = + mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / wsTexelSize; } // Adjust the near and far planes to tightly bound the scene. @@ -545,8 +546,9 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine 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); + s.shadows[i].bulbRadiusLs = + mSoftShadowOptions.penumbraScale * options->shadowBulbRadius / wsTexelSizeAtOneMeter; shadowTechnique |= ShadowTechnique::SHADOW_MAP; } @@ -594,6 +596,8 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view const bool useMipmapping = view.hasVSM() && ((vsmShadowOptions.anisotropy > 0) || vsmShadowOptions.mipmapping); + mSoftShadowOptions = view.getSoftShadowOptions(); + uint8_t mipLevels = 1u; if (useMipmapping) { // Limit the lowest mipmap level to 256x256. diff --git a/filament/src/ShadowMapManager.h b/filament/src/ShadowMapManager.h index d9587bea2c..127ef79fc1 100644 --- a/filament/src/ShadowMapManager.h +++ b/filament/src/ShadowMapManager.h @@ -114,15 +114,6 @@ public: } private: - - // Atlas requirements, updated in ShadowMapManager::update(), - // consumed in ShadowMapManager::render() - struct TextureAtlasRequirements { - uint16_t size = 0; - uint8_t layers = 0; - uint8_t levels = 0; - } mTextureAtlasRequirements; - ShadowTechnique updateCascadeShadowMaps(FEngine& engine, FView& view, FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo) noexcept; @@ -196,6 +187,16 @@ private: size_t mSplitCount; }; + // Atlas requirements, updated in ShadowMapManager::update(), + // consumed in ShadowMapManager::render() + struct TextureAtlasRequirements { + uint16_t size = 0; + uint8_t layers = 0; + uint8_t levels = 0; + } mTextureAtlasRequirements; + + SoftShadowOptions mSoftShadowOptions; + CascadeSplits::Params mCascadeSplitParams; CascadeSplits mCascadeSplits; diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 9539a139cf..eff70753ae 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -674,7 +674,10 @@ void FView::prepareShadow(Handle texture) const noexcept { mPerViewUniforms.prepareShadowVSM(texture, mVsmShadowOptions); break; case filament::ShadowType::DPCF: - mPerViewUniforms.prepareShadowDPCF(texture); + mPerViewUniforms.prepareShadowDPCF(texture, mSoftShadowOptions); + break; + case filament::ShadowType::PCSS: + mPerViewUniforms.prepareShadowPCSS(texture, mSoftShadowOptions); break; } } @@ -1077,6 +1080,14 @@ View::VsmShadowOptions View::getVsmShadowOptions() const noexcept { return upcast(this)->getVsmShadowOptions(); } +void View::setSoftShadowOptions(SoftShadowOptions const& options) noexcept { + upcast(this)->setSoftShadowOptions(options); +} + +SoftShadowOptions View::getSoftShadowOptions() const noexcept { + return upcast(this)->getSoftShadowOptions(); +} + void View::setAmbientOcclusion(View::AmbientOcclusion ambientOcclusion) noexcept { upcast(this)->setAmbientOcclusion(ambientOcclusion); } diff --git a/filament/src/details/View.h b/filament/src/details/View.h index cbe13d48ef..68b0447573 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -188,6 +188,7 @@ public: bool hasFog() const noexcept { return mFogOptions.enabled && mFogOptions.density > 0.0f; } bool hasVSM() const noexcept { return mShadowType == ShadowType::VSM; } bool hasDPCF() const noexcept { return mShadowType == ShadowType::DPCF; } + bool hasPCSS() const noexcept { return mShadowType == ShadowType::PCSS; } bool hasPicking() const noexcept { return mActivePickingQueriesList != nullptr; } void renderShadowMaps(FrameGraph& fg, FEngine& engine, FEngine::DriverApi& driver, @@ -345,6 +346,16 @@ public: return mVsmShadowOptions; } + void setSoftShadowOptions(SoftShadowOptions options) noexcept { + options.penumbraScale = std::max(0.0f, options.penumbraScale); + options.penumbraRatioScale = std::max(1.0f, options.penumbraRatioScale); + mSoftShadowOptions = options; + } + + SoftShadowOptions getSoftShadowOptions() const noexcept { + return mSoftShadowOptions; + } + AmbientOcclusionOptions const& getAmbientOcclusionOptions() const noexcept { return mAmbientOcclusionOptions; } @@ -551,7 +562,8 @@ private: bool mHasPostProcessPass = true; AmbientOcclusionOptions mAmbientOcclusionOptions{}; ShadowType mShadowType = ShadowType::PCF; - VsmShadowOptions mVsmShadowOptions = {}; // FIXME: this should probably be per-light + VsmShadowOptions mVsmShadowOptions; // FIXME: this should probably be per-light + SoftShadowOptions mSoftShadowOptions; BloomOptions mBloomOptions; FogOptions mFogOptions; DepthOfFieldOptions mDepthOfFieldOptions; diff --git a/libs/filabridge/include/private/filament/UibStructs.h b/libs/filabridge/include/private/filament/UibStructs.h index ebb88b9db6..333e73865a 100644 --- a/libs/filabridge/include/private/filament/UibStructs.h +++ b/libs/filabridge/include/private/filament/UibStructs.h @@ -73,7 +73,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float shadowBulbRadiusLs; // light radius in light-space float shadowBias; // normal bias - float reserved; + float shadowPenumbraRatioScale; // For DPCF or PCSS, scale penumbra ratio for artistic use float oneOverFroxelDimensionY; math::float4 zParams; // froxel Z parameters diff --git a/libs/filamat/src/UibGenerator.cpp b/libs/filamat/src/UibGenerator.cpp index 194c3a26cb..6a16302987 100644 --- a/libs/filamat/src/UibGenerator.cpp +++ b/libs/filamat/src/UibGenerator.cpp @@ -59,7 +59,7 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { // shadow .add("shadowBulbRadiusLs", 1, UniformInterfaceBlock::Type::FLOAT) .add("shadowBias", 1, UniformInterfaceBlock::Type::FLOAT) - .add("reserved", 1, UniformInterfaceBlock::Type::FLOAT) + .add("shadowPenumbraRatioScale",1, UniformInterfaceBlock::Type::FLOAT) .add("oneOverFroxelDimensionY", 1, UniformInterfaceBlock::Type::FLOAT) // froxels .add("zParams", 1, UniformInterfaceBlock::Type::FLOAT4) diff --git a/libs/viewer/include/viewer/Settings.h b/libs/viewer/include/viewer/Settings.h index 617ae75ad8..c17cbedbb5 100644 --- a/libs/viewer/include/viewer/Settings.h +++ b/libs/viewer/include/viewer/Settings.h @@ -77,7 +77,7 @@ using LightManager = filament::LightManager; void applySettings(const ViewSettings& settings, View* dest); void applySettings(const MaterialSettings& settings, MaterialInstance* dest); void applySettings(const LightSettings& settings, IndirectLight* ibl, utils::Entity sunlight, - utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene); + utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene, View* view); void applySettings(const ViewerOptions& settings, Camera* camera, Skybox* skybox, Renderer* renderer); @@ -191,6 +191,7 @@ struct LightSettings { bool enableShadows = true; bool enableSunlight = true; LightManager::ShadowOptions shadowOptions; + SoftShadowOptions softShadowOptions; float sunlightIntensity = 100000.0f; math::float3 sunlightDirection = {0.6, -1.0, -0.8};; math::float3 sunlightColor = filament::Color::toLinear({ 0.98, 0.92, 0.89}); diff --git a/libs/viewer/src/AutomationEngine.cpp b/libs/viewer/src/AutomationEngine.cpp index 8d35281e8e..0b12801542 100644 --- a/libs/viewer/src/AutomationEngine.cpp +++ b/libs/viewer/src/AutomationEngine.cpp @@ -150,7 +150,7 @@ void AutomationEngine::applySettings(const char* json, size_t jsonLength, viewer::applySettings(mSettings->material, content.materials[i]); } viewer::applySettings(mSettings->lighting, content.indirectLight, content.sunlight, - content.assetLights, content.assetLightCount, content.lightManager, content.scene); + content.assetLights, content.assetLightCount, content.lightManager, content.scene, content.view); Camera* camera = &content.view->getCamera(); Skybox* skybox = content.scene->getSkybox(); viewer::applySettings(mSettings->viewer, camera, skybox, content.renderer); diff --git a/libs/viewer/src/Settings.cpp b/libs/viewer/src/Settings.cpp index cca24b64d4..5f4621aa68 100644 --- a/libs/viewer/src/Settings.cpp +++ b/libs/viewer/src/Settings.cpp @@ -213,6 +213,7 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ShadowTy 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 if (0 == compare(tokens[i], jsonChunk, "PCSS")) { *out = ShadowType::PCSS; } else { slog.w << "Invalid ShadowType: '" << STR(tokens[i], jsonChunk) << "'" << io::endl; } @@ -880,6 +881,30 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, return i; } +static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, + SoftShadowOptions* out) { + CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i++].size; + math::float3 splitsVector; + for (int j = 0; j < size; ++j) { + const jsmntok_t tok = tokens[i]; + CHECK_KEY(tok); + if (compare(tok, jsonChunk, "penumbraScale") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->penumbraScale); + } else if (compare(tok, jsonChunk, "penumbraRatioScale") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->penumbraRatioScale); + } else { + slog.w << "Invalid soft shadow options key: '" << STR(tok, jsonChunk) << "'" << io::endl; + i = parse(tokens, i + 1); + } + if (i < 0) { + slog.e << "Invalid soft shadow options value: '" << STR(tok, jsonChunk) << "'" << io::endl; + return i; + } + } + return i; +} + static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSettings* out) { CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i++].size; @@ -892,6 +917,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, LightSet i = parse(tokens, i + 1, jsonChunk, &out->enableSunlight); } else if (compare(tok, jsonChunk, "shadowOptions") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->shadowOptions); + } else if (compare(tok, jsonChunk, "softShadowOptions") == 0) { + i = parse(tokens, i + 1, jsonChunk, &out->softShadowOptions); } else if (compare(tok, jsonChunk, "sunlightIntensity") == 0) { i = parse(tokens, i + 1, jsonChunk, &out->sunlightIntensity); } else if (compare(tok, jsonChunk, "sunlightDirection") == 0) { @@ -1011,7 +1038,7 @@ void applySettings(const MaterialSettings& settings, MaterialInstance* dest) { } void applySettings(const LightSettings& settings, IndirectLight* ibl, utils::Entity sunlight, - utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene) { + utils::Entity* sceneLights, size_t sceneLightCount, LightManager* lm, Scene* scene, View* view) { auto light = lm->getInstance(sunlight); if (light) { if (settings.enableSunlight) { @@ -1036,6 +1063,7 @@ void applySettings(const LightSettings& settings, IndirectLight* ibl, utils::Ent } lm->setShadowOptions(light, settings.shadowOptions); } + view->setSoftShadowOptions(settings.softShadowOptions); } static LinearColor inverseTonemapSRGB(sRGBColor x) { @@ -1130,6 +1158,7 @@ static std::ostream& operator<<(std::ostream& out, ShadowType in) { case ShadowType::PCF: return out << "\"PCF\""; case ShadowType::VSM: return out << "\"VSM\""; case ShadowType::DPCF: return out << "\"DPCF\""; + case ShadowType::PCSS: return out << "\"PCSS\""; } return out << "\"INVALID\""; } @@ -1400,11 +1429,20 @@ static std::ostream& operator<<(std::ostream& out, const MaterialSettings& in) { return out << result; } + +static std::ostream& operator<<(std::ostream& out, const SoftShadowOptions& in) { + return out << "{\n" + << "\"penumbraScale\": " << in.penumbraScale << ",\n" + << "\"penumbraRatioScale\": " << in.penumbraRatioScale << "\n" + << "}"; +} + static std::ostream& operator<<(std::ostream& out, const LightSettings& in) { return out << "{\n" << "\"enableShadows\": " << to_string(in.enableShadows) << ",\n" << "\"enableSunlight\": " << to_string(in.enableSunlight) << ",\n" << "\"shadowOptions\": " << (in.shadowOptions) << ",\n" + << "\"softShadowOptions\": " << (in.softShadowOptions) << ",\n" << "\"sunlightIntensity\": " << (in.sunlightIntensity) << ",\n" << "\"sunlightDirection\": " << (in.sunlightDirection) << ",\n" << "\"sunlightColor\": " << (in.sunlightColor) << ",\n" diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 3dfa68a73a..a1eba1c066 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -768,7 +768,7 @@ void SimpleViewer::updateUserInterface() { int shadowType = (int)mSettings.view.shadowType; - ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0\0"); + ImGui::Combo("Shadow type", &shadowType, "PCF\0VSM\0DPCF\0PCSS\0\0"); mSettings.view.shadowType = (ShadowType)shadowType; if (mSettings.view.shadowType == ShadowType::VSM) { @@ -789,6 +789,9 @@ void SimpleViewer::updateUserInterface() { //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); + } else if (mSettings.view.shadowType == ShadowType::DPCF || mSettings.view.shadowType == ShadowType::PCSS) { + ImGui::SliderFloat("Penumbra scale", &light.softShadowOptions.penumbraScale, 0.0f, 100.0f); + ImGui::SliderFloat("Penumbra Ratio scale", &light.softShadowOptions.penumbraRatioScale, 1.0f, 100.0f); } int shadowCascades = light.shadowOptions.shadowCascades; @@ -918,6 +921,8 @@ void SimpleViewer::updateUserInterface() { // so we can now push them into the Filament View. applySettings(mSettings.view, mView); + mView->setSoftShadowOptions(mSettings.lighting.softShadowOptions); + if (light.enableSunlight) { mScene->addEntity(mSunlight); auto sun = lm.getInstance(mSunlight); diff --git a/samples/gltf_viewer.cpp b/samples/gltf_viewer.cpp index 4889e2ae69..60d886ebb5 100644 --- a/samples/gltf_viewer.cpp +++ b/samples/gltf_viewer.cpp @@ -608,6 +608,10 @@ int main(int argc, char** argv) { ImGui::SliderFloat("Ki", debug.getPropertyAddress("d.view.pid.ki"), 0, 10); ImGui::SliderFloat("Kd", debug.getPropertyAddress("d.view.pid.kd"), 0, 10); #endif + bool* lispsm; + if (debug.getPropertyAddress("d.shadowmap.lispsm", &lispsm)) { + ImGui::Checkbox("Enable LiSPSM", lispsm); + } } if (ImGui::BeginPopupModal("MessageBox", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index 63b1de2a3b..11f80a4558 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -6,6 +6,7 @@ #define SHADOW_SAMPLING_RUNTIME_PCF 0u #define SHADOW_SAMPLING_RUNTIME_VSM 1u #define SHADOW_SAMPLING_RUNTIME_DPCF 2u +#define SHADOW_SAMPLING_RUNTIME_PCSS 3u //------------------------------------------------------------------------------ // PCF Shadow Sampling @@ -53,13 +54,34 @@ float ShadowSample_PCF(const mediump sampler2DArray shadowMap, // 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) +// Poisson disk generated with 'poisson-disk-generator' tool from +// https://github.com/corporateshark/poisson-disk-generator by Sergey Kosarevsky +/*const*/ mediump vec2 poissonDisk[64] = vec2[]( // don't use 'const' b/c of OSX GL compiler bug + vec2(0.511749, 0.547686), vec2(0.58929, 0.257224), vec2(0.165018, 0.57663), vec2(0.407692, 0.742285), + vec2(0.707012, 0.646523), vec2(0.31463, 0.466825), vec2(0.801257, 0.485186), vec2(0.418136, 0.146517), + vec2(0.579889, 0.0368284), vec2(0.79801, 0.140114), vec2(-0.0413185, 0.371455), vec2(-0.0529108, 0.627352), + vec2(0.0821375, 0.882071), vec2(0.17308, 0.301207), vec2(-0.120452, 0.867216), vec2(0.371096, 0.916454), + vec2(-0.178381, 0.146101), vec2(-0.276489, 0.550525), vec2(0.12542, 0.126643), vec2(-0.296654, 0.286879), + vec2(0.261744, -0.00604975), vec2(-0.213417, 0.715776), vec2(0.425684, -0.153211), vec2(-0.480054, 0.321357), + vec2(-0.0717878, -0.0250567), vec2(-0.328775, -0.169666), vec2(-0.394923, 0.130802), vec2(-0.553681, -0.176777), + vec2(-0.722615, 0.120616), vec2(-0.693065, 0.309017), vec2(0.603193, 0.791471), vec2(-0.0754941, -0.297988), + vec2(0.109303, -0.156472), vec2(0.260605, -0.280111), vec2(0.129731, -0.487954), vec2(-0.537315, 0.520494), + vec2(-0.42758, 0.800607), vec2(0.77309, -0.0728102), vec2(0.908777, 0.328356), vec2(0.985341, 0.0759158), + vec2(0.947536, -0.11837), vec2(-0.103315, -0.610747), vec2(0.337171, -0.584), vec2(0.210919, -0.720055), + vec2(0.41894, -0.36769), vec2(-0.254228, -0.49368), vec2(-0.428562, -0.404037), vec2(-0.831732, -0.189615), + vec2(-0.922642, 0.0888026), vec2(-0.865914, 0.427795), vec2(0.706117, -0.311662), vec2(0.545465, -0.520942), + vec2(-0.695738, 0.664492), vec2(0.389421, -0.899007), vec2(0.48842, -0.708054), vec2(0.760298, -0.62735), + vec2(-0.390788, -0.707388), vec2(-0.591046, -0.686721), vec2(-0.769903, -0.413775), vec2(-0.604457, -0.502571), + vec2(-0.557234, 0.00451362), vec2(0.147572, -0.924353), vec2(-0.0662488, -0.892081), vec2(0.863832, -0.407206) ); +// tap count up can go up to 64 +const uint DPCF_SHADOW_TAP_COUNT = 12u; +// more samples lead to better "shape" of the hardened shadow +const uint PCSS_SHADOW_BLOCKER_SEARCH_TAP_COUNT = 16u; +// less samples lead to noisier shadows (can be mitigated with TAA) +const uint PCSS_SHADOW_FILTER_TAP_COUNT = 16u; + 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))); @@ -95,7 +117,7 @@ mat2 getRandomRotationMatrix(highp vec2 fragCoord) { return R; } -float getPenumbra(const bool DIRECTIONAL, const uint index, const highp float zLight) { +float getPenumbraLs(const bool DIRECTIONAL, const uint index, const highp float zLight) { float penumbra; // This conditional is resolved at compile time if (DIRECTIONAL) { @@ -126,7 +148,64 @@ float getPenumbraRatio(const bool DIRECTIONAL, const uint index, float nearOverFarMinusNear = shadowUniforms.shadows[index].nearOverFarMinusNear; penumbraRatio = (nearOverFarMinusNear + z_blocker) / (nearOverFarMinusNear + z_receiver) - 1.0; } - return penumbraRatio; + return penumbraRatio * frameUniforms.shadowPenumbraRatioScale; +} + +void blockerSearchAndFilter(out float occludedCount, out float z_occSum, + const mediump sampler2DArray map, const highp vec2 uv, const float z_rec, const uint layer, + const highp vec2 filterRadii, const mat2 R, const highp vec2 dz_duv, + const uint tapCount) { + occludedCount = 0.0; + z_occSum = 0.0; + for (uint i = 0u; i < tapCount; i++) { + highp vec2 duv = R * (poissonDisk[i] * filterRadii); + float z_occ = textureLod(map, vec3(uv + 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 - z_rec; // dz>0 when blocker is between receiver and light + float occluded = step(z_bias, dz); + occludedCount += occluded; + z_occSum += z_occ * occluded; + } +} + +float filterPCSS(const mediump sampler2DArray map, const highp vec2 size, + const highp vec2 uv, const float z_rec, const uint layer, + const highp vec2 filterRadii, const mat2 R, const highp vec2 dz_duv, + const uint tapCount) { + + float occludedCount = 0.0; + for (uint i = 0u; i < tapCount; i++) { + highp vec2 duv = R * (poissonDisk[i] * filterRadii); + + // sample the shadow map with a 2x2 PCF, this helps a lot in low resolution areas + vec4 d; + highp vec2 st = (uv + duv) * size - 0.5; + highp vec2 grad = fract(st); +#if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) + d = textureGather(map, vec3(uv + duv, layer), 0); // 01, 11, 10, 00 +#else + d[0] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(0, 1)).r; + d[1] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(1, 1)).r; + d[2] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(1, 0)).r; + d[3] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(0, 0)).r; +#endif + + // receiver plane depth bias + float z_bias = dot(dz_duv, duv); + vec4 dz = d - vec4(z_rec); // dz>0 when blocker is between receiver and light + vec4 pcf = step(z_bias, dz); + occludedCount += mix(mix(pcf.w, pcf.z, grad.x), mix(pcf.x, pcf.y, grad.x), grad.y); + } + return occludedCount * (1.0 / float(tapCount)); } /* @@ -143,31 +222,17 @@ float ShadowSample_DPCF(const bool DIRECTIONAL, // large kernel. highp vec2 dz_duv = computeReceiverPlaneDepthBias(position); - float penumbra = getPenumbra(DIRECTIONAL, index, zLight); + float penumbra = getPenumbraLs(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; - } + blockerSearchAndFilter(occludedCount, z_occSum, + map, position.xy, position.z, layer, texelSize * penumbra, R, dz_duv, + DPCF_SHADOW_TAP_COUNT); // early exit if there is no occluders at all, also avoids a divide-by-zero below. if (z_occSum == 0.0) { @@ -192,6 +257,43 @@ float ShadowSample_DPCF(const bool DIRECTIONAL, return 1.0 - percentageOccluded; } +float ShadowSample_PCSS(const bool DIRECTIONAL, + const mediump sampler2DArray map, const uint layer, const uint index, + const highp vec4 shadowPosition, const highp float zLight) { + highp vec2 size = vec2(textureSize(map, 0)); + highp vec2 texelSize = vec2(1.0) / size; + highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); + + // 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 = getPenumbraLs(DIRECTIONAL, index, zLight); + + // rotate the poisson disk randomly + mat2 R = getRandomRotationMatrix(gl_FragCoord.xy); + + float occludedCount = 0.0; + float z_occSum = 0.0; + + blockerSearchAndFilter(occludedCount, z_occSum, + map, position.xy, position.z, layer, texelSize * penumbra, R, dz_duv, + PCSS_SHADOW_BLOCKER_SEARCH_TAP_COUNT); + + // 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); + + float percentageOccluded = filterPCSS(map, size, position.xy, position.z, layer, + texelSize * (penumbra * penumbraRatio), + R, dz_duv, PCSS_SHADOW_FILTER_TAP_COUNT); + + return 1.0 - percentageOccluded; +} + //------------------------------------------------------------------------------ // Screen-space Contact Shadows //------------------------------------------------------------------------------ @@ -356,7 +458,7 @@ float shadow(const bool DIRECTIONAL, return ShadowSample_PCF(shadowMap, layer, shadowPosition); } -// VSM or DPCF sampling +// Shadow requiring a sampler2D sampler (VSM, DPCF and PCSS) float shadow(const bool DIRECTIONAL, const mediump sampler2DArray shadowMap, const uint layer, const uint index, const uint cascade) { @@ -384,6 +486,10 @@ float shadow(const bool DIRECTIONAL, return ShadowSample_DPCF(DIRECTIONAL, shadowMap, layer, index, shadowPosition, zLight); } + if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_PCSS) { + return ShadowSample_PCSS(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.