From f344e44d26bf8d307a00593e34481bd25954bebf Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Wed, 31 Jan 2018 10:19:17 +0100 Subject: [PATCH] Switched to a simpler manual fixed/slope based shadow bias system. Automatic stuff fail most of the time --- libraries/render-utils/src/LightStage.cpp | 22 +------ libraries/render-utils/src/LightStage.h | 4 +- .../render-utils/src/RenderShadowTask.cpp | 8 +-- libraries/render-utils/src/RenderShadowTask.h | 9 ++- libraries/render-utils/src/Shadow.slh | 18 ++--- libraries/render-utils/src/ShadowCore.slh | 13 +--- libraries/render-utils/src/Shadows_shared.slh | 4 +- .../developer/utilities/render/debugShadow.js | 4 +- scripts/developer/utilities/render/shadow.qml | 65 +++++++++++++++---- 9 files changed, 78 insertions(+), 69 deletions(-) diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index e06d24f1b1..259d0dd665 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -70,18 +70,6 @@ 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 }, @@ -222,17 +210,14 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum, 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++; } } void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth, float farDepth, float baseBias) { + float nearDepth, float farDepth, float fixedBias, float slopeBias) { assert(nearDepth < farDepth); assert(cascadeIndex < _cascades.size()); @@ -283,8 +268,8 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co auto& schema = _schemaBuffer.edit(); auto& schemaCascade = schema.cascades[cascadeIndex]; schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); - schemaCascade.fixedBias = baseBias; - schema.updateCascade(cascadeIndex, *cascade._frustum); + schemaCascade.fixedBias = fixedBias; + schemaCascade.slopeBias = slopeBias; } void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { @@ -298,7 +283,6 @@ void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const View 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 922ec5eb4a..9812426fa6 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -80,7 +80,7 @@ public: void setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth = 1.0f, float farDepth = 1000.0f); void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, - float nearDepth = 1.0f, float farDepth = 1000.0f, float baseBias = 0.005f); + float nearDepth = 1.0f, float farDepth = 1000.0f, float fixedBias = 0.005f, float slopeBias = 0.005f); void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum); const UniformBufferView& getBuffer() const { return _schemaBuffer; } @@ -110,8 +110,6 @@ public: Schema(); - void updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum); - }; UniformBufferView _schemaBuffer = nullptr; }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 2172dda3e3..2b5ab09c06 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -256,9 +256,8 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) { } void RenderShadowCascadeSetup::configure(const Config& configuration) { - // I'm not very proud of this empirical adjustment - auto cascadeBias = configuration.bias * powf(1.1f, _cascadeIndex); - _baseBias = cascadeBias * cascadeBias * 0.01f; + _fixedBias = configuration.fixedBias * configuration.fixedBias * configuration.fixedBias * 0.004f; + _slopeBias = configuration.slopeBias * configuration.slopeBias * configuration.slopeBias * 0.01f; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { @@ -274,7 +273,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon if (globalShadow && _cascadeIndexgetCascadeCount()) { output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); - globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, _baseBias); + globalShadow->setKeylightCascadeFrustum(_cascadeIndex, args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, + _fixedBias, _slopeBias); // Set the keylight render args args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index c4f0c65bfc..7e5655b375 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -64,10 +64,12 @@ public: class RenderShadowCascadeSetupConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float bias MEMBER bias NOTIFY dirty) + Q_PROPERTY(float fixedBias MEMBER fixedBias NOTIFY dirty) + Q_PROPERTY(float slopeBias MEMBER slopeBias NOTIFY dirty) public: - float bias{ 0.25f }; + float fixedBias{ 0.15f }; + float slopeBias{ 0.55f }; signals: void dirty(); @@ -86,7 +88,8 @@ public: private: unsigned int _cascadeIndex; - float _baseBias{ 0.1f }; + float _fixedBias{ 0.1f }; + float _slopeBias{ 0.1f }; }; class RenderShadowCascadeTeardown { diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index c11f5fa6a7..6575e68090 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -83,19 +83,12 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve return shadowAttenuation; } -float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias, - vec3 worldPosition, vec3 worldLightDir) { +float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) { 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); + float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL; return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } @@ -106,12 +99,11 @@ float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDe float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); // Adjust bias if we are at a grazing angle with light - float ndotl = dot(worldLightDir, worldNormal); - float bias = 1.0/(ndotl*ndotl)-1.0; + float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); vec2 cascadeAttenuations = vec2(1.0, 1.0); - cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], bias, worldPosition.xyz, worldLightDir); + cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], oneMinusNdotL); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { - cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], bias, worldPosition.xyz, worldLightDir); + cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], oneMinusNdotL); } 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 2d48e16ef4..782e2bc2b8 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -37,21 +37,14 @@ float getShadowScale() { return shadow.invMapSize; } -float getShadowBias(int cascadeIndex) { +float getShadowFixedBias(int cascadeIndex) { return shadow.cascades[cascadeIndex].fixedBias; } -vec3 getShadowFrustumPosition(int cascadeIndex) { - return shadow.cascades[cascadeIndex].frustumPosition; +float getShadowSlopeBias(int cascadeIndex) { + return shadow.cascades[cascadeIndex].slopeBias; } -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 vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index def6b1b4d4..0d49fc037e 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -11,10 +11,8 @@ struct ShadowTransform { MAT4 reprojection; - VEC3 frustumPosition; float fixedBias; - float adaptiveBiasUnitScale; - float adaptiveBiasTransformScale; + float slopeBias; float _padding1; float _padding2; }; diff --git a/scripts/developer/utilities/render/debugShadow.js b/scripts/developer/utilities/render/debugShadow.js index a0d2142258..1f1d00e6b4 100644 --- a/scripts/developer/utilities/render/debugShadow.js +++ b/scripts/developer/utilities/render/debugShadow.js @@ -14,7 +14,7 @@ var qml = Script.resolvePath('shadow.qml'); var window = new OverlayWindow({ title: 'Shadow Debug', source: qml, - width: 200, - height: 90 + width: 250, + height: 300 }); window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 32405a5260..a077ab9f50 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -36,6 +36,14 @@ Column { shadow1Config.enabled = false; shadow2Config.enabled = false; shadow3Config.enabled = false; + shadow0Config.isFrozen = false; + shadow1Config.isFrozen = false; + shadow2Config.isFrozen = false; + shadow3Config.isFrozen = false; + shadow0BoundConfig.isFrozen = false; + shadow1BoundConfig.isFrozen = false; + shadow2BoundConfig.isFrozen = false; + shadow3BoundConfig.isFrozen = false; } CheckBox { @@ -72,35 +80,68 @@ Column { } } ConfigSlider { - label: qsTr("Cascade 0 bias") + label: qsTr("Cascade 0 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 1 bias") + label: qsTr("Cascade 1 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 2 bias") + label: qsTr("Cascade 2 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 } ConfigSlider { - label: qsTr("Cascade 3 bias") + label: qsTr("Cascade 3 fixed bias") integral: false config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") - property: "bias" + property: "fixedBias" max: 1.0 - min: 0.01 + min: 0.0 + } + + ConfigSlider { + label: qsTr("Cascade 0 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 1 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 2 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") + property: "slopeBias" + max: 1.0 + min: 0.0 + } + ConfigSlider { + label: qsTr("Cascade 3 slope bias") + integral: false + config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") + property: "slopeBias" + max: 1.0 + min: 0.0 } }