Add smooth shadows blending between directional light cascades
It was deprecated in 1.9 in favor for dithering between cascades. Bing back that option for games that don't use TAA.
This commit is contained in:
BIN
Content/Shaders/Shadows.flax
(Stored with Git LFS)
BIN
Content/Shaders/Shadows.flax
(Stored with Git LFS)
Binary file not shown.
@@ -61,10 +61,9 @@ public:
|
||||
|
||||
/// <summary>
|
||||
/// Enables cascades splits blending for directional light shadows.
|
||||
/// [Deprecated in v1.9]
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")")
|
||||
DEPRECATED() bool AllowCSMBlending = false;
|
||||
bool AllowCSMBlending = false;
|
||||
|
||||
/// <summary>
|
||||
/// Default probes cubemap resolution (use for Environment Probes, can be overriden per-actor).
|
||||
|
||||
@@ -69,6 +69,7 @@ void GraphicsSettings::Apply()
|
||||
Graphics::VolumetricFogQuality = VolumetricFogQuality;
|
||||
Graphics::ShadowsQuality = ShadowsQuality;
|
||||
Graphics::ShadowMapsQuality = ShadowMapsQuality;
|
||||
Graphics::AllowCSMBlending = AllowCSMBlending;
|
||||
Graphics::GlobalSDFQuality = GlobalSDFQuality;
|
||||
Graphics::GIQuality = GIQuality;
|
||||
Graphics::GICascadesBlending = GICascadesBlending;
|
||||
|
||||
@@ -55,9 +55,8 @@ public:
|
||||
|
||||
/// <summary>
|
||||
/// Enables cascades splits blending for directional light shadows.
|
||||
/// [Deprecated in v1.9]
|
||||
/// </summary>
|
||||
API_FIELD() DEPRECATED() static bool AllowCSMBlending;
|
||||
API_FIELD() static bool AllowCSMBlending;
|
||||
|
||||
/// <summary>
|
||||
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#define SHADOWS_MAX_TILES 6
|
||||
#define SHADOWS_MIN_RESOLUTION 32
|
||||
#define SHADOWS_MAX_STATIC_ATLAS_CAPACITY_TO_DEFRAG 0.7f
|
||||
#define SHADOWS_BASE_LIGHT_RESOLUTION(atlasResolution) atlasResolution / MAX_CSM_CASCADES // Allow to store 4 CSM cascades in a single row in all cases
|
||||
#define SHADOWS_BASE_LIGHT_RESOLUTION(atlasResolution) (atlasResolution / MAX_CSM_CASCADES) // Allow to store 4 CSM cascades in a single row in all cases
|
||||
#define NormalOffsetScaleTweak METERS_TO_UNITS(1)
|
||||
#define LocalLightNearPlane METERS_TO_UNITS(0.1f)
|
||||
|
||||
@@ -190,6 +190,7 @@ struct ShadowAtlasLight
|
||||
uint8 TilesNeeded;
|
||||
uint8 TilesCount;
|
||||
bool HasStaticShadowContext;
|
||||
bool BlendCSM;
|
||||
mutable StaticStates StaticState;
|
||||
BoundingSphere Bounds;
|
||||
float Sharpness, Fade, NormalOffsetScale, Bias, FadeDistance, Distance, TileBorder;
|
||||
@@ -769,6 +770,15 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
|
||||
const RenderView& view = renderContext.View;
|
||||
const int32 csmCount = atlasLight.TilesCount;
|
||||
const auto shadowMapsSize = (float)atlasLight.Resolution;
|
||||
atlasLight.BlendCSM = Graphics::AllowCSMBlending;
|
||||
#if USE_EDITOR
|
||||
// Disable cascades blending when baking lightmaps
|
||||
if (IsRunningRadiancePass)
|
||||
atlasLight.BlendCSM = false;
|
||||
#elif PLATFORM_SWITCH || PLATFORM_IOS || PLATFORM_ANDROID
|
||||
// Disable cascades blending on low-end platforms
|
||||
atlasLight.BlendCSM = false;
|
||||
#endif
|
||||
|
||||
// Calculate cascade splits
|
||||
const float minDistance = view.Near;
|
||||
@@ -895,7 +905,8 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
|
||||
Float3 frustumCornersVs[8];
|
||||
for (int32 j = 0; j < 4; j++)
|
||||
{
|
||||
float overlapWithPrevSplit = 0.1f * (splitMinRatio - oldSplitMinRatio); // CSM blending overlap
|
||||
float csmOverlap = atlasLight.BlendCSM ? 0.2f : 0.1f;
|
||||
float overlapWithPrevSplit = csmOverlap * (splitMinRatio - oldSplitMinRatio);
|
||||
const auto frustumRangeVS = frustumCorners[j + 4] - frustumCorners[j];
|
||||
frustumCornersVs[j] = frustumCorners[j] + frustumRangeVS * (splitMinRatio - overlapWithPrevSplit);
|
||||
frustumCornersVs[j + 4] = frustumCorners[j] + frustumRangeVS * splitMaxRatio;
|
||||
@@ -1602,7 +1613,9 @@ void ShadowsPass::RenderShadowMask(RenderContextBatch& renderContextBatch, Rende
|
||||
}
|
||||
else //if (light.IsDirectionalLight)
|
||||
{
|
||||
context->SetState(_psShadowDir.Get(permutationIndex));
|
||||
auto* atlasLight = shadows.Lights.TryGet(light.ID);
|
||||
ASSERT_LOW_LAYER(atlasLight);
|
||||
context->SetState(_psShadowDir.Get(permutationIndex + (atlasLight->BlendCSM ? 8 : 0)));
|
||||
context->DrawFullscreenTriangle();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ private:
|
||||
AssetReference<Model> _sphereModel;
|
||||
GPUPipelineState* _psDepthClear = nullptr;
|
||||
GPUPipelineState* _psDepthCopy = nullptr;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowDir;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowPoint;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowPointInside;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowSpot;
|
||||
GPUPipelineStatePermutationsPs<static_cast<int32>(Quality::MAX) * 2> _psShadowSpotInside;
|
||||
GPUPipelineStatePermutationsPs<int32(Quality::MAX) * 2 * 2> _psShadowDir;
|
||||
GPUPipelineStatePermutationsPs<int32(Quality::MAX) * 2> _psShadowPoint;
|
||||
GPUPipelineStatePermutationsPs<int32(Quality::MAX) * 2> _psShadowPointInside;
|
||||
GPUPipelineStatePermutationsPs<int32(Quality::MAX) * 2> _psShadowSpot;
|
||||
GPUPipelineStatePermutationsPs<int32(Quality::MAX) * 2> _psShadowSpotInside;
|
||||
PixelFormat _shadowMapFormat; // Cached on initialization
|
||||
|
||||
public:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#define USE_GBUFFER_CUSTOM_DATA
|
||||
#define SHADOWS_CSM_BLENDING 1
|
||||
#define SHADOWS_CSM_DITHERING 1
|
||||
|
||||
#include "./Flax/Common.hlsl"
|
||||
#include "./Flax/GBuffer.hlsl"
|
||||
@@ -102,14 +102,22 @@ float4 PS_PointLight(Model_VS2PS input) : SV_Target0
|
||||
|
||||
// Pixel shader for directional light shadow rendering
|
||||
META_PS(true, FEATURE_LEVEL_ES2)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=1,CONTACT_SHADOWS=0)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=0)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=0)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=0,CONTACT_SHADOWS=1)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=1,CONTACT_SHADOWS=1)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=2,CONTACT_SHADOWS=1)
|
||||
META_PERMUTATION_2(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=1,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=2,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=3,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=0,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=1,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=2,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=0)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=0,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=1,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=2,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=3,CONTACT_SHADOWS=0,SHADOWS_CSM_BLENDING=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=0,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=1,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=2,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=1)
|
||||
META_PERMUTATION_3(SHADOWS_QUALITY=3,CONTACT_SHADOWS=1,SHADOWS_CSM_BLENDING=1)
|
||||
float4 PS_DirLight(Quad_VS2PS input) : SV_Target0
|
||||
{
|
||||
// Sample GBuffer
|
||||
|
||||
@@ -3,10 +3,17 @@
|
||||
#ifndef __SHADOWS_SAMPLING__
|
||||
#define __SHADOWS_SAMPLING__
|
||||
|
||||
#ifndef SHADOWS_CSM_BLENDING
|
||||
#define SHADOWS_CSM_BLENDING 0
|
||||
#endif
|
||||
#ifndef SHADOWS_CSM_DITHERING
|
||||
#define SHADOWS_CSM_DITHERING 0
|
||||
#endif
|
||||
|
||||
#include "./Flax/ShadowsCommon.hlsl"
|
||||
#include "./Flax/GBufferCommon.hlsl"
|
||||
#include "./Flax/LightingCommon.hlsl"
|
||||
#ifdef SHADOWS_CSM_BLENDING
|
||||
#if SHADOWS_CSM_DITHERING
|
||||
#include "./Flax/Random.hlsl"
|
||||
#endif
|
||||
|
||||
@@ -204,6 +211,43 @@ float SampleShadowMapOptimizedPCF(Texture2D<float> shadowMap, float2 shadowMapUV
|
||||
#endif
|
||||
}
|
||||
|
||||
// Samples the shadow cascade for the given directional light on the material surface (supports subsurface shadowing)
|
||||
ShadowSample SampleDirectionalLightShadowCascade(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, ShadowData shadow, float3 samplePosition, uint cascadeIndex)
|
||||
{
|
||||
ShadowSample result;
|
||||
ShadowTileData shadowTile = LoadShadowsBufferTile(shadowsBuffer, light.ShadowsBufferAddress, cascadeIndex);
|
||||
|
||||
// Project position into shadow atlas UV
|
||||
float4 shadowPosition;
|
||||
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
|
||||
|
||||
// Sample shadow map
|
||||
result.SurfaceShadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapUV, shadowPosition.z);
|
||||
|
||||
// Increase the sharpness for higher cascades to match the filter radius
|
||||
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
|
||||
shadow.Sharpness *= SharpnessScale[cascadeIndex];
|
||||
|
||||
#if defined(USE_GBUFFER_CUSTOM_DATA)
|
||||
// Subsurface shadowing
|
||||
BRANCH
|
||||
if (IsSubsurfaceMode(gBuffer.ShadingModel))
|
||||
{
|
||||
float opacity = gBuffer.CustomData.a;
|
||||
shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, gBuffer.WorldPos, shadowPosition);
|
||||
float shadowMapDepth = shadowMap.SampleLevel(SAMPLE_SHADOW_MAP_SAMPLER, shadowMapUV, 0).r;
|
||||
result.TransmissionShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
|
||||
result.TransmissionShadow = PostProcessShadow(shadow, result.TransmissionShadow);
|
||||
}
|
||||
#else
|
||||
result.TransmissionShadow = 1;
|
||||
#endif
|
||||
|
||||
result.SurfaceShadow = PostProcessShadow(shadow, result.SurfaceShadow);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Samples the shadow for the given directional light on the material surface (supports subsurface shadowing)
|
||||
ShadowSample SampleDirectionalLightShadow(LightData light, Buffer<float4> shadowsBuffer, Texture2D<float> shadowMap, GBufferSample gBuffer, float dither = 0.0f)
|
||||
{
|
||||
@@ -242,53 +286,42 @@ ShadowSample SampleDirectionalLightShadow(LightData light, Buffer<float4> shadow
|
||||
if (viewDepth > shadow.CascadeSplits[i])
|
||||
cascadeIndex = i + 1;
|
||||
}
|
||||
#ifdef SHADOWS_CSM_BLENDING
|
||||
const float BlendThreshold = 0.05f;
|
||||
#if SHADOWS_CSM_DITHERING || SHADOWS_CSM_BLENDING
|
||||
float nextSplit = shadow.CascadeSplits[cascadeIndex];
|
||||
float splitSize = cascadeIndex == 0 ? nextSplit : nextSplit - shadow.CascadeSplits[cascadeIndex - 1];
|
||||
float splitDist = (nextSplit - viewDepth) / splitSize;
|
||||
#endif
|
||||
#if SHADOWS_CSM_DITHERING && !SHADOWS_CSM_BLENDING
|
||||
const float BlendThreshold = 0.05f;
|
||||
if (splitDist <= BlendThreshold && cascadeIndex != shadow.TilesCount - 1)
|
||||
{
|
||||
// Blend with the next cascade but with screen-space dithering (gets cleaned out by TAA)
|
||||
// Dither with the next cascade but with screen-space dithering (gets cleaned out by TAA)
|
||||
float lerpAmount = 1 - splitDist / BlendThreshold;
|
||||
if (step(RandN2(gBuffer.ViewPos.xy + dither).x, lerpAmount))
|
||||
cascadeIndex++;
|
||||
}
|
||||
#endif
|
||||
ShadowTileData shadowTile = LoadShadowsBufferTile(shadowsBuffer, light.ShadowsBufferAddress, cascadeIndex);
|
||||
|
||||
// Sample cascade
|
||||
float3 samplePosition = gBuffer.WorldPos;
|
||||
#if !LIGHTING_NO_DIRECTIONAL
|
||||
// Apply normal offset bias
|
||||
samplePosition += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
|
||||
#endif
|
||||
result = SampleDirectionalLightShadowCascade(light, shadowsBuffer, shadowMap, gBuffer, shadow, samplePosition, cascadeIndex);
|
||||
|
||||
// Project position into shadow atlas UV
|
||||
float4 shadowPosition;
|
||||
float2 shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, samplePosition, shadowPosition);
|
||||
|
||||
// Sample shadow map
|
||||
result.SurfaceShadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapUV, shadowPosition.z);
|
||||
|
||||
// Increase the sharpness for higher cascades to match the filter radius
|
||||
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
|
||||
shadow.Sharpness *= SharpnessScale[cascadeIndex];
|
||||
|
||||
#if defined(USE_GBUFFER_CUSTOM_DATA)
|
||||
// Subsurface shadowing
|
||||
BRANCH
|
||||
if (IsSubsurfaceMode(gBuffer.ShadingModel))
|
||||
{
|
||||
float opacity = gBuffer.CustomData.a;
|
||||
shadowMapUV = GetLightShadowAtlasUV(shadow, shadowTile, gBuffer.WorldPos, shadowPosition);
|
||||
float shadowMapDepth = shadowMap.SampleLevel(SAMPLE_SHADOW_MAP_SAMPLER, shadowMapUV, 0).r;
|
||||
result.TransmissionShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
|
||||
result.TransmissionShadow = PostProcessShadow(shadow, result.TransmissionShadow);
|
||||
}
|
||||
#if SHADOWS_CSM_BLENDING
|
||||
const float BlendThreshold = 0.1f;
|
||||
if (splitDist <= BlendThreshold && cascadeIndex != shadow.TilesCount - 1)
|
||||
{
|
||||
// Sample the next cascade, and blend between the two results to smooth the transition
|
||||
ShadowSample nextResult = SampleDirectionalLightShadowCascade(light, shadowsBuffer, shadowMap, gBuffer, shadow, samplePosition, cascadeIndex + 1);
|
||||
float blendAmount = splitDist / BlendThreshold;
|
||||
result.SurfaceShadow = lerp(nextResult.SurfaceShadow, result.SurfaceShadow, blendAmount);
|
||||
result.TransmissionShadow = lerp(nextResult.TransmissionShadow, result.TransmissionShadow, blendAmount);
|
||||
}
|
||||
#endif
|
||||
|
||||
result.SurfaceShadow = PostProcessShadow(shadow, result.SurfaceShadow);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user