Improvements to shadowing
- use the geometric normal to apply the shadow bias. This affects cascades > 0 and spot/point lights. - use the scene's origin as a reference point for stabilizing the shadowmap, this is more robust. - clamp directional shadowmap correctly to the 1-texel border, which needs to be reachable, as it is a valid value. - don't snap the shadowmap to texel boundaries if stable mode is not active (before we only didn't do it based on lispsm). Stable mode can make the shadow unstable when both the camera and the scene move together, so it's better to have a more predictable API where "stable" mode means that the snapping occurs and doesn't otherwise. - add "far origin" distance slider to the debug ui FIXES=[299310624]
This commit is contained in:
committed by
Mathias Agopian
parent
da96b45827
commit
1ff0a2dd6d
@@ -7,3 +7,5 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: Fixes "stable" shadows (see b/299310624)
|
||||
|
||||
@@ -327,9 +327,9 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
if (params.options.stable) {
|
||||
if (viewVolumeBoundingSphere.w > 0) {
|
||||
s = 1.0f / viewVolumeBoundingSphere.w;
|
||||
o = mat4f::project(Mv * camera.model, viewVolumeBoundingSphere.xyz).xy;
|
||||
o = mat4f::project(LMpMv * camera.model, viewVolumeBoundingSphere.xyz).xy;
|
||||
} else {
|
||||
Aabb const bounds = compute2DBounds(Mv,
|
||||
Aabb const bounds = compute2DBounds(LMpMv,
|
||||
wsClippedShadowReceiverVolume.data(), vertexCount);
|
||||
if (UTILS_UNLIKELY((bounds.min.x >= bounds.max.x) || (bounds.min.y >= bounds.max.y))) {
|
||||
// this could happen if the only thing visible is a perfectly horizontal or
|
||||
@@ -379,9 +379,9 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
// adjust offset for scale
|
||||
o = -s * o;
|
||||
|
||||
if (!useLispsm) {
|
||||
// stabilize the shadowmap in all modes, except lispsm which can never be stable
|
||||
snapLightFrustum(s, o, Mv, camera.worldOrigin, shadowMapInfo.shadowDimension);
|
||||
if (params.options.stable) {
|
||||
snapLightFrustum(s, o, LMpMv,
|
||||
sceneInfo.wsShadowCastersVolume.center(), shadowMapInfo.shadowDimension);
|
||||
}
|
||||
|
||||
const mat4f F(mat4f::row_major_init {
|
||||
@@ -400,8 +400,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
// Computes St the transform to use in the shader to access the shadow map texture
|
||||
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
|
||||
const backend::Viewport viewport = getViewport();
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, viewport);
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||
const mat4f St = math::highPrecisionMultiply(Mt, S);
|
||||
|
||||
ShadowMap::ShaderParameters shaderParameters;
|
||||
@@ -423,7 +422,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
shaderParameters.lightSpace = computeVsmLightSpaceMatrix(St, Mv, znear, zfar);
|
||||
}
|
||||
|
||||
shaderParameters.scissorNormalized = getViewportNormalized(shadowMapInfo);
|
||||
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
|
||||
|
||||
// We apply the constant bias in world space (as opposed to light-space) to account
|
||||
// for perspective and lispsm shadow maps. This also allows us to do this at zero-cost
|
||||
@@ -456,9 +455,8 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
|
||||
assert_invariant(shadowMapInfo.textureDimension == mOptions->mapSize);
|
||||
|
||||
// Final shadow transform
|
||||
const backend::Viewport viewport = getViewport();
|
||||
const mat4f S = math::highPrecisionMultiply(Mp, Mv);
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, viewport);
|
||||
const auto [Mt, Mn] = ShadowMap::getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||
const mat4f St = math::highPrecisionMultiply(Mt, S);
|
||||
|
||||
// TODO: focus projection
|
||||
@@ -485,7 +483,7 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
|
||||
shaderParameters.lightSpace = computeVsmLightSpaceMatrix(St, Mv, nearPlane, farPlane);
|
||||
}
|
||||
|
||||
shaderParameters.scissorNormalized = getViewportNormalized(shadowMapInfo);
|
||||
shaderParameters.scissorNormalized = getClampToEdgeCoords(shadowMapInfo);
|
||||
|
||||
const float3 direction = -transpose(Mv)[2].xyz;
|
||||
const float constantBias = shadowMapInfo.vsm ? 0.0f : params.options.constantBias;
|
||||
@@ -848,9 +846,9 @@ void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
|
||||
}
|
||||
|
||||
void ShadowMap::snapLightFrustum(float2& s, float2& o,
|
||||
mat4f const& Mv, mat4 worldOrigin, int2 resolution) noexcept {
|
||||
mat4f const& Mv, double3 wsSnapCoords, int2 resolution) noexcept {
|
||||
|
||||
auto proj = [](mat4 m, double4 v) -> double3 {
|
||||
auto proj = [](mat4 m, double3 v) -> double3 {
|
||||
// for directional light p.w == 1, exactly
|
||||
auto p = m * v;
|
||||
assert_invariant(p.w == 1.0);
|
||||
@@ -876,7 +874,7 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
|
||||
mat4 const FMv{ F * Mv };
|
||||
|
||||
// This offsets the texture coordinates, so it has a fixed offset w.r.t the world
|
||||
double2 const lsOrigin = proj(FMv, worldOrigin[3]).xy;
|
||||
double2 const lsOrigin = proj(FMv, wsSnapCoords).xy;
|
||||
double2 const d = (fract(lsOrigin * resolution * 0.5) * 2.0) / resolution;
|
||||
|
||||
// adjust offset
|
||||
@@ -1258,33 +1256,58 @@ void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getViewport() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the
|
||||
// texture. This can only happen for the directional light when "focus shadow casters is used".
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp).
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
return { border, border, dim - 2u * border, dim - 2u * border };
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getScissor() const noexcept {
|
||||
// We set a viewport with a 1-texel border for when we index outside the
|
||||
// texture. This can only happen for the directional light when "focus shadow casters is used".
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp), so we
|
||||
// don't scissor the border, so it gets filled with correct neighboring texels.
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
return { border, border, dim - 2u * border, dim - 2u * border };
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
default:
|
||||
return { 0, 0, dim, dim };
|
||||
}
|
||||
}
|
||||
|
||||
math::float4 ShadowMap::getViewportNormalized(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
const auto [l, b, w, h] = getViewport();
|
||||
const float texel = 1.0f / float(shadowMapInfo.atlasDimension);
|
||||
const float4 v = float4{ l, b, l + w, b + h } * texel;
|
||||
math::float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
float border; // shadowmap border in texels
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
// For directional lights, we need to allow the sampling to reach the border, it
|
||||
// happens when "focus shadow casters" is used for instance.
|
||||
border = 0.5f;
|
||||
break;
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
// For spot and point light, this is equal to the viewport. i.e. the valid
|
||||
// texels are inside the viewport (w/ 1-texel border), the border will be used
|
||||
// for bilinear filtering.
|
||||
border = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
float const texel = 1.0f / float(shadowMapInfo.atlasDimension);
|
||||
float const dim = float(mOptions->mapSize);
|
||||
float const l = border;
|
||||
float const b = border;
|
||||
float const w = dim - 2.0f * border;
|
||||
float const h = dim - 2.0f * border;
|
||||
float4 const v = float4{ l, b, l + w, b + h } * texel;
|
||||
if (shadowMapInfo.textureSpaceFlipped) {
|
||||
// this is equivalent to calling uvToRenderTargetUV() in the shader *after* clamping
|
||||
// texture coordinates to this normalized viewport.
|
||||
|
||||
@@ -222,7 +222,7 @@ private:
|
||||
const math::float3& dir);
|
||||
|
||||
static inline void snapLightFrustum(math::float2& s, math::float2& o,
|
||||
math::mat4f const& Mv, math::mat4 worldOrigin, math::int2 resolution) noexcept;
|
||||
math::mat4f const& Mv, math::double3 wsSnapCoords, math::int2 resolution) noexcept;
|
||||
|
||||
static inline void computeFrustumCorners(math::float3* out,
|
||||
const math::mat4f& projectionViewInverse, math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
|
||||
@@ -281,7 +281,7 @@ private:
|
||||
static math::mat4f computeVsmLightSpaceMatrix(const math::mat4f& lightSpacePcf,
|
||||
const math::mat4f& Mv, float znear, float zfar) noexcept;
|
||||
|
||||
math::float4 getViewportNormalized(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||
math::float4 getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||
|
||||
float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) const noexcept;
|
||||
|
||||
@@ -101,6 +101,7 @@ struct App {
|
||||
|
||||
bool actualSize = false;
|
||||
bool originIsFarAway = false;
|
||||
float originDistance = 6378137; // Earth's radius in [m]
|
||||
|
||||
struct Scene {
|
||||
Entity groundPlane;
|
||||
@@ -759,6 +760,7 @@ int main(int argc, char** argv) {
|
||||
ImGui::Checkbox("Disable buffer padding", debug.getPropertyAddress<bool>("d.renderer.disable_buffer_padding"));
|
||||
ImGui::Checkbox("Camera at origin", debug.getPropertyAddress<bool>("d.view.camera_at_origin"));
|
||||
ImGui::Checkbox("Far Origin", &app.originIsFarAway);
|
||||
ImGui::SliderFloat("Origin", &app.originDistance, 0, 10000000);
|
||||
auto dataSource = debug.getDataSource("d.view.frame_info");
|
||||
if (dataSource.data) {
|
||||
ImGuiExt::PlotLinesSeries("FrameInfo", 6,
|
||||
@@ -956,7 +958,7 @@ int main(int argc, char** argv) {
|
||||
tcm.setParent(tcm.getInstance(camera.getEntity()), root);
|
||||
tcm.setParent(tcm.getInstance(app.asset->getRoot()), root);
|
||||
tcm.setParent(tcm.getInstance(view->getFogEntity()), root);
|
||||
tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? 1e6f : 0.0f }));
|
||||
tcm.setTransform(root, mat4f::translation(float3{ app.originIsFarAway ? app.originDistance : 0.0f }));
|
||||
|
||||
// Check if color grading has changed.
|
||||
ColorGradingSettings& options = app.viewer->getSettings().view.colorGrading;
|
||||
|
||||
@@ -109,7 +109,7 @@ highp vec4 getSpotLightSpacePosition(int index, highp vec3 dir, highp float zLig
|
||||
// for spotlights, the bias depends on z
|
||||
float bias = shadowUniforms.shadows[index].normalBias * zLight;
|
||||
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldGeometricNormalVector(),
|
||||
dir, bias, lightFromWorldMatrix);
|
||||
}
|
||||
#endif
|
||||
@@ -141,7 +141,7 @@ highp vec4 getCascadeLightSpacePosition(int cascade) {
|
||||
return vertex_lightSpacePosition;
|
||||
}
|
||||
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldNormalVector(),
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldGeometricNormalVector(),
|
||||
frameUniforms.lightDirection,
|
||||
shadowUniforms.shadows[cascade].normalBias,
|
||||
shadowUniforms.shadows[cascade].lightFromWorldMatrix);
|
||||
|
||||
Reference in New Issue
Block a user