From bc381ac1b7077697277975b5e336d79f43abdb0c Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 2 Apr 2019 16:56:34 -0700 Subject: [PATCH] Add a "stable" shadow-map option - "stable" mode disables all resolution optimizations that can affect stability of the shadow map (e.g. lispsm, focusing) - expose near/far hint + stable option to java --- .../src/main/cpp/LightManager.cpp | 13 ++- .../google/android/filament/LightManager.java | 8 +- filament/include/filament/LightManager.h | 21 +++++ filament/src/ShadowMap.cpp | 94 +++++++++++-------- filament/src/components/LightManager.cpp | 22 +++-- filament/src/components/LightManager.h | 23 +++-- filament/src/details/ShadowMap.h | 8 +- samples/material_sandbox.cpp | 7 ++ samples/material_sandbox.h | 1 + 9 files changed, 131 insertions(+), 66 deletions(-) diff --git a/android/filament-android/src/main/cpp/LightManager.cpp b/android/filament-android/src/main/cpp/LightManager.cpp index f24d596e4c..af5c35adb7 100644 --- a/android/filament-android/src/main/cpp/LightManager.cpp +++ b/android/filament-android/src/main/cpp/LightManager.cpp @@ -64,10 +64,17 @@ Java_com_google_android_filament_LightManager_nBuilderCastShadows(JNIEnv *env, j extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_LightManager_nBuilderShadowOptions(JNIEnv *env, jclass type, - jlong nativeBuilder, jint mapSize, jfloat constantBias, jfloat normalBias, jfloat shadowFar) { + jlong nativeBuilder, jint mapSize, jfloat constantBias, jfloat normalBias, jfloat shadowFar, + jfloat shadowNearHint, jfloat shadowFarHint, jboolean stable) { LightManager::Builder *builder = (LightManager::Builder *) nativeBuilder; builder->shadowOptions( - LightManager::ShadowOptions{(uint32_t) mapSize, constantBias, normalBias, shadowFar}); + LightManager::ShadowOptions{.mapSize = (uint32_t)mapSize, + .constantBias = constantBias, + .normalBias = normalBias, + .shadowFar = shadowFar, + .shadowNearHint = shadowNearHint, + .shadowFarHint = shadowFarHint, + .stable = (bool)stable}); } extern "C" JNIEXPORT void JNICALL @@ -292,4 +299,4 @@ Java_com_google_android_filament_LightManager_nGetHaloFalloff(JNIEnv *env, jclas jlong nativeLightManager, jint i) { LightManager *lm = (LightManager *) nativeLightManager; return lm->getSunHaloFalloff((LightManager::Instance) i); -} \ No newline at end of file +} 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 b36f9c2ba3..1ffaf6de63 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 @@ -53,6 +53,9 @@ public class LightManager { public float constantBias = 0.05f; public float normalBias = 0.4f; public float shadowFar = 0.0f; + public float shadowNearHint = 1.0f; + public float shadowFarHint = 100.0f; + public boolean stable = true; } public static final float EFFICIENCY_INCANDESCENT = 0.0220f; @@ -79,7 +82,8 @@ public class LightManager { @NonNull public Builder shadowOptions(@NonNull ShadowOptions options) { nBuilderShadowOptions(mNativeBuilder, - options.mapSize, options.constantBias, options.normalBias, options.shadowFar); + options.mapSize, options.constantBias, options.normalBias, options.shadowFar, + options.shadowNearHint, options.shadowFarHint, options.stable); return this; } @@ -275,7 +279,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); + 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 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/filament/include/filament/LightManager.h b/filament/include/filament/LightManager.h index b6bae56706..d58b2df834 100644 --- a/filament/include/filament/LightManager.h +++ b/filament/include/filament/LightManager.h @@ -210,6 +210,13 @@ public: * use the camera far distance. */ float shadowFarHint = 100.0f; + + /** + * Controls whether the shadow map should be optimized for resolution or stability. + * When set to true, all resolution enhancing features that can affect stability are + * disabling, resulting in significantly lower resolution shadows, albeit stable ones. + */ + bool stable = false; }; //! Use Builder to construct a Light object instance @@ -638,6 +645,20 @@ public: * @return the halo falloff */ float getSunHaloFalloff(Instance i) const noexcept; + + /** + * returns the shadow-map options for a given light + * @param i Instance of the component obtained from getInstance(). + * @return A ShadowOption structure + */ + ShadowOptions const& getShadowOptions(Instance i) const noexcept; + + /** + * sets the shadow-map options for a given light + * @param i Instance of the component obtained from getInstance(). + * @param options A ShadowOption structure + */ + void setShadowOptions(Instance i, ShadowOptions const& options) noexcept; }; } // namespace filament diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index 7a02c4dc2a..bb9eb7375f 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -125,9 +125,9 @@ void ShadowMap::update( FLightManager::ShadowParams params = lcm.getShadowParams(li); mat4f projection(camera.cullingProjection); - if (params.shadowFar > 0.0f) { + if (params.options.shadowFar > 0.0f) { float n = camera.zn; - float f = params.shadowFar; + float f = params.options.shadowFar; if (std::abs(projection[2].w) <= std::numeric_limits::epsilon()) { // perspective projection projection[2].z = (f + n) / (n - f); @@ -145,8 +145,6 @@ void ShadowMap::update( .view = camera.view, .zn = camera.zn, .zf = camera.zf, - .dzn = std::max(0.0f, params.shadowNearHint - camera.zn), - .dzf = std::max(0.0f, camera.zf - params.shadowFarHint), .frustum = Frustum(projection * camera.view), .worldOrigin = camera.worldOrigin }; @@ -155,10 +153,10 @@ void ShadowMap::update( const float dz = cameraInfo.zf - cameraInfo.zn; float& dzn = mEngine.debug.shadowmap.dzn; float& dzf = mEngine.debug.shadowmap.dzf; - if (dzn < 0) dzn = cameraInfo.dzn / dz; - else cameraInfo.dzn = dzn * dz; - if (dzf > 0) dzf =-cameraInfo.dzf / dz; - else cameraInfo.dzf =-dzf * dz; + if (dzn < 0) dzn = std::max(0.0f, params.options.shadowNearHint - camera.zn) / dz; + else params.options.shadowNearHint = dzn * dz - camera.zn; + if (dzf > 0) dzf =-std::max(0.0f, camera.zf - params.options.shadowFarHint) / dz; + else params.options.shadowFarHint = dzf * dz + camera.zf; using Type = FLightManager::Type; @@ -166,7 +164,7 @@ void ShadowMap::update( case Type::SUN: case Type::DIRECTIONAL: computeShadowCameraDirectional( - lightData.elementAt(index), scene, cameraInfo, + lightData.elementAt(index), scene, cameraInfo, params, visibleLayers); break; case Type::FOCUSED_SPOT: @@ -179,6 +177,7 @@ void ShadowMap::update( void ShadowMap::computeShadowCameraDirectional( float3 const& dir, FScene const* scene, CameraInfo const& camera, + FLightManager::ShadowParams const& params, uint8_t visibleLayers) noexcept { // scene bounds in world space @@ -193,10 +192,18 @@ void ShadowMap::computeShadowCameraDirectional( computeFrustumCorners(wsViewFrustumCorners, camera.model * FCamera::inverseProjection(camera.projection)); - // compute the intersection of the shadow receivers volume with the view volume - // in world space. This returns a set of points on the convex-hull of the intersection. - size_t vertexCount = intersectFrustumWithBox(mWsClippedShadowReceiverVolume, - camera.frustum, wsViewFrustumCorners, wsShadowReceiversVolume); + // compute the shadow receiver volume of interest + size_t vertexCount = 8; + if (params.options.stable) { + // in stable mode we simply take the shadow receivers volume + std::copy_n(wsShadowReceiversVolume.getCorners().data(), 8, + mWsClippedShadowReceiverVolume.data()); + } else { + // compute the intersection of the shadow receivers volume with the view volume + // in world space. This returns a set of points on the convex-hull of the intersection. + vertexCount = intersectFrustumWithBox(mWsClippedShadowReceiverVolume, + camera.frustum, wsViewFrustumCorners, wsShadowReceiversVolume); + } mHasVisibleShadows = vertexCount >= 2; if (mHasVisibleShadows) { @@ -208,28 +215,34 @@ void ShadowMap::computeShadowCameraDirectional( * * The light's model matrix contains the light position and direction. * - * For directional lights, we can choose any position. + * For directional lights, we could choose any position. + * + * TODO: The light position needs to be taken into account when computing L, + * which is not the case right now. */ - const float3 lightPosition = {}; // TODO: pick something better + const float3 lightPosition = {}; const mat4f M = mat4f::lookAt(lightPosition, lightPosition + dir, float3{ 0, 1, 0 }); const mat4f Mv = FCamera::rigidTransformInverse(M); // Orient the shadow map in the direction of the view vector by constructing a // rotation matrix around the z-axis, that aligns the y-axis with the camera's // forward vector (V) -- this gives the wrap direction for LiSPSM. - // - // If the light and view vector are parallel, this rotation becomes - // meaningless. Just use identity. - // (LdotV == (Mv*V).z, because L = {0,0,1} in light-space) mat4f L; - const float3 wsCameraFwd(camera.getForwardVector()); - const float3 lsCameraFwd = mat4f::project(Mv, wsCameraFwd); - if (UTILS_LIKELY(std::abs(lsCameraFwd.z) < 0.9997f)) { // this is |dot(L, V)| - L[0].xyz = normalize(cross(lsCameraFwd, float3{ 0, 0, 1 })); - L[1].xyz = cross(float3{ 0, 0, 1 }, L[0].xyz); - L[2].xyz = { 0, 0, 1 }; + if (params.options.stable) { + // We can't align the light space and view space in stable mode + } else { + // If the light and view vector are parallel, this rotation becomes + // meaningless. Just use identity. + // (LdotV == (Mv*V).z, because L = {0,0,1} in light-space) + const float3 wsCameraFwd(camera.getForwardVector()); + const float3 lsCameraFwd = mat4f::project(Mv, wsCameraFwd); + if (UTILS_LIKELY(std::abs(lsCameraFwd.z) < 0.9997f)) { // this is |dot(L, V)| + L[0].xyz = normalize(cross(lsCameraFwd, float3{ 0, 0, 1 })); + L[1].xyz = cross(float3{ 0, 0, 1 }, L[0].xyz); + L[2].xyz = { 0, 0, 1 }; + L = transpose(L); + } } - L = transpose(L); // lights space matrix used for finding the near and far planes const mat4f LMv(L * Mv); @@ -291,8 +304,13 @@ void ShadowMap::computeShadowCameraDirectional( // Compute the LiSPSM warping mat4f W; - if (USE_LISPSM) { - W = applyLISPSM(camera, LMpMv, mWsClippedShadowReceiverVolume, vertexCount, dir); + if (params.options.stable) { + // We can't use LISPSM in stable mode + } else { + if (USE_LISPSM) { + W = applyLISPSM(camera, params, LMpMv, + mWsClippedShadowReceiverVolume, vertexCount, dir); + } } /* @@ -338,12 +356,6 @@ void ShadowMap::computeShadowCameraDirectional( // compute focus scale and offset float2 s = 2.0f / float2(lsLightFrustum.max.xy - lsLightFrustum.min.xy); float2 o = -s * float2(lsLightFrustum.max.xy + lsLightFrustum.min.xy) * 0.5f; - - // temporal aliasing stabilization - // 3) snap the light frustum (width & height) to texels, to stabilize the shadow map - snapLightFrustum(s, o, mShadowMapDimension); - - // construct the Focus transform (scale + offset) const mat4f F(mat4f::row_major_init { s.x, 0, 0, o.x, 0, s.y, 0, o.y, @@ -372,7 +384,8 @@ void ShadowMap::computeShadowCameraDirectional( } } -mat4f ShadowMap::applyLISPSM(CameraInfo const& camera, mat4f const& LMpMv, +mat4f ShadowMap::applyLISPSM(CameraInfo const& camera, FLightManager::ShadowParams const& params, + mat4f const& LMpMv, FrustumBoxIntersection const& wsShadowReceiversVolume, size_t vertexCount, float3 const& dir) { @@ -382,8 +395,8 @@ mat4f ShadowMap::applyLISPSM(CameraInfo const& camera, mat4f const& LMpMv, // Virtual near plane -- the default is 1m, can be changed by the user. // The virtual near plane prevents too much resolution to be wasted in the area near the eye // where shadows might not be visible (e.g. a character standing won't see shadows at her feet). - const float dzn = camera.dzn; - const float dzf = camera.dzf; + const float dzn = std::max(0.0f, params.options.shadowNearHint - camera.zn); + const float dzf = std::max(0.0f, camera.zf - params.options.shadowFarHint); // near/far plane's distance from the eye in view space of the shadow receiver volume float2 znf = -computeNearFar(camera.view, wsShadowReceiversVolume.data(), vertexCount); @@ -676,9 +689,10 @@ size_t ShadowMap::intersectFrustumWithBox( return vertexCount; } -void ShadowMap::snapLightFrustum(float2& s, float2& o, - uint32_t shadowMapDimension) noexcept { - // TODO: we can also quantize the scaling value +void ShadowMap::snapLightFrustum(float2& s, float2& o, uint32_t shadowMapDimension) noexcept { + // This snaps the shadow map bounds to texels -- This can be used to stabilize the shadow-map + // when the scene is rendered in world-space and the light frustum is somewhat dependent on the + // camera position. const float r = shadowMapDimension * 0.5f; o = ceil(o * r) / r; } diff --git a/filament/src/components/LightManager.cpp b/filament/src/components/LightManager.cpp index 65e30ef4b5..a037f548e8 100644 --- a/filament/src/components/LightManager.cpp +++ b/filament/src/components/LightManager.cpp @@ -35,7 +35,7 @@ using namespace details; // ------------------------------------------------------------------------------------------------ struct LightManager::BuilderDetails { - Type mType; + Type mType = Type::DIRECTIONAL; bool mCastShadows = false; bool mCastLight = true; float3 mPosition = {}; @@ -165,14 +165,14 @@ void FLightManager::create(const FLightManager::Builder& builder, utils::Entity lightType.type = builder->mType; lightType.shadowCaster = builder->mCastShadows; lightType.lightCaster = builder->mCastLight; - lightType.shadowMapBits = uint8_t(std::min(15, std::ilogbf(builder->mShadowOptions.mapSize))); ShadowParams& shadowParams = manager[i].shadowParams; - shadowParams.shadowConstantBias = clamp(builder->mShadowOptions.constantBias, 0.0f, 2.0f); - shadowParams.shadowNormalBias = clamp(builder->mShadowOptions.normalBias, 0.0f, 3.0f); - shadowParams.shadowFar = std::max(builder->mShadowOptions.shadowFar, 0.0f); - shadowParams.shadowNearHint = std::max(builder->mShadowOptions.shadowNearHint, 0.0f); - shadowParams.shadowFarHint = std::max(builder->mShadowOptions.shadowFarHint, 0.0f); + shadowParams.options.mapSize = clamp(builder->mShadowOptions.mapSize, 0u, 2048u); + shadowParams.options.constantBias = clamp(builder->mShadowOptions.constantBias, 0.0f, 2.0f); + shadowParams.options.normalBias = clamp(builder->mShadowOptions.normalBias, 0.0f, 3.0f); + shadowParams.options.shadowFar = std::max(builder->mShadowOptions.shadowFar, 0.0f); + shadowParams.options.shadowNearHint = std::max(builder->mShadowOptions.shadowNearHint, 0.0f); + shadowParams.options.shadowFarHint = std::max(builder->mShadowOptions.shadowFarHint, 0.0f); // set default values by calling the setters setLocalPosition(i, builder->mPosition); @@ -429,4 +429,12 @@ LightManager::Type LightManager::getType(LightManager::Instance i) const noexcep return upcast(this)->getType(i); } +const LightManager::ShadowOptions& LightManager::getShadowOptions(Instance i) const noexcept { + return upcast(this)->getShadowOptions(i); +} + +void LightManager::setShadowOptions(Instance i, ShadowOptions const& options) noexcept { + upcast(this)->setShadowOptions(i, options); +} + } // namespace filament diff --git a/filament/src/components/LightManager.h b/filament/src/components/LightManager.h index 0b132a64c1..c64decbf95 100644 --- a/filament/src/components/LightManager.h +++ b/filament/src/components/LightManager.h @@ -65,7 +65,6 @@ public: struct LightType { Type type : 3; - uint8_t shadowMapBits : 4; bool shadowCaster : 1; bool lightCaster : 1; }; @@ -79,11 +78,7 @@ public: }; struct ShadowParams { - float shadowConstantBias; - float shadowNormalBias; - float shadowFar; - float shadowNearHint; - float shadowFarHint; + LightManager::ShadowOptions options; }; UTILS_NOINLINE void setLocalPosition(Instance i, const math::float3& position) noexcept; @@ -135,7 +130,7 @@ public: } constexpr uint32_t getShadowMapSize(Instance i) const noexcept { - return 1u << getLightType(i).shadowMapBits; + return getShadowParams(i).options.mapSize; } constexpr ShadowParams const& getShadowParams(Instance i) const noexcept { @@ -143,15 +138,15 @@ public: } constexpr float getShadowConstantBias(Instance i) const noexcept { - return getShadowParams(i).shadowConstantBias; + return getShadowParams(i).options.constantBias; } constexpr float getShadowNormalBias(Instance i) const noexcept { - return getShadowParams(i).shadowNormalBias; + return getShadowParams(i).options.normalBias; } constexpr float getShadowFar(Instance i) const noexcept { - return getShadowParams(i).shadowFar; + return getShadowParams(i).options.shadowFar; } constexpr const math::float3& getColor(Instance i) const noexcept { @@ -202,6 +197,14 @@ public: return mManager[i].direction; } + const ShadowOptions& getShadowOptions(Instance i) const noexcept { + return getShadowParams(i).options; + } + + void setShadowOptions(Instance i, ShadowOptions const& options) noexcept { + static_cast(mManager[i].shadowParams).options = options; + } + private: friend class FScene; diff --git a/filament/src/details/ShadowMap.h b/filament/src/details/ShadowMap.h index ad22ab3a4f..279c47c3b0 100644 --- a/filament/src/details/ShadowMap.h +++ b/filament/src/details/ShadowMap.h @@ -81,8 +81,6 @@ private: math::mat4f worldOrigin; float zn = 0; float zf = 0; - float dzn = 0; - float dzf = 0; Frustum frustum; float getNear() const noexcept { return zn; } float getFar() const noexcept { return zf; } @@ -104,11 +102,13 @@ private: using FrustumBoxIntersection = std::array; void computeShadowCameraDirectional( - math::float3 const& direction, FScene const* scene, CameraInfo const& camera, + math::float3 const& direction, FScene const* scene, + CameraInfo const& camera, FLightManager::ShadowParams const& params, uint8_t visibleLayers) noexcept; static math::mat4f applyLISPSM( - CameraInfo const& camera, const math::mat4f& LMpMv, + CameraInfo const& camera, FLightManager::ShadowParams const& params, + const math::mat4f& LMpMv, FrustumBoxIntersection const& wsShadowReceiverVolume, size_t vertexCount, const math::float3& dir); diff --git a/samples/material_sandbox.cpp b/samples/material_sandbox.cpp index b5c18b4e07..a473d28040 100644 --- a/samples/material_sandbox.cpp +++ b/samples/material_sandbox.cpp @@ -336,6 +336,7 @@ static void gui(filament::Engine* engine, filament::View*) { DebugRegistry& debug = engine->getDebugRegistry(); ImGui::Checkbox("Camera at origin", debug.getPropertyAddress("d.view.camera_at_origin")); + ImGui::Checkbox("Stable Shadow Map", ¶ms.stableShadowMap); ImGui::Checkbox("Light Far uses shadow casters", debug.getPropertyAddress("d.shadowmap.far_uses_shadowcasters")); ImGui::Checkbox("Focus shadow casters", @@ -374,6 +375,12 @@ static void gui(filament::Engine* engine, filament::View*) { params.hasDirectionalLight = false; } + auto& lcm = engine->getLightManager(); + auto instance = lcm.getInstance(params.light); + LightManager::ShadowOptions options = lcm.getShadowOptions(instance); + options.stable = params.stableShadowMap; + lcm.setShadowOptions(instance, options); + auto* ibl = FilamentApp::get().getIBL(); if (ibl) { ibl->getIndirectLight()->setIntensity(params.iblIntensity); diff --git a/samples/material_sandbox.h b/samples/material_sandbox.h index dc17262385..128a4796fc 100644 --- a/samples/material_sandbox.h +++ b/samples/material_sandbox.h @@ -80,6 +80,7 @@ struct SandboxParameters { bool tonemapping = true; bool msaa = false; bool dithering = true; + bool stableShadowMap = false; }; inline void createInstances(SandboxParameters& params, filament::Engine& engine) {