mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 23:17:02 +02:00
Added normal based adaptive depth bias
This commit is contained in:
parent
c9c93370da
commit
014e81b2f4
6 changed files with 39 additions and 26 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue