Trying to improve adaptive shadow bias

This commit is contained in:
Olivier Prat 2018-01-29 17:23:35 +01:00
parent 6b0b17ff63
commit 0324f41565
7 changed files with 58 additions and 23 deletions

View file

@ -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<ViewFrustum>() },
_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<Schema>();
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>();
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>();
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<Schema>().cascades[cascadeIndex].reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();
auto& schema = _schemaBuffer.edit<Schema>();
auto& schemaCascade = schema.cascades[cascadeIndex];
schemaCascade.reprojection = _biasMatrix * shadowFrustum.getProjection() * viewInverse.getMatrix();
schema.updateCascade(cascadeIndex, shadowFrustum);
}
LightStage::Index LightStage::findLight(const LightPointer& light) const {

View file

@ -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<LightStage>;

View file

@ -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) {

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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;