From d54ceff7a34d4eb0bc5156c13385842013783f3d Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Mon, 16 Mar 2020 22:39:24 -0700 Subject: [PATCH] Add support for screen-space contact shadows (#2245) * Add support for screen-space contact shadows This CL adds support and always enables it. toggles and setting in the next CL (same PR). * Plumb settings for screen-space contact shadows screen-space contact shadows is handled like a shadow option, parameters (including on/off) are set in the LightManager, using the existing ShadowOptions API. Additionally there is a per-renderable toggle. Both toggles are off by default. * Allow contact shadows when shadowing is auto-disabled Shadowing can be auto-disabled when for instance there are no shadow casters in the scene. We still allow contact shadows in that case. This would allow for instance, to make the vegetation on a terrain not shadow-casting, and still get some shadowing there by using contact shadows instead. * apply micro-shadowing after contact shadows also, don't compute contact shadows when we know we're fully shadowed. --- RELEASE_NOTES.md | 1 + .../src/main/cpp/LightManager.cpp | 8 +- .../src/main/cpp/RenderableManager.cpp | 14 ++++ .../google/android/filament/LightManager.java | 8 +- .../android/filament/RenderableManager.java | 23 ++++++ filament/include/filament/LightManager.h | 21 ++++++ filament/include/filament/RenderableManager.h | 14 ++++ filament/src/Renderer.cpp | 10 ++- filament/src/Scene.cpp | 6 +- filament/src/ShadowMapManager.cpp | 25 +++++-- filament/src/View.cpp | 6 ++ filament/src/components/RenderableManager.cpp | 13 +++- filament/src/components/RenderableManager.h | 23 ++++-- filament/src/details/View.h | 3 +- filament/src/fg/FrameGraph.cpp | 2 + .../include/private/filament/SibGenerator.h | 3 +- .../include/private/filament/UibGenerator.h | 11 ++- libs/filabridge/src/SibGenerator.cpp | 3 +- libs/filabridge/src/UibGenerator.cpp | 7 +- libs/filamat/src/shaders/ShaderGenerator.cpp | 2 + libs/gltfio/include/gltfio/SimpleViewer.h | 11 +++ samples/material_sandbox.cpp | 10 +++ samples/material_sandbox.h | 3 + shaders/src/light_directional.fs | 19 ++++- shaders/src/shadowing.fs | 75 +++++++++++++++++++ 25 files changed, 288 insertions(+), 33 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 790a51fc17..d7eafe573b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -23,6 +23,7 @@ A new header is inserted each time a *tag* is created. - WebGL: Improved TypeScript annotations. - WebGL: Simplified callback API for glTF. (⚠ API Change) - gltfio: Removed deprecated "Bindings" API. (⚠ API Change) +- Add support for screen-space contact shadows ## v1.4.5 diff --git a/android/filament-android/src/main/cpp/LightManager.cpp b/android/filament-android/src/main/cpp/LightManager.cpp index f5328a4c0a..3c9d9f99e2 100644 --- a/android/filament-android/src/main/cpp/LightManager.cpp +++ b/android/filament-android/src/main/cpp/LightManager.cpp @@ -66,7 +66,8 @@ Java_com_google_android_filament_LightManager_nBuilderCastShadows(JNIEnv*, jclas extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv*, jclass, jlong nativeBuilder, jint mapSize, jfloat constantBias, jfloat normalBias, jfloat shadowFar, - jfloat shadowNearHint, jfloat shadowFarHint, jboolean stable) { + jfloat shadowNearHint, jfloat shadowFarHint, jboolean stable, + jboolean screenSpaceContactShadows, jint stepCount, jfloat maxShadowDistance) { LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder; builder->shadowOptions( LightManager::ShadowOptions{.mapSize = (uint32_t)mapSize, @@ -75,7 +76,10 @@ Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv*, jcl .shadowFar = shadowFar, .shadowNearHint = shadowNearHint, .shadowFarHint = shadowFarHint, - .stable = (bool)stable}); + .stable = (bool)stable, + .screenSpaceContactShadows = (bool)screenSpaceContactShadows, + .stepCount = uint8_t(stepCount), + .maxShadowDistance = maxShadowDistance}); } extern "C" JNIEXPORT void JNICALL diff --git a/android/filament-android/src/main/cpp/RenderableManager.cpp b/android/filament-android/src/main/cpp/RenderableManager.cpp index 3512e69e53..7cba4c1692 100644 --- a/android/filament-android/src/main/cpp/RenderableManager.cpp +++ b/android/filament-android/src/main/cpp/RenderableManager.cpp @@ -164,6 +164,13 @@ Java_com_google_android_filament_RenderableManager_nBuilderReceiveShadows(JNIEnv builder->receiveShadows(enabled); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nBuilderScreenSpaceContactShadows(JNIEnv*, jclass, + jlong nativeBuilder, jboolean enabled) { + RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder; + builder->screenSpaceContactShadows(enabled); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_RenderableManager_nBuilderSkinning(JNIEnv*, jclass, jlong nativeBuilder, jint boneCount) { @@ -275,6 +282,13 @@ Java_com_google_android_filament_RenderableManager_nSetReceiveShadows(JNIEnv*, j rm->setReceiveShadows((RenderableManager::Instance) i, enabled); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_RenderableManager_nSetScreenSpaceContactShadows(JNIEnv*, jclass, + jlong nativeRenderableManager, jint i, jboolean enabled) { + RenderableManager *rm = (RenderableManager *) nativeRenderableManager; + rm->setScreenSpaceContactShadows((RenderableManager::Instance) i, enabled); +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_RenderableManager_nIsShadowCaster(JNIEnv*, jclass, jlong nativeRenderableManager, jint i) { 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 837ff71e80..d7a2d08c26 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 @@ -56,6 +56,9 @@ public class LightManager { public float shadowNearHint = 1.0f; public float shadowFarHint = 100.0f; public boolean stable = true; + public boolean screenSpaceContactShadows = false; + public int stepCount = 8; + public float maxShadowDistance = 0.3f; } public static final float EFFICIENCY_INCANDESCENT = 0.0220f; @@ -83,7 +86,8 @@ public class LightManager { public Builder shadowOptions(@NonNull ShadowOptions options) { nBuilderShadowOptions(mNativeBuilder, options.mapSize, options.constantBias, options.normalBias, options.shadowFar, - options.shadowNearHint, options.shadowFarHint, options.stable); + options.shadowNearHint, options.shadowFarHint, options.stable, + options.screenSpaceContactShadows, options.stepCount, options.maxShadowDistance); return this; } @@ -282,7 +286,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, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable); + private static native void nBuilderShadowOptions(long nativeBuilder, int mapSize, float constantBias, float normalBias, float shadowFar, float shadowNearHint, float shadowFarhint, boolean stable, boolean screenSpaceContactShadows, int stepCount, float maxShadowDistance); 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/RenderableManager.java b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java index e12e41bbe2..06d20a71d2 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java @@ -284,6 +284,17 @@ public class RenderableManager { return this; } + /** + * Controls if this renderable uses screen-space contact shadows. This is more + * expensive but can improve the quality of shadows, especially in large scenes. + * (off by default). + */ + @NonNull + public Builder screenSpaceContactShadows(boolean enabled) { + nBuilderScreenSpaceContactShadows(mNativeBuilder, enabled); + return this; + } + @NonNull public Builder skinning(@IntRange(from = 0, to = 255) int boneCount) { nBuilderSkinning(mNativeBuilder, boneCount); @@ -460,6 +471,16 @@ public class RenderableManager { nSetReceiveShadows(mNativeObject, i, enabled); } + /** + * Changes whether or not the renderable can use screen-space contact shadows. + + * + * @see Builder#screenSpaceContactShadows + */ + public void setScreenSpaceContactShadows(@EntityInstance int i, boolean enabled) { + nSetScreenSpaceContactShadows(mNativeObject, i, enabled); + } + /** * Checks if the renderable can cast shadows. * @@ -614,6 +635,7 @@ public class RenderableManager { private static native void nBuilderCulling(long nativeBuilder, boolean enabled); private static native void nBuilderCastShadows(long nativeBuilder, boolean enabled); private static native void nBuilderReceiveShadows(long nativeBuilder, boolean enabled); + private static native void nBuilderScreenSpaceContactShadows(long nativeBuilder, boolean enabled); private static native void nBuilderSkinning(long nativeBuilder, int boneCount); private static native int nBuilderSkinningBones(long nativeBuilder, int boneCount, Buffer bones, int remaining); private static native void nBuilderMorphing(long nativeBuilder, boolean enabled); @@ -626,6 +648,7 @@ public class RenderableManager { private static native void nSetPriority(long nativeRenderableManager, int i, int priority); private static native void nSetCastShadows(long nativeRenderableManager, int i, boolean enabled); private static native void nSetReceiveShadows(long nativeRenderableManager, int i, boolean enabled); + private static native void nSetScreenSpaceContactShadows(long nativeRenderableManager, int i, boolean enabled); private static native boolean nIsShadowCaster(long nativeRenderableManager, int i); private static native boolean nIsShadowReceiver(long nativeRenderableManager, int i); private static native void nGetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float[] center, float[] halfExtent); diff --git a/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index 47eba14d96..a3f4aa6af8 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -233,6 +233,27 @@ public: * Setting this value correctly is essential for LISPSM shadow-maps. */ float polygonOffsetSlope = 2.0f; + + /** + * 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. + * 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) + */ + uint8_t stepCount = 8; + + /** + * Maximum shadow-occluder distance for screen-space contact shadows (world units). + * (30 cm by default) + */ + float maxShadowDistance = 0.3; }; //! Use Builder to construct a Light object instance diff --git a/filament/include/filament/RenderableManager.h b/filament/include/filament/RenderableManager.h index 3c8bf5b397..e9b6cc01fd 100644 --- a/filament/include/filament/RenderableManager.h +++ b/filament/include/filament/RenderableManager.h @@ -228,6 +228,13 @@ public: */ Builder& receiveShadows(bool enable) noexcept; + /** + * Controls if this renderable uses screen-space contact shadows. This is more + * expensive but can improve the quality of shadows, especially in large scenes. + * (off by default). + */ + Builder& screenSpaceContactShadows(bool enable) noexcept; + /** * Enables GPU vertex skinning for up to 255 bones, 0 by default. * @@ -343,6 +350,13 @@ public: */ void setReceiveShadows(Instance instance, bool enable) noexcept; + /** + * Changes whether or not the renderable can use screen-space contact shadows. + * + * \see Builder::screenSpaceContactShadows() + */ + void setScreenSpaceContactShadows(Instance instance, bool enable) noexcept; + /** * Checks if the renderable can cast shadows. * diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index cdc555c064..0c4071b859 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -557,6 +557,7 @@ FrameGraphId FRenderer::colorPass(FrameGraph& fg, const char* FrameGraphId depth; FrameGraphId ssao; FrameGraphId ssr; + FrameGraphId structure; FrameGraphRenderTargetHandle rt{}; }; @@ -572,7 +573,11 @@ FrameGraphId FRenderer::colorPass(FrameGraph& fg, const char* data.ssr = blackboard.get("ssr"); data.ssao = blackboard.get("ssao"); data.color = blackboard.get("color"); - data.depth = blackboard.get("depth"); + data.structure = blackboard.get("structure"); + + if (data.structure.isValid()) { + data.structure = builder.sample(data.structure); + } if (data.ssr.isValid()) { data.ssr = builder.sample(data.ssr); @@ -619,6 +624,9 @@ FrameGraphId FRenderer::colorPass(FrameGraph& fg, const char* // set samplers and uniforms PostProcessManager& ppm = getEngine().getPostProcessManager(); view.prepareSSAO(data.ssao.isValid() ? resources.getTexture(data.ssao) : ppm.getOneTexture()); + if (data.structure.isValid()) { + view.prepareStructure(resources.getTexture(data.structure)); + } if (data.ssr.isValid()) { view.prepareSSR(resources.getTexture(data.ssr), config.refractionLodOffset); } diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 50dfc44681..30af83ec96 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -184,8 +184,7 @@ void FScene::updateUBOs(utils::Range visibleRenderables, backend::Hand const size_t offset = i * sizeof(PerRenderableUib); UniformBuffer::setUniform(buffer, - offset + offsetof(PerRenderableUib, worldFromModelMatrix), - model); + offset + offsetof(PerRenderableUib, worldFromModelMatrix), model); // Using mat3f::getTransformForNormals handles non-uniform scaling, but DOESN'T guarantee that // the transformed normals will have unit-length, therefore they need to be normalized @@ -213,6 +212,9 @@ void FScene::updateUBOs(utils::Range visibleRenderables, backend::Hand UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, morphingEnabled), uint32_t(sceneData.elementAt(i).morphing)); + UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, screenSpaceContactShadows), + uint32_t(sceneData.elementAt(i).screenSpaceContactShadows)); + UniformBuffer::setUniform(buffer, offset + offsetof(PerRenderableUib, morphWeights), sceneData.elementAt(i)); } diff --git a/filament/src/ShadowMapManager.cpp b/filament/src/ShadowMapManager.cpp index fe153c26c5..59dcec40ac 100644 --- a/filament/src/ShadowMapManager.cpp +++ b/filament/src/ShadowMapManager.cpp @@ -132,6 +132,9 @@ bool ShadowMapManager::update(FEngine& engine, FView& view, UniformBuffer& perVi uint8_t visibleLayers = view.getVisibleLayers(); const uint16_t textureSize = mTextureState.size; + uint32_t directionalShadows = 0; + float screenSpaceShadowDistance = 0.0; + auto& lcm = engine.getLightManager(); if (mDirectionalShadowMap) { // Compute the frustum for the directional light. @@ -139,6 +142,8 @@ bool ShadowMapManager::update(FEngine& engine, FView& view, UniformBuffer& perVi size_t l = mDirectionalShadowMap.getLightIndex(); assert(l == 0); + FLightManager::Instance light = lightData.elementAt(l); + const size_t textureDimension = mDirectionalShadowMap.getLayout().size; const ShadowMap::ShadowMapLayout layout{ .zResolution = mTextureZResolution, @@ -159,29 +164,37 @@ bool ShadowMapManager::update(FEngine& engine, FView& view, UniformBuffer& perVi mat4f const& lightFromWorldMatrix = shadowMap.getLightSpaceMatrix(); u.setUniform(offsetof(PerViewUib, lightFromWorldMatrix), lightFromWorldMatrix); - FLightManager::Instance light = lightData.elementAt(l); const float texelSizeWorldSpace = shadowMap.getTexelSizeWorldSpace(); const float normalBias = lcm.getShadowNormalBias(light); u.setUniform(offsetof(PerViewUib, shadowBias), float3{0, normalBias * texelSizeWorldSpace, 0}); 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; } } - perViewUb.setUniform(offsetof(PerViewUib, directionalShadows), - mDirectionalShadowMap && mDirectionalShadowMap.getShadowMap()->hasVisibleShadows()); + perViewUb.setUniform(offsetof(PerViewUib, directionalShadows), directionalShadows); + perViewUb.setUniform(offsetof(PerViewUib, ssContactShadowDistance), screenSpaceShadowDistance); FScene::ShadowInfo* const shadowInfo = lightData.data(); - for (size_t i = 0; i < mSpotShadowMaps.size(); i++) { + for (size_t i = 0, c = mSpotShadowMaps.size(); i < c; i++) { auto& entry = mSpotShadowMaps[i]; // compute the frustum for this light ShadowMap& shadowMap = *entry.getShadowMap(); - size_t l = mSpotShadowMaps[i].getLightIndex(); + size_t l = entry.getLightIndex(); - const size_t textureDimension = mSpotShadowMaps[i].getLayout().size; + const size_t textureDimension = entry.getLayout().size; const ShadowMap::ShadowMapLayout layout{ .zResolution = mTextureZResolution, .atlasDimension = textureSize, diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 3f48c474ff..7c9b94e692 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -709,10 +709,16 @@ void FView::prepareSSR(backend::Handle ssr, float refraction mPerViewUb.setUniform(offsetof(PerViewUib, refractionLodOffset), refractionLodOffset); } +void FView::prepareStructure(backend::Handle structure) const noexcept { + // sampler must be NEAREST + mPerViewSb.setSampler(PerViewSib::STRUCTURE, structure, {}); +} + void FView::cleanupRenderPasses() const noexcept { auto& samplerGroup = mPerViewSb; samplerGroup.setSampler(PerViewSib::SSAO, {}, {}); samplerGroup.setSampler(PerViewSib::SSR, {}, {}); + samplerGroup.setSampler(PerViewSib::STRUCTURE, {}, {}); } void FView::froxelize(FEngine& engine) const noexcept { diff --git a/filament/src/components/RenderableManager.cpp b/filament/src/components/RenderableManager.cpp index 13a54493b7..f2aeb67b55 100644 --- a/filament/src/components/RenderableManager.cpp +++ b/filament/src/components/RenderableManager.cpp @@ -45,6 +45,7 @@ struct RenderableManager::BuilderDetails { bool mCulling : 1; bool mCastShadows : 1; bool mReceiveShadows : 1; + bool mScreenSpaceContactShadows : 1; bool mMorphingEnabled : 1; size_t mSkinningBoneCount = 0; Bone const* mUserBones = nullptr; @@ -52,7 +53,7 @@ struct RenderableManager::BuilderDetails { explicit BuilderDetails(size_t count) : mEntries(count), mCulling(true), mCastShadows(false), mReceiveShadows(true), - mMorphingEnabled(false) { + mScreenSpaceContactShadows(false), mMorphingEnabled(false) { } // this is only needed for the explicit instantiation below BuilderDetails() = default; @@ -135,6 +136,11 @@ RenderableManager::Builder& RenderableManager::Builder::receiveShadows(bool enab return *this; } +RenderableManager::Builder& RenderableManager::Builder::screenSpaceContactShadows(bool enable) noexcept { + mImpl->mScreenSpaceContactShadows = enable; + return *this; +} + RenderableManager::Builder& RenderableManager::Builder::skinning(size_t boneCount) noexcept { mImpl->mSkinningBoneCount = boneCount; return *this; @@ -280,6 +286,7 @@ void FRenderableManager::create( setPriority(ci, builder->mPriority); setCastShadows(ci, builder->mCastShadows); setReceiveShadows(ci, builder->mReceiveShadows); + setScreenSpaceContactShadows(ci, builder->mScreenSpaceContactShadows); setCulling(ci, builder->mCulling); setSkinning(ci, false); setMorphing(ci, builder->mMorphingEnabled); @@ -572,6 +579,10 @@ void RenderableManager::setReceiveShadows(Instance instance, bool enable) noexce upcast(this)->setReceiveShadows(instance, enable); } +void RenderableManager::setScreenSpaceContactShadows(Instance instance, bool enable) noexcept { + upcast(this)->setScreenSpaceContactShadows(instance, enable); +} + bool RenderableManager::isShadowCaster(Instance instance) const noexcept { return upcast(this)->isShadowCaster(instance); } diff --git a/filament/src/components/RenderableManager.h b/filament/src/components/RenderableManager.h index 63969841d9..aa580adba1 100644 --- a/filament/src/components/RenderableManager.h +++ b/filament/src/components/RenderableManager.h @@ -52,14 +52,17 @@ public: // TODO: consider renaming, this pertains to material variants, not strictly visibility. struct Visibility { - uint8_t priority : 3; - bool castShadows : 1; - bool receiveShadows : 1; - bool culling : 1; - bool skinning : 1; - bool morphing : 1; + uint8_t priority : 3; + bool castShadows : 1; + bool receiveShadows : 1; + bool culling : 1; + bool skinning : 1; + bool morphing : 1; + bool screenSpaceContactShadows : 1; }; + static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits"); + explicit FRenderableManager(FEngine& engine) noexcept; ~FRenderableManager(); @@ -103,6 +106,7 @@ public: inline void setLayerMask(Instance instance, uint8_t layerMask) noexcept; inline void setReceiveShadows(Instance instance, bool enable) noexcept; + inline void setScreenSpaceContactShadows(Instance instance, bool enable) noexcept; inline void setCulling(Instance instance, bool enable) noexcept; inline void setSkinning(Instance instance, bool enable) noexcept; inline void setMorphing(Instance instance, bool enable) noexcept; @@ -252,6 +256,13 @@ void FRenderableManager::setReceiveShadows(Instance instance, bool enable) noexc } } +void FRenderableManager::setScreenSpaceContactShadows(Instance instance, bool enable) noexcept { + if (instance) { + Visibility& visibility = mManager[instance].visibility; + visibility.screenSpaceContactShadows = enable; + } +} + void FRenderableManager::setCulling(Instance instance, bool enable) noexcept { if (instance) { Visibility& visibility = mManager[instance].visibility; diff --git a/filament/src/details/View.h b/filament/src/details/View.h index 2b46ed81d3..bd7c26d153 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -163,8 +163,9 @@ public: void prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaScope& arena, Viewport const& viewport) noexcept; void prepareSSAO(backend::Handle ssao) const noexcept; - void cleanupRenderPasses() const noexcept; void prepareSSR(backend::Handle ssr, float refractionLodOffset) const noexcept; + void prepareStructure(backend::Handle structure) const noexcept; + void cleanupRenderPasses() const noexcept; void froxelize(FEngine& engine) const noexcept; void commitUniforms(backend::DriverApi& driver) const noexcept; void commitFroxels(backend::DriverApi& driverApi) const noexcept; diff --git a/filament/src/fg/FrameGraph.cpp b/filament/src/fg/FrameGraph.cpp index f90b011c62..e4d6951e2f 100644 --- a/filament/src/fg/FrameGraph.cpp +++ b/filament/src/fg/FrameGraph.cpp @@ -280,12 +280,14 @@ FrameGraph& FrameGraph::compile() noexcept { // compute resources reference counts (i.e. resources we're reading from) for (FrameGraphHandle resource : pass.reads) { + assert(resource.isValid()); // add a reference for each pass that reads from this resource resourceNodes[resource.index]->readerCount++; } #ifndef NDEBUG for (FrameGraphHandle resource : pass.writes) { + assert(resource.isValid()); assert(resourceNodes[resource.index]->writer == &pass); } #endif diff --git a/libs/filabridge/include/private/filament/SibGenerator.h b/libs/filabridge/include/private/filament/SibGenerator.h index 64801b2da1..23ab7b7ad3 100644 --- a/libs/filabridge/include/private/filament/SibGenerator.h +++ b/libs/filabridge/include/private/filament/SibGenerator.h @@ -42,8 +42,9 @@ struct PerViewSib { static constexpr size_t IBL_SPECULAR = 4; static constexpr size_t SSAO = 5; static constexpr size_t SSR = 6; + static constexpr size_t STRUCTURE = 7; - static constexpr size_t SAMPLER_COUNT = 7; + static constexpr size_t SAMPLER_COUNT = 8; }; } diff --git a/libs/filabridge/include/private/filament/UibGenerator.h b/libs/filabridge/include/private/filament/UibGenerator.h index 7f0a20903a..be42b1c380 100644 --- a/libs/filabridge/include/private/filament/UibGenerator.h +++ b/libs/filabridge/include/private/filament/UibGenerator.h @@ -84,10 +84,13 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) filament::math::float2 iblMaxMipLevel; // maxlevel, float(1<getRenderableManager(); + static constexpr int kNumAvailable = 128; utils::Entity renderables[kNumAvailable]; while (size_t numWritten = mAsset->popRenderables(renderables, kNumAvailable)) { + for (size_t i = 0; i < numWritten; i++) { + auto ri = tcm.getInstance(renderables[i]); + tcm.setScreenSpaceContactShadows(ri, true); + } mScene->addEntities(renderables, numWritten); } } @@ -451,6 +458,7 @@ void SimpleViewer::updateUserInterface() { ImGuiExt::DirectionWidget("Sun direction", mSunlightDirection.v); ImGui::Checkbox("Enable sunlight", &mEnableSunlight); ImGui::Checkbox("Enable shadows", &mEnableShadows); + ImGui::Checkbox("Enable contact shadows", &mEnableContactShadows); } if (mEnableSunlight) { @@ -460,6 +468,9 @@ 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); } diff --git a/samples/material_sandbox.cpp b/samples/material_sandbox.cpp index a3054bf116..c0d73592ca 100644 --- a/samples/material_sandbox.cpp +++ b/samples/material_sandbox.cpp @@ -204,6 +204,7 @@ static void setup(Engine* engine, View*, Scene* scene) { if (!instance) continue; rcm.setCastShadows(instance, g_params.castShadows); + rcm.setScreenSpaceContactShadows(instance, true); if (!g_singleMode || count == 0) { for (size_t i = 0; i < rcm.getPrimitiveCount(instance); i++) { @@ -476,6 +477,12 @@ static void gui(filament::Engine* engine, filament::View*) { ImGui::SliderAngle("ibl rotation", ¶ms.iblRotation); ImGuiExt::DirectionWidget("direction", params.lightDirection.v); ImGui::Indent(); + if (ImGui::CollapsingHeader("Contact Shadows")) { + DebugRegistry& debug = engine->getDebugRegistry(); + ImGui::Checkbox("enabled###contactShadows", ¶ms.screenSpaceContactShadows); + ImGui::SliderInt("steps", ¶ms.stepCount, 0, 255); + ImGui::SliderFloat("distance", ¶ms.maxShadowDistance, 0.0f, 10.0f); + } if (ImGui::CollapsingHeader("SSAO")) { DebugRegistry& debug = engine->getDebugRegistry(); ImGui::Checkbox("enabled###ssao", ¶ms.ssao); @@ -578,6 +585,9 @@ static void gui(filament::Engine* engine, filament::View*) { options.constantBias = params.constantBias; options.polygonOffsetConstant = params.polygonOffsetConstant; options.polygonOffsetSlope = params.polygonOffsetSlope; + options.screenSpaceContactShadows = params.screenSpaceContactShadows; + options.stepCount = params.stepCount; + options.maxShadowDistance = params.maxShadowDistance; lcm.setShadowOptions(lightInstance, options); } diff --git a/samples/material_sandbox.h b/samples/material_sandbox.h index 14d167e0ef..f41aa23ffb 100644 --- a/samples/material_sandbox.h +++ b/samples/material_sandbox.h @@ -109,6 +109,9 @@ struct SandboxParameters { bool ssao = false; filament::View::AmbientOcclusionOptions ssaoOptions; filament::View::BloomOptions bloomOptions; + bool screenSpaceContactShadows = false; + int stepCount = 8; + float maxShadowDistance = 0.3; }; inline void createInstances(SandboxParameters& params, filament::Engine& engine) { diff --git a/shaders/src/light_directional.fs b/shaders/src/light_directional.fs index 47852fb225..3fa9b52b06 100644 --- a/shaders/src/light_directional.fs +++ b/shaders/src/light_directional.fs @@ -38,12 +38,23 @@ void evaluateDirectionalLight(const MaterialInputs material, float visibility = 1.0; #if defined(HAS_SHADOWING) if (light.NoL > 0.0) { - if (frameUniforms.directionalShadows) { + float ssContactShadowOcclusion = 0.0; + + if ((frameUniforms.directionalShadows & 1u) != 0u) { visibility = shadow(light_shadowMap, 0u, getLightSpacePosition()); - #if defined(MATERIAL_HAS_AMBIENT_OCCLUSION) - visibility *= computeMicroShadowing(light.NoL, material.ambientOcclusion); - #endif } + + if (visibility > 0.0 && frameUniforms.ssContactShadowDistance != 0.0) { + if (objectUniforms.screenSpaceContactShadows != 0) { + ssContactShadowOcclusion = screenSpaceContactShadow(light.l); + } + } + + visibility = visibility * (1.0 - ssContactShadowOcclusion); + + #if defined(MATERIAL_HAS_AMBIENT_OCCLUSION) + visibility *= computeMicroShadowing(light.NoL, material.ambientOcclusion); + #endif } else { #if defined(MATERIAL_CAN_SKIP_LIGHTING) return; diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index c6958339a3..ce64bb0505 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -231,6 +231,81 @@ float ShadowSample_PCF_High(const lowp sampler2DArrayShadow map, const uint laye } #endif +//------------------------------------------------------------------------------ +// Screen-space Contact Shadows +//------------------------------------------------------------------------------ + +struct ScreenSpaceRay { + highp vec3 ssRayStart; + highp vec3 ssRayEnd; + highp vec3 ssViewRayEnd; + highp vec3 uvRayStart; + highp vec3 uvRay; +}; + +void initScreenSpaceRay(out ScreenSpaceRay ray, highp vec3 wsRayStart, vec3 wsRayDirection, float wsRayLength) { + highp mat4 worldToClip = getClipFromWorldMatrix(); + highp mat4 viewToClip = getClipFromViewMatrix(); + + // ray end in world space + highp vec3 wsRayEnd = wsRayStart + wsRayDirection * wsRayLength; + + // ray start/end in clip space + highp vec4 csRayStart = worldToClip * vec4(wsRayStart, 1.0); + highp vec4 csRayEnd = worldToClip * vec4(wsRayEnd, 1.0); + highp vec4 csViewRayEnd = csRayStart + viewToClip * vec4(0.0, 0.0, wsRayLength, 0.0); + + // ray start/end in screen space + ray.ssRayStart = csRayStart.xyz * 1.0 / csRayStart.w; + ray.ssRayEnd = csRayEnd.xyz * 1.0 / csRayEnd.w; + ray.ssViewRayEnd = csViewRayEnd.xyz * 1.0 / csViewRayEnd.w; + + // convert all to uv (texture) space + highp vec3 uvRayEnd = ray.ssRayEnd.xyz * 0.5 + 0.5; + ray.uvRayStart = ray.ssRayStart.xyz * 0.5 + 0.5; + ray.uvRay = uvRayEnd - ray.uvRayStart; +} + +float screenSpaceContactShadow(vec3 lightDirection) { + // cast a ray in the direction of the light + float occlusion = 0.0; + uint kStepCount = (frameUniforms.directionalShadows >> 8u) & 0xFFu; + float kDistanceMax = frameUniforms.ssContactShadowDistance; + + ScreenSpaceRay rayData; + initScreenSpaceRay(rayData, shading_position, lightDirection, kDistanceMax); + + // step + highp float dt = 1.0 / float(kStepCount); + + // tolerance + float tolerance = abs(rayData.ssViewRayEnd.z - rayData.ssRayStart.z) * dt * 0.5; + + // dithter the ray with interleaved grandient noise + const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189); + float dither = fract(m.z * fract(dot(gl_FragCoord.xy, m.xy))) - 0.5; + + // normalized postion on the ray (0 to 1) + float t = dt * dither + dt; + + highp vec3 ray; + for (uint i = 0 ; i < kStepCount ; i++, t += dt) { + ray = rayData.uvRayStart + rayData.uvRay * t; + float z = textureLod(light_structure, uvToRenderTargetUV(ray.xy), 0.0).r; + float dz = ray.z - z; + if (abs(tolerance - dz) < tolerance) { + occlusion = 1.0; + break; + } + } + + // we fade out the contribution of contact shadows towards the edge of the screen + // because we don't have depth data there + vec2 fade = max(12.0 * abs(ray.xy - 0.5) - 5.0, 0.0); + occlusion *= saturate(1.0 - dot(fade, fade)); + return occlusion; +} + //------------------------------------------------------------------------------ // Shadow sampling dispatch //------------------------------------------------------------------------------