Added normal based adaptive depth bias

This commit is contained in:
Olivier Prat 2017-12-06 17:06:48 +01:00
parent c9c93370da
commit 014e81b2f4
6 changed files with 39 additions and 26 deletions

View file

@ -36,7 +36,7 @@ LightStage::Shadow::Schema::Schema() {
std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform);
invMapSize = 1.0f / MAP_SIZE;
cascadeCount = 1;
invCascadeBlendWidth = 1.0f / 0.1f;
invCascadeBlendWidth = 1.0f / 0.2f;
invFalloffDistance = 1.0f / 2.0f;
maxDistance = 20.0f;
}
@ -104,7 +104,10 @@ LightStage::Shadow::Shadow(model::LightPointer light, float maxDistance, unsigne
}
void LightStage::Shadow::setMaxDistance(float value) {
static const auto OVERLAP_FACTOR = 1.0f / 4.0f;
// This overlaping factor isn't really used directly for blending of shadow cascades. It
// just there to be sure the cascades do overlap. The blending width used is relative
// to the UV space and is set in the Schema with invCascadeBlendWidth.
static const auto OVERLAP_FACTOR = 1.0f / 5.0f;
_maxDistance = std::max(0.0f, value);
@ -114,7 +117,7 @@ void LightStage::Shadow::setMaxDistance(float value) {
} else {
// Distribute the cascades along that distance
// TODO : these parameters should be exposed to the user as part of the light entity parameters, no?
static const auto LOW_MAX_DISTANCE = 1.5f;
static const auto LOW_MAX_DISTANCE = 2.0f;
static const auto MAX_RESOLUTION_LOSS = 0.6f; // Between 0 and 1, 0 giving tighter distributions
// The max cascade distance is computed by multiplying the previous cascade's max distance by a certain
@ -135,10 +138,10 @@ void LightStage::Shadow::setMaxDistance(float value) {
if (cascadeIndex == _cascades.size() - 1) {
maxCascadeDistance = _maxDistance;
} else {
maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor;
maxCascadeDistance = maxCascadeUserDistance + (maxCascadeOptimalDistance - maxCascadeUserDistance)*blendFactor*blendFactor;
}
float shadowOverlapDistance = (maxCascadeDistance - minCascadeDistance) * OVERLAP_FACTOR;
float shadowOverlapDistance = maxCascadeDistance * OVERLAP_FACTOR;
_cascades[cascadeIndex].setMinDistance(minCascadeDistance);
_cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);
@ -155,7 +158,7 @@ void LightStage::Shadow::setMaxDistance(float value) {
const auto& lastCascade = _cascades.back();
auto& schema = _schemaBuffer.edit<Schema>();
schema.maxDistance = _maxDistance;
schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*(lastCascade.getMaxDistance() - lastCascade.getMinDistance()));
schema.invFalloffDistance = 1.0f / (OVERLAP_FACTOR*lastCascade.getMaxDistance());
}
void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
@ -164,16 +167,16 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
assert(cascadeIndex < _cascades.size());
// Orient the keylight frustum
const auto& direction = glm::normalize(_light->getDirection());
const auto& lightDirection = glm::normalize(_light->getDirection());
glm::quat orientation;
if (direction == IDENTITY_UP) {
if (lightDirection == IDENTITY_UP) {
orientation = glm::quat(glm::mat3(-IDENTITY_RIGHT, IDENTITY_FORWARD, -IDENTITY_UP));
} else if (direction == -IDENTITY_UP) {
} else if (lightDirection == -IDENTITY_UP) {
orientation = glm::quat(glm::mat3(IDENTITY_RIGHT, IDENTITY_FORWARD, IDENTITY_UP));
} else {
auto side = glm::normalize(glm::cross(direction, IDENTITY_UP));
auto up = glm::normalize(glm::cross(side, direction));
orientation = glm::quat_cast(glm::mat3(side, up, -direction));
auto side = glm::normalize(glm::cross(lightDirection, IDENTITY_UP));
auto up = glm::normalize(glm::cross(side, lightDirection));
orientation = glm::quat_cast(glm::mat3(side, up, -lightDirection));
}
auto& cascade = _cascades[cascadeIndex];
@ -184,7 +187,7 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
cascade._frustum->setOrientation(orientation);
// Position the keylight frustum
cascade._frustum->setPosition(viewFrustum.getPosition() - (nearDepth + farDepth)*direction);
cascade._frustum->setPosition(viewFrustum.getPosition() - (nearDepth + farDepth)*lightDirection);
const Transform shadowView{ cascade._frustum->getView()};
const Transform shadowViewInverse{ shadowView.getInverseMatrix() };
@ -229,9 +232,12 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix();
// Adapt shadow bias to shadow resolution with a totally empirical formula
const auto maxShadowFrustumDim = std::max(fabsf(min.x - max.x), fabsf(min.y - max.y));
const auto REFERENCE_TEXEL_DENSITY = 12.0f;
const auto REFERENCE_TEXEL_DENSITY = 7.5f;
const auto cascadeTexelDensity = MAP_SIZE / maxShadowFrustumDim;
schema.cascades[cascadeIndex].bias = MAX_BIAS * std::min(1.0f, REFERENCE_TEXEL_DENSITY / cascadeTexelDensity);
// TODO: this isn't the best place to do this as this is common for all cascades and this is called for
// each cascade at each frame.
schema.lightDirInViewSpace = Transform(Transform(viewFrustum.getView()).getInverseMatrix()).transformDirection(lightDirection);
}
void LightStage::Shadow::setFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) {

View file

@ -71,8 +71,8 @@ ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) {
return offsets;
}
float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord) {
float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) {
shadowTexcoord.z -= bias;
float shadowAttenuation = 0.25 * (
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) +
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) +
@ -83,24 +83,27 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve
return shadowAttenuation;
}
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord) {
float evalShadowCascadeAttenuation(int cascadeIndex, vec3 viewNormal, ShadowSampleOffsets offsets, vec4 shadowTexcoord) {
if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) {
// If a point is not in the map, do not attenuate
return 1.0;
}
return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord);
// Multiply bias if we are at a grazing angle with light
float tangentFactor = abs(dot(getShadowDirInViewSpace(), viewNormal));
float bias = getShadowBias(cascadeIndex) * (5.0-4.0*tangentFactor);
return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias);
}
float evalShadowAttenuation(vec4 worldPosition, float viewDepth) {
float evalShadowAttenuation(vec4 worldPosition, float viewDepth, vec3 viewNormal) {
ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
vec4 cascadeShadowCoords[2];
ivec2 cascadeIndices;
float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
vec2 cascadeAttenuations = vec2(1.0, 1.0);
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0]);
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, viewNormal, offsets, cascadeShadowCoords[0]);
if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1]);
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, viewNormal, offsets, cascadeShadowCoords[1]);
}
float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
// Falloff to max distance

