Switched to a simpler manual fixed/slope based shadow bias system. Automatic stuff fail most of the time

This commit is contained in:
Olivier Prat 2018-01-31 10:19:17 +01:00
parent 0324f41565
commit f344e44d26
9 changed files with 78 additions and 69 deletions

View file

@ -70,18 +70,6 @@ LightStage::Shadow::Schema::Schema() {
maxDistance = 20.0f; 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() : LightStage::Shadow::Cascade::Cascade() :
_frustum{ std::make_shared<ViewFrustum>() }, _frustum{ std::make_shared<ViewFrustum>() },
_minDistance{ 0.0f }, _minDistance{ 0.0f },
@ -222,17 +210,14 @@ void LightStage::Shadow::setKeylightFrustum(const ViewFrustum& viewFrustum,
auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection; auto position = viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection;
// Update the buffer // Update the buffer
auto& schema = _schemaBuffer.edit<Schema>(); auto& schema = _schemaBuffer.edit<Schema>();
auto cascadeIndex = 0;
for (auto& cascade : _cascades) { for (auto& cascade : _cascades) {
cascade._frustum->setOrientation(orientation); cascade._frustum->setOrientation(orientation);
cascade._frustum->setPosition(position); cascade._frustum->setPosition(position);
schema.cascades[cascadeIndex].frustumPosition = position;
cascadeIndex++;
} }
} }
void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, 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(nearDepth < farDepth);
assert(cascadeIndex < _cascades.size()); assert(cascadeIndex < _cascades.size());
@ -283,8 +268,8 @@ void LightStage::Shadow::setKeylightCascadeFrustum(unsigned int cascadeIndex, co
auto& schema = _schemaBuffer.edit<Schema>(); auto& schema = _schemaBuffer.edit<Schema>();
auto& schemaCascade = schema.cascades[cascadeIndex]; auto& schemaCascade = schema.cascades[cascadeIndex];
schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix(); schemaCascade.reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix();
schemaCascade.fixedBias = baseBias; schemaCascade.fixedBias = fixedBias;
schema.updateCascade(cascadeIndex, *cascade._frustum); schemaCascade.slopeBias = slopeBias;
} }
void LightStage::Shadow::setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) { 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<Schema>(); auto& schema = _schemaBuffer.edit<Schema>();
auto& schemaCascade = schema.cascades[cascadeIndex]; auto& schemaCascade = schema.cascades[cascadeIndex];
schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix(); schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();
schema.updateCascade(cascadeIndex, shadowFrustum);
} }
LightStage::Index LightStage::findLight(const LightPointer& light) const { LightStage::Index LightStage::findLight(const LightPointer& light) const {

View file

@ -80,7 +80,7 @@ public:
void setKeylightFrustum(const ViewFrustum& viewFrustum, void setKeylightFrustum(const ViewFrustum& viewFrustum,
float nearDepth = 1.0f, float farDepth = 1000.0f); float nearDepth = 1.0f, float farDepth = 1000.0f);
void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, 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); void setCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum);
const UniformBufferView& getBuffer() const { return _schemaBuffer; } const UniformBufferView& getBuffer() const { return _schemaBuffer; }
@ -110,8 +110,6 @@ public:
Schema(); Schema();
void updateCascade(int cascadeIndex, const ViewFrustum& shadowFrustum);
}; };
UniformBufferView _schemaBuffer = nullptr; UniformBufferView _schemaBuffer = nullptr;
}; };

View file

@ -256,9 +256,8 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext) {
} }
void RenderShadowCascadeSetup::configure(const Config& configuration) { void RenderShadowCascadeSetup::configure(const Config& configuration) {
// I'm not very proud of this empirical adjustment _fixedBias = configuration.fixedBias * configuration.fixedBias * configuration.fixedBias * 0.004f;
auto cascadeBias = configuration.bias * powf(1.1f, _cascadeIndex); _slopeBias = configuration.slopeBias * configuration.slopeBias * configuration.slopeBias * 0.01f;
_baseBias = cascadeBias * cascadeBias * 0.01f;
} }
void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) {
@ -274,7 +273,8 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) { if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) {
output.edit1() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered(); 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 // Set the keylight render args
args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum())); args->pushViewFrustum(*(globalShadow->getCascade(_cascadeIndex).getFrustum()));

View file

