From 56285d6227e8ffd800818bec404245d318b57b72 Mon Sep 17 00:00:00 2001 From: Pixelflinger Date: Thu, 16 Apr 2020 21:40:39 -0700 Subject: [PATCH] improve contact shadows support - contact shadows are now supported for point and spot lights - and are now independent from regular shadows, that is they can be enabled without enabling regular shadows The only limitation currently is that the distance and step count for ALL contact shadow are taken from the directional light options. --- .../src/main/cpp/LightManager.cpp | 7 +++ .../google/android/filament/LightManager.java | 5 +++ filament/include/filament/LightManager.h | 43 ++++++++++++++++-- filament/src/Scene.cpp | 21 ++++++--- filament/src/ShadowMapManager.cpp | 33 ++++++++++---- filament/src/View.cpp | 2 +- filament/src/components/LightManager.cpp | 11 ++++- filament/src/components/LightManager.h | 8 ++++ filament/src/details/Scene.h | 9 ++-- filament/src/details/View.h | 4 +- .../include/private/filament/UibGenerator.h | 5 ++- libs/filabridge/src/UibGenerator.cpp | 2 +- libs/gltfio/include/gltfio/SimpleViewer.h | 10 +++-- shaders/src/common_lighting.fs | 1 + shaders/src/light_directional.fs | 5 +-- shaders/src/light_punctual.fs | 44 +++++++++++++------ shaders/src/shading_unlit.fs | 4 +- 17 files changed, 162 insertions(+), 52 deletions(-) diff --git a/android/filament-android/src/main/cpp/LightManager.cpp b/android/filament-android/src/main/cpp/LightManager.cpp index 3c9d9f99e2..0e2bba147d 100644 --- a/android/filament-android/src/main/cpp/LightManager.cpp +++ b/android/filament-android/src/main/cpp/LightManager.cpp @@ -23,6 +23,13 @@ using namespace filament; using namespace utils; +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_LightManager_nGetComponentCount(JNIEnv*, jclass, + jlong nativeLightManager) { + LightManager *lm = (LightManager *) nativeLightManager; + return lm->getComponentCount(); +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_LightManager_nHasComponent(JNIEnv*, jclass, jlong nativeLightManager, jint entity) { 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 d7a2d08c26..99e639d96c 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 @@ -27,6 +27,10 @@ public class LightManager { mNativeObject = nativeLightManager; } + public int getComponentCount() { + return nGetComponentCount(mNativeObject); + } + public boolean hasComponent(@Entity int entity) { return nHasComponent(mNativeObject, entity); } @@ -278,6 +282,7 @@ public class LightManager { return mNativeObject; } + private static native int nGetComponentCount(long nativeLightManager); private static native boolean nHasComponent(long nativeLightManager, int entity); private static native int nGetInstance(long nativeLightManager, int entity); private static native void nDestroy(long nativeLightManager, int entity); diff --git a/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index a4ee84e48f..1e1421d660 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -144,6 +145,21 @@ class UTILS_PUBLIC LightManager : public FilamentAPI { public: using Instance = utils::EntityInstance; + /** + * Returns the number of component in the LightManager, not that component are not + * guaranteed to be active. Use the EntityManager::isAlive() before use if needed. + * + * @return number of component in the LightManager + */ + size_t getComponentCount() const noexcept; + + /** + * Returns the list of Entity for all components. Use getComponentCount() to know the size + * of the list. + * @return a pointer to Entity + */ + utils::Entity const* getEntities() const noexcept; + /** * Returns whether a particular Entity is associated with a component of this LightManager * @param e An Entity. @@ -236,22 +252,28 @@ public: /** * Whether screen-space contact shadows are used. This applies regardless of whether a - * Renderable is a shadow caster. This setting is currently only used for directional - * lights, ignored otherwise. + * Renderable is a shadow caster. * Screen-space contact shadows are typically useful in large scenes. * (off by default) */ bool screenSpaceContactShadows = false; /** - * Number of ray-marching steps for screen-space contact shadows. - * (8 by default) + * Number of ray-marching steps for screen-space contact shadows (8 by default). + * + * CAUTION: this parameter is ignored for all lights except the directional/sun light, + * all other lights use the same value set for the directional/sun light. + * */ uint8_t stepCount = 8; /** * Maximum shadow-occluder distance for screen-space contact shadows (world units). * (30 cm by default) + * + * CAUTION: this parameter is ignored for all lights except the directional/sun light, + * all other lights use the same value set for the directional/sun light. + * */ float maxShadowDistance = 0.3; }; @@ -721,6 +743,19 @@ public: * @param i Instance of the component obtained from getInstance(). */ bool isShadowCaster(Instance i) const noexcept; + + /** + * Helper to process all components with a given function + * @tparam F a void(Entity entity, Instance instance) + * @param func a function of type F + */ + template + void forEachComponent(F func) noexcept { + utils::Entity const* const pEntity = getEntities(); + for (size_t i = 0, c = getComponentCount(); i < c; i++) { + func(pEntity[i], Instance(i)); + } + } }; } // namespace filament diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 17e96707bd..fe5c4682fb 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -403,14 +403,23 @@ void FScene::setSkybox(FSkybox* skybox) noexcept { } bool FScene::hasContactShadows() const noexcept { - bool hasContactShadows = mHasContactShadows; + // find out if at least one light has contact-shadow enabled + // TODO: cache the the result of this Loop in the LightManager + bool hasContactShadows = false; auto& lcm = mEngine.getLightManager(); - FLightManager::Instance directionalLight = mLightData.elementAt(0); - if (directionalLight.isValid()) { - auto const& shadowOoptions = lcm.getShadowOptions(directionalLight); - hasContactShadows = hasContactShadows && shadowOoptions.screenSpaceContactShadows; + auto pFirst = mLightData.begin(); + auto pLast = mLightData.end(); + while (pFirst != pLast && !hasContactShadows) { + if (pFirst->isValid()) { + auto const& shadowOptions = lcm.getShadowOptions(*pFirst); + hasContactShadows = shadowOptions.screenSpaceContactShadows; + } + ++pFirst; } - return hasContactShadows; + + // at least some renderables in the scene must have contact-shadows enabled + // TODO: we should refine this with only the visible ones + return hasContactShadows && mHasContactShadows; } } // namespace details diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index 59dcec40ac..11f1304cf9 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -172,21 +172,23 @@ bool ShadowMapManager::update(FEngine& engine, FView& view, UniformBuffer& perVi hasShadowing = true; directionalShadows |= 0x1u; } + } - // screen-space contact shadows - LightManager::ShadowOptions const& options = lcm.getShadowOptions(light); - if (options.screenSpaceContactShadows) { - hasShadowing = true; - directionalShadows |= std::min(uint8_t(255u), options.stepCount) << 8u; - screenSpaceShadowDistance = options.maxShadowDistance; - } + // screen-space contact shadows for the directional light + FLightManager::Instance directionalLight = lightData.elementAt(0); + LightManager::ShadowOptions const& options = lcm.getShadowOptions(directionalLight); + screenSpaceShadowDistance = options.maxShadowDistance; + directionalShadows |= std::min(uint8_t(255u), options.stepCount) << 8u; + if (options.screenSpaceContactShadows) { + hasShadowing = true; + directionalShadows |= 0x2u; } perViewUb.setUniform(offsetof(PerViewUib, directionalShadows), directionalShadows); perViewUb.setUniform(offsetof(PerViewUib, ssContactShadowDistance), screenSpaceShadowDistance); + // shadow-map shadows for point/spot lights FScene::ShadowInfo* const shadowInfo = lightData.data(); - for (size_t i = 0, c = mSpotShadowMaps.size(); i < c; i++) { auto& entry = mSpotShadowMaps[i]; @@ -202,6 +204,8 @@ bool ShadowMapManager::update(FEngine& engine, FView& view, UniformBuffer& perVi .shadowDimension = textureDimension - 2 }; shadowMap.update(lightData, l, scene, viewingCameraInfo, visibleLayers, layout); + + FLightManager::Instance light = lightData.elementAt(l); if (shadowMap.hasVisibleShadows()) { entry.setHasVisibleShadows(true); @@ -219,7 +223,6 @@ bool ShadowMapManager::update(FEngine& engine, FView& view, UniformBuffer& perVi shadowInfo[l].index = i; shadowInfo[l].layer = mSpotShadowMaps[i].getLayout().layer; - FLightManager::Instance light = lightData.elementAt(l); const float3 dir = lightData.elementAt(l); const float texelSizeWorldSpace = shadowMap.getTexelSizeWorldSpace(); const float normalBias = lcm.getShadowNormalBias(light); @@ -230,6 +233,18 @@ bool ShadowMapManager::update(FEngine& engine, FView& view, UniformBuffer& perVi } } + // screen-space contact shadows for point/spot lights + auto pInstance = lightData.data(); + for (size_t i = 0, c = lightData.size(); i < c; i++) { + // screen-space contact shadows + LightManager::ShadowOptions const& shadowOptions = lcm.getShadowOptions(pInstance[i]); + if (shadowOptions.screenSpaceContactShadows) { + hasShadowing = true; + shadowInfo[i].contactShadows = true; + // TODO: distance/steps are always taken from the directional light currently + } + } + return hasShadowing; } diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 7a39e50caa..2f7fd48e0e 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -574,7 +574,7 @@ void FView::computeVisibilityMasks( uint8_t visibleLayers, uint8_t const* UTILS_RESTRICT layers, FRenderableManager::Visibility const* UTILS_RESTRICT visibility, - uint8_t* UTILS_RESTRICT visibleMask, size_t count) const { + uint8_t* UTILS_RESTRICT visibleMask, size_t count) { // __restrict__ seems to only be taken into account as function parameters. This is very // important here, otherwise, this loop doesn't get vectorized. // This is vectorized 16x. diff --git a/filament/src/components/LightManager.cpp b/filament/src/components/LightManager.cpp index 3dc21b5509..3e309cbe8d 100644 --- a/filament/src/components/LightManager.cpp +++ b/filament/src/components/LightManager.cpp @@ -347,12 +347,19 @@ void FLightManager::setShadowCaster(Instance i, bool shadowCaster) noexcept { using namespace details; +size_t LightManager::getComponentCount() const noexcept { + return upcast(this)->getComponentCount(); +} + +utils::Entity const* LightManager::getEntities() const noexcept { + return upcast(this)->getEntities(); +} + bool LightManager::hasComponent(Entity e) const noexcept { return upcast(this)->hasComponent(e); } -LightManager::Instance -LightManager::getInstance(Entity e) const noexcept { +LightManager::Instance LightManager::getInstance(Entity e) const noexcept { return upcast(this)->getInstance(e); } diff --git a/filament/src/components/LightManager.h b/filament/src/components/LightManager.h index 41f421d759..3d30aa4680 100644 --- a/filament/src/components/LightManager.h +++ b/filament/src/components/LightManager.h @@ -45,6 +45,14 @@ public: void terminate() noexcept; + size_t getComponentCount() const noexcept { + return mManager.getComponentCount(); + } + + utils::Entity const* getEntities() const noexcept { + return mManager.getEntities(); + } + bool hasComponent(utils::Entity e) const noexcept { return mManager.hasComponent(e); } diff --git a/filament/src/details/Scene.h b/filament/src/details/Scene.h index d036582158..7407cf8397 100644 --- a/filament/src/details/Scene.h +++ b/filament/src/details/Scene.h @@ -155,20 +155,23 @@ public: // They're packed into 32 bits and stored in the Lights uniform buffer. // They're unpacked in the fragment shader and used to calculate punctual shadows. bool castsShadows = false; // whether this light casts shadows + bool contactShadows = false; // whether this light casts contact shadows uint8_t index = 0; // an index into the arrays in the Shadows uniform buffer uint8_t layer = 0; // which layer of the shadow texture array to sample from // -- LSB ------------- // castsShadows : 1 + // contactShadows : 1 // index : 4 // layer : 4 // -- MSB ------------- uint32_t pack() const { assert(index < 16); assert(layer < 16); - return uint8_t(castsShadows) << 0u | - index << 1u | - layer << 5u; + return uint8_t(castsShadows) << 0u | + uint8_t(contactShadows) << 1u | + index << 2u | + layer << 6u; } }; diff --git a/filament/src/details/View.h b/filament/src/details/View.h index e12c5e8141..24f1ec858d 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -328,10 +328,10 @@ private: FLightManager const& lcm, utils::JobSystem& js, Frustum const& frustum, FScene::LightSoa& lightData) noexcept; - void computeVisibilityMasks( + static void computeVisibilityMasks( uint8_t visibleLayers, uint8_t const* layers, FRenderableManager::Visibility const* visibility, uint8_t* visibleMask, - size_t count) const; + size_t count) ; void bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noexcept { driver.bindUniformBuffer(BindingPoints::PER_VIEW, mPerViewUbh); diff --git a/libs/filabridge/include/private/filament/UibGenerator.h b/libs/filabridge/include/private/filament/UibGenerator.h index ceea90d834..4d0ad0ac7a 100644 --- a/libs/filabridge/include/private/filament/UibGenerator.h +++ b/libs/filabridge/include/private/filament/UibGenerator.h @@ -90,6 +90,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float refractionLodOffset; // bit 0: directional (sun) shadow enabled + // bit 1: directional (sun) screen-space contact shadow enabled // bit 8-15: screen-space contact shadows ray casting steps uint32_t directionalShadows; @@ -119,8 +120,8 @@ struct alignas(256) PerRenderableUib { filament::math::mat3f worldFromModelNormalMatrix; // this gets expanded to 48 bytes during the copy to the UBO alignas(16) filament::math::float4 morphWeights; // TODO: we can pack all the boolean bellow - uint32_t skinningEnabled; // 0=disabled, 1=enabled, ignored unless variant & SKINNING_OR_MORPHING - uint32_t morphingEnabled; // 0=disabled, 1=enabled, ignored unless variant & SKINNING_OR_MORPHING + int32_t skinningEnabled; // 0=disabled, 1=enabled, ignored unless variant & SKINNING_OR_MORPHING + int32_t morphingEnabled; // 0=disabled, 1=enabled, ignored unless variant & SKINNING_OR_MORPHING uint32_t screenSpaceContactShadows; // 0=disabled, 1=enabled, ignored unless variant & SKINNING_OR_MORPHING float padding0; }; diff --git a/libs/filabridge/src/UibGenerator.cpp b/libs/filabridge/src/UibGenerator.cpp index cd4d5bd799..411d15d6ae 100644 --- a/libs/filabridge/src/UibGenerator.cpp +++ b/libs/filabridge/src/UibGenerator.cpp @@ -108,7 +108,7 @@ UniformInterfaceBlock const& UibGenerator::getPerRenderableUib() noexcept { .add("morphWeights", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH) .add("skinningEnabled", 1, UniformInterfaceBlock::Type::INT) .add("morphingEnabled", 1, UniformInterfaceBlock::Type::INT) - .add("screenSpaceContactShadows", 1, UniformInterfaceBlock::Type::INT) + .add("screenSpaceContactShadows", 1, UniformInterfaceBlock::Type::UINT) .add("padding0", 1, UniformInterfaceBlock::Type::FLOAT) .build(); return uib; diff --git a/libs/gltfio/include/gltfio/SimpleViewer.h b/libs/gltfio/include/gltfio/SimpleViewer.h index 0401fd682c..208549a8bd 100644 --- a/libs/gltfio/include/gltfio/SimpleViewer.h +++ b/libs/gltfio/include/gltfio/SimpleViewer.h @@ -517,13 +517,17 @@ void SimpleViewer::updateUserInterface() { lm.setDirection(sun, normalize(mSunlightDirection)); lm.setColor(sun, mSunlightColor); lm.setShadowCaster(sun, mEnableShadows); - auto options = lm.getShadowOptions(sun); - options.screenSpaceContactShadows = mEnableContactShadows; - lm.setShadowOptions(sun, options); } else { mScene->remove(mSunlight); } + lm.forEachComponent([this, &lm](utils::Entity e, LightManager::Instance ci) { + auto options = lm.getShadowOptions(ci); + options.screenSpaceContactShadows = mEnableContactShadows; + lm.setShadowOptions(ci, options); + lm.setShadowCaster(ci, mEnableShadows); + }); + if (mAsset != nullptr) { if (ImGui::CollapsingHeader("Model", headerFlags)) { if (mAnimator->getAnimationCount() > 0) { diff --git a/shaders/src/common_lighting.fs b/shaders/src/common_lighting.fs index 7c17d15dcd..4338114c66 100644 --- a/shaders/src/common_lighting.fs +++ b/shaders/src/common_lighting.fs @@ -4,6 +4,7 @@ struct Light { float attenuation; float NoL; bool castsShadows; + bool contactShadows; uint shadowIndex; uint shadowLayer; }; diff --git a/shaders/src/light_directional.fs b/shaders/src/light_directional.fs index c6ce1b493a..e2e03faa4d 100644 --- a/shaders/src/light_directional.fs +++ b/shaders/src/light_directional.fs @@ -43,9 +43,8 @@ void evaluateDirectionalLight(const MaterialInputs material, if ((frameUniforms.directionalShadows & 1u) != 0u) { visibility = shadow(light_shadowMap, 0u, getLightSpacePosition()); } - - if (visibility > 0.0 && frameUniforms.ssContactShadowDistance != 0.0) { - if (objectUniforms.screenSpaceContactShadows != 0) { + if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) { + if (objectUniforms.screenSpaceContactShadows != 0u) { ssContactShadowOcclusion = screenSpaceContactShadow(light.l); } } diff --git a/shaders/src/light_punctual.fs b/shaders/src/light_punctual.fs index 54ccb57814..71197d7f8d 100644 --- a/shaders/src/light_punctual.fs +++ b/shaders/src/light_punctual.fs @@ -147,8 +147,9 @@ Light getSpotLight(uint index) { uint shadowBits = floatBitsToUint(scaleOffsetShadow.z); light.castsShadows = bool(shadowBits & 0x1u); - light.shadowIndex = ((shadowBits & 0x01Eu) >> 1u); - light.shadowLayer = ((shadowBits & 0x1E0u) >> 5u); + light.contactShadows = bool((shadowBits >> 1u) & 0x1u); + light.shadowIndex = (shadowBits >> 2u) & 0xFu; + light.shadowLayer = (shadowBits >> 6u) & 0xFu; return light; } @@ -199,13 +200,22 @@ void evaluatePunctualLights(const PixelParams pixel, inout vec3 color) { // Iterate point lights for ( ; index < end; index++) { Light light = getPointLight(index); -#if defined(MATERIAL_CAN_SKIP_LIGHTING) - if (light.NoL > 0.0) { - color.rgb += surfaceShading(pixel, light, 1.0); + float visibility = 1.0; +#if defined(HAS_SHADOWING) + if (light.NoL > 0.0){ + if (light.contactShadows) { + if (objectUniforms.screenSpaceContactShadows != 0u) { + visibility -= screenSpaceContactShadow(light.l); + } + } } -#else - color.rgb += surfaceShading(pixel, light, 1.0); #endif +#if defined(MATERIAL_CAN_SKIP_LIGHTING) + if (light.NoL <= 0.0) { + continue; + } +#endif + color.rgb += surfaceShading(pixel, light, visibility); } end += froxel.spotCount; @@ -215,17 +225,23 @@ void evaluatePunctualLights(const PixelParams pixel, inout vec3 color) { Light light = getSpotLight(index); float visibility = 1.0; #if defined(HAS_SHADOWING) - if (light.castsShadows && light.NoL > 0.0) { - visibility = shadow(light_shadowMap, light.shadowLayer, - getSpotLightSpacePosition(light.shadowIndex)); + if (light.NoL > 0.0){ + if (light.castsShadows) { + visibility = shadow(light_shadowMap, light.shadowLayer, + getSpotLightSpacePosition(light.shadowIndex)); + } + if (light.contactShadows && visibility > 0.0) { + if (objectUniforms.screenSpaceContactShadows != 0u) { + visibility *= 1.0 - screenSpaceContactShadow(light.l); + } + } } #endif #if defined(MATERIAL_CAN_SKIP_LIGHTING) - if (light.NoL > 0.0) { - color.rgb += surfaceShading(pixel, light, visibility); + if (light.NoL <= 0.0) { + continue; } -#else - color.rgb += surfaceShading(pixel, light, visibility); #endif + color.rgb += surfaceShading(pixel, light, visibility); } } diff --git a/shaders/src/shading_unlit.fs b/shaders/src/shading_unlit.fs index fcaa677f9e..28a77a675c 100644 --- a/shaders/src/shading_unlit.fs +++ b/shaders/src/shading_unlit.fs @@ -40,8 +40,8 @@ vec4 evaluateMaterial(const MaterialInputs material) { if ((frameUniforms.directionalShadows & 1u) != 0u) { visibility = shadow(light_shadowMap, 0u, getLightSpacePosition()); } - if (visibility > 0.0 && frameUniforms.ssContactShadowDistance != 0.0) { - if (objectUniforms.screenSpaceContactShadows != 0) { + if ((frameUniforms.directionalShadows & 0x2u) != 0u && visibility > 0.0) { + if (objectUniforms.screenSpaceContactShadows != 0u) { visibility *= (1.0 - screenSpaceContactShadow(frameUniforms.lightDirection)); } }