From f9aaf5c42eecf6800fe9b43ce199e50fa8ab211a Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 4 Nov 2021 12:34:09 -0700 Subject: [PATCH] Fix normal bias for spotlights. The normal bias is now computed correctly, this requires to compute the z in lightspace in the shader. Note that this would not work as well if we used LISPSM, but we'll cross that bridge when we get there. --- filament/src/ShadowMap.cpp | 7 ++++-- shaders/src/common_shadowing.fs | 43 ++++++++++++++++++++++++--------- shaders/src/getters.fs | 6 ++--- shaders/src/main.vs | 3 ++- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/filament/src/ShadowMap.cpp b/filament/src/ShadowMap.cpp index 880508fc95..ce909601a5 100644 --- a/filament/src/ShadowMap.cpp +++ b/filament/src/ShadowMap.cpp @@ -427,8 +427,11 @@ void ShadowMap::updateSpot(const FScene::LightSoa& lightData, size_t index, // Project receivers, casters and view onto near plane, // compute intersection of that which gives the l,r,t,b planes - // FIXME: texelSizeWorldSpace doesn't work for spotlights - mTexelSizeWs = 0; //texelSizeWorldSpace(Mp, mat4f(MbMt)); + // For spotlights, we store the texel size at 1 world unit + // The size of a texel in world unit is given by: (near/dimension) / lightspace.z, + // when computing the required bias we need a half-texel size, so we multiply by 0.5 here. + // Note: this would not work with LISPSM, which warps the texture space. + mTexelSizeWs = 0.5f * nearPlane / float(mShadowMapInfo.shadowDimension); if (!mShadowMapInfo.vsm) { mLightSpace = St; diff --git a/shaders/src/common_shadowing.fs b/shaders/src/common_shadowing.fs index db5ab25bb5..3ecdbe60cc 100644 --- a/shaders/src/common_shadowing.fs +++ b/shaders/src/common_shadowing.fs @@ -8,18 +8,37 @@ * The returned point may contain a bias to attempt to eliminate common * shadowing artifacts such as "acne". To achieve this, the world space * normal at the point must also be passed to this function. + * Normal bias is not used for VSM. */ -highp vec4 computeLightSpacePosition(const highp vec3 p, const highp vec3 n, const highp vec3 l, - const float b, const highp mat4 lightFromWorldMatrix) { + #if defined(HAS_VSM) - // VSM don't apply the shadow bias - highp vec4 lightSpacePosition = mulMat4x4Float3(lightFromWorldMatrix, p); -#else - float NoL = saturate(dot(n, l)); - float sinTheta = sqrt(1.0 - NoL * NoL); - highp vec3 offsetPosition = p + n * (sinTheta * b); - highp vec4 lightSpacePosition = mulMat4x4Float3(lightFromWorldMatrix, offsetPosition); -#endif - return lightSpacePosition; +#if defined(HAS_DIRECTIONAL_LIGHTING) +highp vec4 computeLightSpacePositionDirectional(const highp vec3 p, const highp vec3 n, + const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) { + return mulMat4x4Float3(lightFromWorldMatrix, p); } -#endif +#endif // HAS_DIRECTIONAL_LIGHTING +#if defined(HAS_DYNAMIC_LIGHTING) +highp vec4 computeLightSpacePositionSpot(const highp vec3 p, const highp vec3 n, + const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) { + return mulMat4x4Float3(lightFromWorldMatrix, p); +} +#endif // HAS_DYNAMIC_LIGHTING +#else // HAS_VSM +highp vec4 computeLightSpacePositionDirectional(const highp vec3 p, const highp vec3 n, + const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) { + float NoL = saturate(dot(n, l)); + highp float sinTheta = sqrt(1.0 - NoL * NoL); + highp vec3 offsetPosition = p + n * (sinTheta * b); + return mulMat4x4Float3(lightFromWorldMatrix, offsetPosition); +} +#if defined(HAS_DYNAMIC_LIGHTING) +highp vec4 computeLightSpacePositionSpot(const highp vec3 p, const highp vec3 n, + const highp vec3 l, const float b, const highp mat4 lightFromWorldMatrix) { + highp vec4 positionLs = mulMat4x4Float3(lightFromWorldMatrix, p); + highp float oneOverZ = positionLs.w / positionLs.z; + return computeLightSpacePositionDirectional(p, n, l, b * oneOverZ, lightFromWorldMatrix); +} +#endif // HAS_DIRECTIONAL_LIGHTING +#endif // HAS_VSM +#endif // HAS_SHADOWING diff --git a/shaders/src/getters.fs b/shaders/src/getters.fs index d3fb8a3e7a..795a56619c 100644 --- a/shaders/src/getters.fs +++ b/shaders/src/getters.fs @@ -102,7 +102,7 @@ highp vec3 getNormalizedViewportCoord2() { highp vec4 getSpotLightSpacePosition(uint index) { highp vec3 dir = shadowUniforms.shadows[index].direction; float bias = shadowUniforms.shadows[index].normalBias; - return computeLightSpacePosition(vertex_worldPosition.xyz, + return computeLightSpacePositionSpot(vertex_worldPosition.xyz, vertex_worldNormal, dir, bias, shadowUniforms.shadows[index].lightFromWorldMatrix); } #endif @@ -128,13 +128,13 @@ uint getShadowCascade() { highp vec4 getCascadeLightSpacePosition(uint cascade) { // For the first cascade, return the interpolated light space position. // This branch will be coherent (mostly) for neighboring fragments, and it's worth avoiding - // the matrix multiply inside computeLightSpacePosition. + // the matrix multiply inside computeLightSpacePositionDirectional. if (cascade == 0u) { // Note: this branch may cause issues with derivatives return vertex_lightSpacePosition; } - return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(), + return computeLightSpacePositionDirectional(getWorldPosition(), getWorldNormalVector(), frameUniforms.lightDirection, frameUniforms.shadowBias.y, frameUniforms.lightFromWorldMatrix[cascade]); } diff --git a/shaders/src/main.vs b/shaders/src/main.vs index 4b90130bb3..8619f5a22d 100644 --- a/shaders/src/main.vs +++ b/shaders/src/main.vs @@ -89,7 +89,8 @@ void main() { #endif #if defined(HAS_SHADOWING) && defined(HAS_DIRECTIONAL_LIGHTING) - vertex_lightSpacePosition = computeLightSpacePosition(vertex_worldPosition.xyz, vertex_worldNormal, + vertex_lightSpacePosition = computeLightSpacePositionDirectional( + vertex_worldPosition.xyz, vertex_worldNormal, frameUniforms.lightDirection, frameUniforms.shadowBias.y, getLightFromWorldMatrix()); #endif