@ -64,10 +64,12 @@ public:
class RenderShadowCascadeSetupConfig : public render::Job::Config { class RenderShadowCascadeSetupConfig : public render::Job::Config {
Q_OBJECT 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: public:
float bias{ 0.25f }; float fixedBias{ 0.15f };
float slopeBias{ 0.55f };
signals: signals:
void dirty(); void dirty();
@ -86,7 +88,8 @@ public:
private: private:
unsigned int _cascadeIndex; unsigned int _cascadeIndex;
float _baseBias{ 0.1f }; float _fixedBias{ 0.1f };
float _slopeBias{ 0.1f };
}; };
class RenderShadowCascadeTeardown { class RenderShadowCascadeTeardown {

View file

@ -83,19 +83,12 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve
return shadowAttenuation; return shadowAttenuation;
} }
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias, float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) {
vec3 worldPosition, vec3 worldLightDir) {
if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) { if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) {
// If a point is not in the map, do not attenuate // If a point is not in the map, do not attenuate
return 1.0; return 1.0;
} }
// After this, bias is in view units float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL;
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); 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); float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
// Adjust bias if we are at a grazing angle with light // Adjust bias if we are at a grazing angle with light
float ndotl = dot(worldLightDir, worldNormal); float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1);
float bias = 1.0/(ndotl*ndotl)-1.0;
vec2 cascadeAttenuations = vec2(1.0, 1.0); 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()) { 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); float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
// Falloff to max distance // Falloff to max distance

View file

@ -37,21 +37,14 @@ float getShadowScale() {
return shadow.invMapSize; return shadow.invMapSize;
} }
float getShadowBias(int cascadeIndex) { float getShadowFixedBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].fixedBias; return shadow.cascades[cascadeIndex].fixedBias;
} }
vec3 getShadowFrustumPosition(int cascadeIndex) { float getShadowSlopeBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].frustumPosition; 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 // Compute the texture coordinates from world coordinates
vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) { vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) {

View file

@ -11,10 +11,8 @@
struct ShadowTransform { struct ShadowTransform {
MAT4 reprojection; MAT4 reprojection;
VEC3 frustumPosition;
float fixedBias; float fixedBias;
float adaptiveBiasUnitScale; float slopeBias;
float adaptiveBiasTransformScale;
float _padding1; float _padding1;
float _padding2; float _padding2;
}; };

View file

@ -14,7 +14,7 @@ var qml = Script.resolvePath('shadow.qml');
var window = new OverlayWindow({ var window = new OverlayWindow({
title: 'Shadow Debug', title: 'Shadow Debug',
source: qml, source: qml,
width: 200, width: 250,
height: 90 height: 300
}); });
window.closed.connect(function() { Script.stop(); }); window.closed.connect(function() { Script.stop(); });

View file

@ -36,6 +36,14 @@ Column {
shadow1Config.enabled = false; shadow1Config.enabled = false;
shadow2Config.enabled = false; shadow2Config.enabled = false;
shadow3Config.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 { CheckBox {
@ -72,35 +80,68 @@ Column {
} }
} }
ConfigSlider { ConfigSlider {
label: qsTr("Cascade 0 bias") label: qsTr("Cascade 0 fixed bias")
integral: false integral: false
config: Render.getConfig("RenderMainView.ShadowCascadeSetup0") config: Render.getConfig("RenderMainView.ShadowCascadeSetup0")
property: "bias" property: "fixedBias"
max: 1.0 max: 1.0
min: 0.01 min: 0.0
} }
ConfigSlider { ConfigSlider {
label: qsTr("Cascade 1 bias") label: qsTr("Cascade 1 fixed bias")
integral: false integral: false
config: Render.getConfig("RenderMainView.ShadowCascadeSetup1") config: Render.getConfig("RenderMainView.ShadowCascadeSetup1")
property: "bias" property: "fixedBias"
max: 1.0 max: 1.0
min: 0.01 min: 0.0
} }
ConfigSlider { ConfigSlider {
label: qsTr("Cascade 2 bias") label: qsTr("Cascade 2 fixed bias")
integral: false integral: false
config: Render.getConfig("RenderMainView.ShadowCascadeSetup2") config: Render.getConfig("RenderMainView.ShadowCascadeSetup2")
property: "bias" property: "fixedBias"
max: 1.0 max: 1.0
min: 0.01 min: 0.0
} }
ConfigSlider { ConfigSlider {
label: qsTr("Cascade 3 bias") label: qsTr("Cascade 3 fixed bias")
integral: false integral: false
config: Render.getConfig("RenderMainView.ShadowCascadeSetup3") config: Render.getConfig("RenderMainView.ShadowCascadeSetup3")
property: "bias" property: "fixedBias"
max: 1.0 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
} }
} }