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); std::fill(cascades, cascades + SHADOW_CASCADE_MAX_COUNT, defaultTransform);
invMapSize = 1.0f / MAP_SIZE; invMapSize = 1.0f / MAP_SIZE;
cascadeCount = 1; cascadeCount = 1;
invCascadeBlendWidth = 1.0f / 0.1f; invCascadeBlendWidth = 1.0f / 0.2f;
invFalloffDistance = 1.0f / 2.0f; invFalloffDistance = 1.0f / 2.0f;
maxDistance = 20.0f; maxDistance = 20.0f;
} }
@ -104,7 +104,10 @@ LightStage::Shadow::Shadow(model::LightPointer light, float maxDistance, unsigne
} }
void LightStage::Shadow::setMaxDistance(float value) { 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); _maxDistance = std::max(0.0f, value);
@ -114,7 +117,7 @@ void LightStage::Shadow::setMaxDistance(float value) {
} else { } else {
// Distribute the cascades along that distance // Distribute the cascades along that distance
// TODO : these parameters should be exposed to the user as part of the light entity parameters, no? // 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 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 // 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) { if (cascadeIndex == _cascades.size() - 1) {
maxCascadeDistance = _maxDistance; maxCascadeDistance = _maxDistance;
} else { } 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].setMinDistance(minCascadeDistance);
_cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance); _cascades[cascadeIndex].setMaxDistance(maxCascadeDistance + shadowOverlapDistance);
@ -155,7 +158,7 @@ void LightStage::Shadow::setMaxDistance(float value) {
const auto& lastCascade = _cascades.back(); const auto& lastCascade = _cascades.back();
auto& schema = _schemaBuffer.edit<Schema>(); auto& schema = _schemaBuffer.edit<Schema>();
schema.maxDistance = _maxDistance; 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, 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()); assert(cascadeIndex < _cascades.size());
// Orient the keylight frustum // Orient the keylight frustum
const auto& direction = glm::normalize(_light->getDirection()); const auto& lightDirection = glm::normalize(_light->getDirection());
glm::quat orientation; glm::quat orientation;
if (direction == IDENTITY_UP) { if (lightDirection == IDENTITY_UP) {
orientation = glm::quat(glm::mat3(-IDENTITY_RIGHT, IDENTITY_FORWARD, -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)); orientation = glm::quat(glm::mat3(IDENTITY_RIGHT, IDENTITY_FORWARD, IDENTITY_UP));
} else { } else {
auto side = glm::normalize(glm::cross(direction, IDENTITY_UP)); auto side = glm::normalize(glm::cross(lightDirection, IDENTITY_UP));
auto up = glm::normalize(glm::cross(side, direction)); auto up = glm::normalize(glm::cross(side, lightDirection));
orientation = glm::quat_cast(glm::mat3(side, up, -direction)); orientation = glm::quat_cast(glm::mat3(side, up, -lightDirection));
} }
auto& cascade = _cascades[cascadeIndex]; auto& cascade = _cascades[cascadeIndex];
@ -184,7 +187,7 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
cascade._frustum->setOrientation(orientation); cascade._frustum->setOrientation(orientation);
// Position the keylight frustum // 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 shadowView{ cascade._frustum->getView()};
const Transform shadowViewInverse{ shadowView.getInverseMatrix() }; 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(); schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * shadowViewInverse.getMatrix();
// Adapt shadow bias to shadow resolution with a totally empirical formula // 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 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; const auto cascadeTexelDensity = MAP_SIZE / maxShadowFrustumDim;
schema.cascades[cascadeIndex].bias = MAX_BIAS * std::min(1.0f, REFERENCE_TEXEL_DENSITY / cascadeTexelDensity); 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) { void LightStage::Shadow::setFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) {

View file

@ -71,8 +71,8 @@ ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) {
return offsets; 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 * ( float shadowAttenuation = 0.25 * (
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) +
fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) +
@ -83,24 +83,27 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve
return shadowAttenuation; return shadowAttenuation;
} }
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord) { float evalShadowCascadeAttenuation(int cascadeIndex, vec3 viewNormal, ShadowSampleOffsets offsets, vec4 shadowTexcoord) {
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;
} }
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); ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
vec4 cascadeShadowCoords[2]; vec4 cascadeShadowCoords[2];
ivec2 cascadeIndices; ivec2 cascadeIndices;
float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
vec2 cascadeAttenuations = vec2(1.0, 1.0); 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()) { 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); float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
// Falloff to max distance // Falloff to max distance

View file

@ -41,11 +41,14 @@ float getShadowBias(int cascadeIndex) {
return shadow.cascades[cascadeIndex].bias; return shadow.cascades[cascadeIndex].bias;
} }
vec3 getShadowDirInViewSpace() {
return shadow.lightDirInViewSpace;
}
// 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) {
vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position; vec4 shadowCoord = getShadowReprojection(cascadeIndex) * position;
float bias = getShadowBias(cascadeIndex); return vec4(shadowCoord.xyz, 1.0);
return vec4(shadowCoord.xy, shadowCoord.z - bias, 1.0);
} }
bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) { bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) {

View file

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

View file

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

View file

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