From 0324f41565c77acdda2d326560aeba2a2f260498 Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 29 Jan 2018 17:23:35 +0100 Subject: [PATCH] Trying to improve adaptive shadow bias --- libraries/render-utils/src/LightStage.cpp | 35 ++++++++++++++----- libraries/render-utils/src/LightStage.h | 3 ++ .../render-utils/src/RenderShadowTask.cpp | 4 ++- libraries/render-utils/src/RenderShadowTask.h | 2 +- libraries/render-utils/src/Shadow.slh | 15 +++++--- libraries/render-utils/src/ShadowCore.slh | 14 ++++++-- libraries/render-utils/src/Shadows_shared.slh | 8 ++--- 7 files changed, 58 insertions(+), 23 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index eac9dd7657..e06d24f1b1 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -23,8 +23,6 @@ const glm::mat4 LightStage::Shadow::_biasMatrix{ 0.5, 0.5, 0.5, 1.0 }; const int LightStage::Shadow::MAP_SIZE = 1024; -static const auto MAX_BIAS = 0.006f; - const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { @@ -63,7 +61,7 @@ LightStage::LightStage() { LightStage::Shadow::Schema::Schema() { ShadowTransform defaultTransform; - defaultTransform.bias = MAX_BIAS; + defaultTransform.fixedBias = 0.005f; std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform); invMapSize = 1.0f / MAP_SIZE; cascadeCount = 1; @@ -72,6 +70,18 @@ LightStage::Shadow::Schema::Schema() { maxDistance = 20.0f; } +void LightStage::Shadow::Schema::updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum) { + auto& cascade = cascades[cascadeIndex]; + cascade.frustumPosition = shadowFrustum.getPosition(); + // The adaptative bias is computing how much depth offset we have to add to + // push back a coarsely sampled surface perpendicularly to the shadow direction, + // to not have shadow acnee. The final computation is done in the shader, based on the + // surface normal. + auto maxWorldFrustumSize = glm::max(shadowFrustum.getWidth(), shadowFrustum.getHeight()); + cascade.adaptiveBiasUnitScale = maxWorldFrustumSize * 0.5f * invMapSize; + cascade.adaptiveBiasTransformScale = shadowFrustum.getNearClip() * shadowFrustum.getFarClip() / (shadowFrustum.getFarClip() - shadowFrustum.getNearClip()); +} + LightStage::Shadow::Cascade::Cascade() : _frustum{ std::make_shared() }, _minDistance{ 0.0f }, @@ -210,13 +220,15 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, // Position the keylight frustum auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; + // Update the buffer + auto& schema = _schemaBuffer.edit(); + auto cascadeIndex = 0; for (auto& cascade : _cascades) { cascade._frustum->setOrientation(orientation); cascade._frustum->setPosition(position); + schema.cascades[cascadeIndex].frustumPosition = position; + cascadeIndex++; } - // Update the buffer - auto& schema = _schemaBuffer.edit(); - schema.lightDirInViewSpace = glm::inverse(viewFrustum.getView()) * glm::vec4(lightDirection, 0.f); } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, @@ -269,8 +281,10 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co // Update the buffer auto& schema = _schemaBuffer.edit(); - schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - schema.cascades[cascadeIndex].bias = baseBias; + auto& schemaCascade = schema.cascades[cascadeIndex]; + schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); + schemaCascade.fixedBias = baseBias; + schema.updateCascade(cascadeIndex, *cascade._frustum); } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { @@ -281,7 +295,10 @@ void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const View *cascade._frustum = shadowFrustum; // Update the buffer - _schemaBuffer.edit().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); + auto& schema = _schemaBuffer.edit(); + auto& schemaCascade = schema.cascades[cascadeIndex]; + schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); + schema.updateCascade(cascadeIndex, shadowFrustum); } LightStage::Index LightStage::findLight(const LightPointer& light) const { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index c2c2df8c89..922ec5eb4a 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -110,6 +110,8 @@ public: Schema(); + void updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum); + }; UniformBufferView _schemaBuffer = nullptr; }; @@ -213,6 +215,7 @@ protected: Index _sunOffLightId; Index _defaultLightId; + }; using LightStagePointer = std::shared_ptr; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index f41860b6de..2172dda3e3 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -256,7 +256,9 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } void RenderShadowCascadeSetup::configure(const Config& configuration) { - _baseBias = configuration.bias * configuration.bias * 0.01f; + // I'm not very proud of this empirical adjustment + auto cascadeBias = configuration.bias * powf(1.1f, _cascadeIndex); + _baseBias = cascadeBias * cascadeBias * 0.01f; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 42c279c02a..c4f0c65bfc 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -67,7 +67,7 @@ class RenderShadowCascadeSetupConfig : public render::Job::Config { Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) public: - float bias{ 0.5f }; + float bias{ 0.25f }; signals: void dirty(); diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 50ae8864f4..c11f5fa6a7 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,11 +83,17 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias, + vec3 worldPosition, vec3 worldLightDir) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { // If a point is not in the map, do not attenuate return 1.0; } + // After this, bias is in view units + bias *= getShadowAdaptiveBiasUnitScale(cascadeIndex); + // Transform to texcoord space (between 0 and 1) + float shadowDepth = abs(dot(worldPosition-getShadowFrustumPosition(cascadeIndex), worldLightDir)); + bias = transformShadowAdaptiveBias(cascadeIndex, shadowDepth, bias); // Add fixed bias bias += getShadowBias(cascadeIndex); return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); @@ -101,12 +107,11 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe // Adjust bias if we are at a grazing angle with light float ndotl = dot(worldLightDir, worldNormal); - float bias = 0.5*(1.0/(ndotl*ndotl)-1.0); - bias *= getShadowScale(); + float bias = 1.0/(ndotl*ndotl)-1.0; vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias, worldPosition.xyz, worldLightDir); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias, worldPosition.xyz, worldLightDir); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 9b3b086598..2d48e16ef4 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -38,11 +38,19 @@ float getShadowScale() { } float getShadowBias(int cascadeIndex) { - return shadow.cascades[cascadeIndex].bias; + return shadow.cascades[cascadeIndex].fixedBias; } -vec3 getShadowDirInViewSpace() { - return shadow.lightDirInViewSpace; +vec3 getShadowFrustumPosition(int cascadeIndex) { + return shadow.cascades[cascadeIndex].frustumPosition; +} + +float getShadowAdaptiveBiasUnitScale(int cascadeIndex) { + return shadow.cascades[cascadeIndex].adaptiveBiasUnitScale; +} + +float transformShadowAdaptiveBias(int cascadeIndex, float shadowDepth, float depthBias) { + return shadow.cascades[cascadeIndex].adaptiveBiasTransformScale * depthBias / (shadowDepth*shadowDepth); } // Compute the texture coordinates from world coordinates diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index abb226421c..def6b1b4d4 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -11,16 +11,16 @@ struct ShadowTransform { MAT4 reprojection; - - float bias; + VEC3 frustumPosition; + float fixedBias; + float adaptiveBiasUnitScale; + float adaptiveBiasTransformScale; float _padding1; float _padding2; - float _padding3; }; struct ShadowParameters { ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT]; - VEC3 lightDirInViewSpace; int cascadeCount; float invMapSize; float invCascadeBlendWidth;