diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 89dc729185..b49408a1f4 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -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.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) { diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index c4b7828688..a12dd0f4a4 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -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 diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 0a34cafe6f..9b3b086598 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -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) { diff --git a/libraries/render-utils/src/Shadows_shared.slh b/libraries/render-utils/src/Shadows_shared.slh index 2797f4e962..abb226421c 100644 --- a/libraries/render-utils/src/Shadows_shared.slh +++ b/libraries/render-utils/src/Shadows_shared.slh @@ -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; diff --git a/libraries/render-utils/src/directional_ambient_light_shadow.slf b/libraries/render-utils/src/directional_ambient_light_shadow.slf index b791c9297a..f7ea8c5966 100644 --- a/libraries/render-utils/src/directional_ambient_light_shadow.slf +++ b/libraries/render-utils/src/directional_ambient_light_shadow.slf @@ -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; diff --git a/libraries/render-utils/src/directional_skybox_light_shadow.slf b/libraries/render-utils/src/directional_skybox_light_shadow.slf index 2bccc68550..37c9ae7fba 100644 --- a/libraries/render-utils/src/directional_skybox_light_shadow.slf +++ b/libraries/render-utils/src/directional_skybox_light_shadow.slf @@ -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) {