View file

@ -41,11 +41,14 @@ float getShadowBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].bias;
}
vec3 getShadowDirInViewSpace() {
return shadow.lightDirInViewSpace;
}
// Compute the texture coordinates from world coordinates
vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) {
vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position;
float bias = getShadowBias(cascadeIndex);
return vec4(shadowCoord.xy, shadowCoord.z - bias, 1.0);
return vec4(shadowCoord.xyz, 1.0);
}
bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) {

View file

@ -4,7 +4,7 @@
# define VEC3 glm::vec3
#else
# define MAT4 mat4
# define VEC3 ve3
# define VEC3 vec3
#endif
#define SHADOW_CASCADE_MAX_COUNT 4
@ -20,6 +20,7 @@ struct ShadowTransform {
struct ShadowParameters {
ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT];
VEC3 lightDirInViewSpace;
int cascadeCount;
float invMapSize;
float invCascadeBlendWidth;

View file

@ -28,7 +28,7 @@ void main(void) {
vec4 viewPos = vec4(frag.position.xyz, 1.0);
vec4 worldPos = getViewInverse() * viewPos;
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z);
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
if (frag.mode == FRAG_MODE_UNLIT) {
discard;

View file

@ -28,7 +28,7 @@ void main(void) {
vec4 viewPos = vec4(frag.position.xyz, 1.0);
vec4 worldPos = getViewInverse() * viewPos;
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z);
float shadowAttenuation = evalShadowAttenuation(worldPos, -viewPos.z, frag.normal);
// Light mapped or not ?
if (frag.mode == FRAG_MODE_UNLIT) {