mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 07:13:57 +02:00
Much better cascade blend
This commit is contained in:
parent
917ce7165c
commit
b246c479e3
7 changed files with 74 additions and 75 deletions
|
@ -151,21 +151,23 @@ static const std::string DEFAULT_SHADOW_SHADER{
|
|||
};
|
||||
|
||||
static const std::string DEFAULT_SHADOW_CASCADE_SHADER{
|
||||
"vec3 cascadeColors[4] = vec3[4]( vec3(0,1,0), vec3(0,0,1), vec3(1,0,0), vec3(0,0,0) );"
|
||||
"vec3 cascadeColors[4] = vec3[4]( vec3(0,1,0), vec3(0,0,1), vec3(1,0,0), vec3(1) );"
|
||||
"vec4 getFragmentColor() {"
|
||||
" DeferredFrameTransform deferredTransform = getDeferredFrameTransform();"
|
||||
" DeferredFragment frag = unpackDeferredFragment(deferredTransform, uv);"
|
||||
" vec4 viewPosition = vec4(frag.position.xyz, 1.0);"
|
||||
" float viewDepth = -viewPosition.z;"
|
||||
" vec4 worldPosition = getViewInverse() * viewPosition;"
|
||||
" vec4 cascadeShadowCoords[4] = vec4[4]("
|
||||
" evalShadowTexcoord(0, worldPosition),"
|
||||
" evalShadowTexcoord(1, worldPosition),"
|
||||
" evalShadowTexcoord(2, worldPosition),"
|
||||
" evalShadowTexcoord(3, worldPosition)"
|
||||
" );"
|
||||
" vec4 cascadeShadowCoords[2];"
|
||||
" ivec2 cascadeIndices;"
|
||||
" float cascadeMix = evalCascadeIndicesAndMix(-viewPosition.z, cascadeShadowCoords, cascadeIndices);"
|
||||
" return vec4(mix(cascadeColors[cascadeIndices.x], cascadeColors[cascadeIndices.y], cascadeMix), 1.0);"
|
||||
" float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);"
|
||||
" vec3 firstCascadeColor = cascadeColors[cascadeIndices.x];"
|
||||
" vec3 secondCascadeColor = cascadeColors[cascadeIndices.x];"
|
||||
" if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {"
|
||||
" secondCascadeColor = cascadeColors[cascadeIndices.y];"
|
||||
" }"
|
||||
" vec3 color = mix(firstCascadeColor, secondCascadeColor, cascadeMix);"
|
||||
" return vec4(mix(vec3(0.0), color, evalShadowFalloff(viewDepth)), 1.0);"
|
||||
"}"
|
||||
};
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ 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;
|
||||
invFalloffDistance = 1.0f / 2.0f;
|
||||
maxDistance = 20.0f;
|
||||
}
|
||||
|
||||
LightStage::Shadow::Cascade::Cascade() : _frustum{ std::make_shared<ViewFrustum>() } {
|
||||
|
@ -52,11 +55,10 @@ const glm::mat4& LightStage::Shadow::Cascade::getProjection() const {
|
|||
|
||||
LightStage::Shadow::Shadow(model::LightPointer light, unsigned int cascadeCount) : _light{ light } {
|
||||
cascadeCount = std::min(cascadeCount, (unsigned int)SHADOW_CASCADE_MAX_COUNT);
|
||||
|
||||
Schema schema;
|
||||
schema.cascadeCount = cascadeCount;
|
||||
_schemaBuffer = std::make_shared<gpu::Buffer>(sizeof(Schema), (const gpu::Byte*) &schema);
|
||||
_cascades.resize(cascadeCount);
|
||||
_schemaBuffer.edit<Schema>().cascadeCount = cascadeCount;
|
||||
}
|
||||
|
||||
void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum,
|
||||
|
@ -125,12 +127,12 @@ void LightStage::Shadow::setKeylightFrustum(unsigned int cascadeIndex, const Vie
|
|||
cascade._frustum->calculate();
|
||||
|
||||
// Update the buffer
|
||||
auto& schemaCascade = _schemaBuffer.edit<Schema>().cascades[cascadeIndex];
|
||||
schemaCascade.reprojection = _biasMatrix * ortho * viewInverse.getMatrix();
|
||||
schemaCascade.invTransitionWidth = 1.0f / viewOverlapDistance;
|
||||
schemaCascade.maxDistance = viewMaxShadowDistance;
|
||||
cascade.minDistance = viewMinShadowDistance;
|
||||
cascade.maxDistance = viewMaxShadowDistance;
|
||||
auto& schema = _schemaBuffer.edit<Schema>();
|
||||
if (cascadeIndex == getCascadeCount() - 1) {
|
||||
schema.maxDistance = viewMaxShadowDistance;
|
||||
schema.invFalloffDistance = 1.0f / viewOverlapDistance;
|
||||
}
|
||||
schema.cascades[cascadeIndex].reprojection = _biasMatrix * ortho * viewInverse.getMatrix();
|
||||
}
|
||||
|
||||
void LightStage::Shadow::setFrustum(unsigned int cascadeIndex, const ViewFrustum& shadowFrustum) {
|
||||
|
|
|
@ -56,8 +56,6 @@ public:
|
|||
|
||||
gpu::FramebufferPointer framebuffer;
|
||||
gpu::TexturePointer map;
|
||||
float minDistance;
|
||||
float maxDistance;
|
||||
|
||||
const std::shared_ptr<ViewFrustum>& getFrustum() const { return _frustum; }
|
||||
|
||||
|
|
|
@ -258,7 +258,6 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O
|
|||
|
||||
const auto nearClip = args->getViewFrustum().getNearClip();
|
||||
const auto farClip = args->getViewFrustum().getFarClip();
|
||||
const auto nearDepth = -args->_boomOffset.z;
|
||||
|
||||
static const float HIGH_CASCADE_MAX_DISTANCE = 20.0f;
|
||||
float maxCascadeDistance = HIGH_CASCADE_MAX_DISTANCE;
|
||||
|
@ -266,7 +265,7 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O
|
|||
float shadowOverlapDistance = 0.0f;
|
||||
|
||||
if (globalShadow->getCascadeCount() > 1) {
|
||||
static const float LOW_CASCADE_MAX_DISTANCE = 2.0f;
|
||||
static const float LOW_CASCADE_MAX_DISTANCE = 3.0f;
|
||||
const float cascadeLevelScale = powf(HIGH_CASCADE_MAX_DISTANCE / LOW_CASCADE_MAX_DISTANCE, 1.0f / (globalShadow->getCascadeCount() - 1));
|
||||
|
||||
maxCascadeDistance = HIGH_CASCADE_MAX_DISTANCE / powf(cascadeLevelScale, globalShadow->getCascadeCount() - 1 - _cascadeIndex);
|
||||
|
@ -276,9 +275,9 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O
|
|||
shadowOverlapDistance = (maxCascadeDistance - minCascadeDistance) / 3.0f;
|
||||
maxCascadeDistance += shadowOverlapDistance;
|
||||
if (_cascadeIndex == 0) {
|
||||
minCascadeDistance = nearDepth;
|
||||
minCascadeDistance = nearClip;
|
||||
}
|
||||
minCascadeDistance = std::max(minCascadeDistance, nearDepth);
|
||||
minCascadeDistance = std::max(minCascadeDistance, nearClip);
|
||||
maxCascadeDistance = std::min(maxCascadeDistance, farClip);
|
||||
globalShadow->setKeylightFrustum(_cascadeIndex, args->getViewFrustum(), minCascadeDistance, maxCascadeDistance, shadowOverlapDistance, SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR);
|
||||
|
||||
|
|
|
@ -84,9 +84,7 @@ float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, ve
|
|||
}
|
||||
|
||||
float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord) {
|
||||
if (shadowTexcoord.x < 0.0 || shadowTexcoord.x > 1.0 ||
|
||||
shadowTexcoord.y < 0.0 || shadowTexcoord.y > 1.0 ||
|
||||
shadowTexcoord.z < 0.0 || shadowTexcoord.z > 1.0) {
|
||||
if (!isShadowCascadeProjectedOnPixel(shadowTexcoord)) {
|
||||
// If a point is not in the map, do not attenuate
|
||||
return 1.0;
|
||||
}
|
||||
|
@ -95,21 +93,18 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets
|
|||
|
||||
float evalShadowAttenuation(vec4 worldPosition, float viewDepth) {
|
||||
ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
|
||||
vec4 cascadeShadowCoords[4] = vec4[4] (
|
||||
evalShadowTexcoord(0, worldPosition),
|
||||
evalShadowTexcoord(1, worldPosition),
|
||||
evalShadowTexcoord(2, worldPosition),
|
||||
evalShadowTexcoord(3, worldPosition)
|
||||
);
|
||||
vec4 cascadeShadowCoords[2];
|
||||
ivec2 cascadeIndices;
|
||||
float cascadeMix = evalCascadeIndicesAndMix(viewDepth, cascadeShadowCoords, cascadeIndices);
|
||||
float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
|
||||
|
||||
vec2 cascadeAttenuations = vec2(1.0, 1.0);
|
||||
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[cascadeIndices.x]);
|
||||
cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0]);
|
||||
if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) {
|
||||
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[cascadeIndices.y]);
|
||||
cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1]);
|
||||
}
|
||||
return mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
|
||||
float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix);
|
||||
// Falloff to max distance
|
||||
return mix(1.0, attenuation, evalShadowFalloff(viewDepth));
|
||||
}
|
||||
|
||||
<@endif@>
|
||||
|
|
|
@ -21,12 +21,12 @@ int getShadowCascadeCount() {
|
|||
return shadow.cascadeCount;
|
||||
}
|
||||
|
||||
float getShadowCascadeInvTransitionWidth(int cascadeIndex) {
|
||||
return shadow.cascades[cascadeIndex].invTransitionWidth;
|
||||
float getShadowCascadeInvBlendWidth() {
|
||||
return shadow.invCascadeBlendWidth;
|
||||
}
|
||||
|
||||
float getShadowCascadeMaxDistance(int cascadeIndex) {
|
||||
return shadow.cascades[cascadeIndex].maxDistance;
|
||||
float evalShadowFalloff(float depth) {
|
||||
return clamp((shadow.maxDistance-depth) * shadow.invFalloffDistance, 0.0, 1.0);
|
||||
}
|
||||
|
||||
mat4 getShadowReprojection(int cascadeIndex) {
|
||||
|
@ -48,44 +48,46 @@ vec4 evalShadowTexcoord(int cascadeIndex, vec4 position) {
|
|||
return vec4(shadowCoord.xy, shadowCoord.z - bias, 1.0);
|
||||
}
|
||||
|
||||
int getFirstValidShadowTexcoord(vec4 cascadeShadowCoords[4]) {
|
||||
bool isShadowCascadeProjectedOnPixel(vec4 cascadeTexCoords) {
|
||||
bvec3 greaterThanZero = greaterThanEqual(cascadeTexCoords.xyz, vec3(0));
|
||||
bvec3 lessThanOne = lessThanEqual(cascadeTexCoords.xyz, vec3(1));
|
||||
return all(greaterThanZero) && all(lessThanOne);
|
||||
}
|
||||
|
||||
int getFirstShadowCascadeOnPixel(int startCascadeIndex, vec4 worldPosition, out vec4 cascadeShadowCoords) {
|
||||
int cascadeIndex;
|
||||
for (cascadeIndex=0 ; cascadeIndex<getShadowCascadeCount()-1 ; cascadeIndex++) {
|
||||
vec4 shadowTexcoord = cascadeShadowCoords[cascadeIndex];
|
||||
bvec2 greaterThanZero = greaterThanEqual(shadowTexcoord.xy, vec2(0));
|
||||
bvec2 lessThanOne = lessThanEqual(shadowTexcoord.xy, vec2(1));
|
||||
if (all(greaterThanZero) && all(lessThanOne)) {
|
||||
startCascadeIndex = min(startCascadeIndex, getShadowCascadeCount()-1);
|
||||
for (cascadeIndex=startCascadeIndex ; cascadeIndex<getShadowCascadeCount() ; cascadeIndex++) {
|
||||
cascadeShadowCoords = evalShadowTexcoord(cascadeIndex, worldPosition);
|
||||
if (isShadowCascadeProjectedOnPixel(cascadeShadowCoords)) {
|
||||
return cascadeIndex;
|
||||
}
|
||||
}
|
||||
return cascadeIndex;
|
||||
}
|
||||
|
||||
float evalCascadeIndicesAndMix(float viewDepth, vec4 cascadeShadowCoords[4], out ivec2 cascadeIndices) {
|
||||
int cascadeCount = getShadowCascadeCount();
|
||||
#if 0
|
||||
// Cascade selection based on :
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee416307(v=vs.85).aspx
|
||||
vec4 cascadeDepthLimits = vec4(
|
||||
getShadowCascadeMinDistance(0),
|
||||
getShadowCascadeMinDistance(1),
|
||||
getShadowCascadeMinDistance(2),
|
||||
getShadowCascadeMinDistance(3)
|
||||
);
|
||||
bvec4 comparison = greaterThan( vec4(viewDepth), cascadeDepthLimits);
|
||||
bvec4 cascadeCountMask = greaterThan(ivec4(cascadeCount), ivec4(0,1,2,3));
|
||||
int cascadeIndex = int(dot(ivec4(cascadeCountMask), ivec4(comparison)));
|
||||
cascadeIndex = min( cascadeIndex, cascadeCount-1 );
|
||||
#else
|
||||
int cascadeIndex = getFirstValidShadowTexcoord(cascadeShadowCoords);
|
||||
#endif
|
||||
float evalShadowCascadeWeight(vec4 cascadeTexCoords) {
|
||||
// Inspired by DirectX CascadeShadowMaps11 example
|
||||
vec2 distanceToOne = vec2(1.0) - cascadeTexCoords.xy;
|
||||
float blend1 = min( cascadeTexCoords.x, cascadeTexCoords.y );
|
||||
float blend2 = min( distanceToOne.x, distanceToOne.y );
|
||||
float blend = min( blend1, blend2 );
|
||||
return clamp(blend * getShadowCascadeInvBlendWidth(), 0.0, 1.0);
|
||||
}
|
||||
|
||||
cascadeIndices.x = cascadeIndex;
|
||||
cascadeIndices.y = cascadeIndex+1;
|
||||
float determineShadowCascadesOnPixel(vec4 worldPosition, float viewDepth, out vec4 cascadeShadowCoords[2], out ivec2 cascadeIndices) {
|
||||
cascadeIndices.x = getFirstShadowCascadeOnPixel(0, worldPosition, cascadeShadowCoords[0]);
|
||||
cascadeIndices.y = cascadeIndices.x+1;
|
||||
if (cascadeIndices.x < (getShadowCascadeCount()-1)) {
|
||||
cascadeIndices.y = getFirstShadowCascadeOnPixel(cascadeIndices.y, worldPosition, cascadeShadowCoords[1]);
|
||||
|
||||
float maxDepth = getShadowCascadeMaxDistance(cascadeIndices.x);
|
||||
float cascadeMixRatio = (maxDepth-viewDepth) * getShadowCascadeInvTransitionWidth(cascadeIndices.x);
|
||||
return 1.0 - clamp(cascadeMixRatio, 0.0, 1.0);
|
||||
float firstCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[0]);
|
||||
float secondCascadeWeight = evalShadowCascadeWeight(cascadeShadowCoords[1]);
|
||||
// Returns the mix amount between first and second cascade.
|
||||
return ((1.0-firstCascadeWeight) * secondCascadeWeight) / (firstCascadeWeight + secondCascadeWeight);
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
<@endif@>
|
||||
|
|
|
@ -13,17 +13,18 @@ struct ShadowTransform {
|
|||
MAT4 reprojection;
|
||||
|
||||
float bias;
|
||||
float maxDistance;
|
||||
float invTransitionWidth;
|
||||
float _padding;
|
||||
float _padding1;
|
||||
float _padding2;
|
||||
float _padding3;
|
||||
};
|
||||
|
||||
struct ShadowParameters {
|
||||
ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT];
|
||||
int cascadeCount;
|
||||
float invMapSize;
|
||||
float _padding1;
|
||||
float _padding2;
|
||||
ShadowTransform cascades[SHADOW_CASCADE_MAX_COUNT];
|
||||
float invCascadeBlendWidth;
|
||||
float maxDistance;
|
||||
float invFalloffDistance;
|
||||
};
|
||||
|
||||
// <@if 1@>
|
||||
|
|
Loading…
Reference in a new issue