724 lines
26 KiB
HLSL
724 lines
26 KiB
HLSL
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
#ifndef __SHADOWS_SAMPLING__
|
|
#define __SHADOWS_SAMPLING__
|
|
|
|
#include "./Flax/ShadowsCommon.hlsl"
|
|
#include "./Flax/GBufferCommon.hlsl"
|
|
#include "./Flax/LightingCommon.hlsl"
|
|
|
|
// Select shadows filter based on quality
|
|
// Supported sampling kernel sizes fo each shadowing technique:
|
|
// CSM: 2, 3, 5, 7, 9
|
|
// Cube: 2, 5, 12, 29
|
|
// Spot: 2, 5, 12, 29
|
|
#if SHADOWS_QUALITY == 0
|
|
|
|
#define FilterSizeCSM 2
|
|
#define FilterSizeCube 2
|
|
#define FilterSizeSpot 2
|
|
|
|
#elif SHADOWS_QUALITY == 1
|
|
|
|
#define FilterSizeCSM 3
|
|
#define FilterSizeCube 5
|
|
#define FilterSizeSpot 5
|
|
|
|
#elif SHADOWS_QUALITY == 2
|
|
|
|
#define FilterSizeCSM 5
|
|
#define FilterSizeCube 12
|
|
#define FilterSizeSpot 12
|
|
|
|
#else // SHADOWS_QUALITY == 3
|
|
|
|
#define FilterSizeCSM 7
|
|
#define FilterSizeCube 12
|
|
#define FilterSizeSpot 12
|
|
|
|
#endif
|
|
|
|
#if SHADOWS_QUALITY != 0
|
|
#include "./Flax/PCFKernels.hlsl"
|
|
#endif
|
|
|
|
// Gets the cube texture face index to use for shadow map sampling for the given view-to-light direction vector
|
|
// Where: direction = normalize(worldPosition - lightPosition)
|
|
int GetCubeFaceIndex(float3 direction)
|
|
{
|
|
int cubeFaceIndex;
|
|
float3 absDirection = abs(direction);
|
|
float maxDirection = max(absDirection.x, max(absDirection.y, absDirection.z));
|
|
if (maxDirection == absDirection.x)
|
|
cubeFaceIndex = absDirection.x == direction.x ? 0 : 1;
|
|
else if (maxDirection == absDirection.y)
|
|
cubeFaceIndex = absDirection.y == direction.y ? 2 : 3;
|
|
else
|
|
cubeFaceIndex = absDirection.z == direction.z ? 4 : 5;
|
|
return cubeFaceIndex;
|
|
}
|
|
|
|
// Samples the shadow map with a fixed-size PCF kernel optimized with GatherCmpRed.
|
|
// Uses code from "Fast Conventional Shadow Filtering" by Holger Gruen, in GPU Pro.
|
|
float SampleShadowMapFixedSizePCF(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPos, uint cascadeIndex)
|
|
{
|
|
#if FilterSizeCSM == 2
|
|
|
|
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
|
|
|
|
return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(shadowPos.xy, cascadeIndex), sceneDepth);
|
|
|
|
#else
|
|
|
|
return sceneDepth < shadowMap.SampleLevel(SamplerLinearClamp, float3(shadowPos.xy, cascadeIndex), 0).r;
|
|
|
|
#endif
|
|
|
|
#else
|
|
|
|
const int FS_2 = FilterSizeCSM / 2;
|
|
float2 tc = shadowPos.xy;
|
|
float4 s = 0.0f;
|
|
float2 stc = (shadowMapSize * tc.xy) + float2(0.5f, 0.5f);
|
|
float2 tcs = floor(stc);
|
|
float2 fc;
|
|
int row;
|
|
int col;
|
|
float4 v1[FS_2 + 1];
|
|
float2 v0[FS_2 + 1];
|
|
float3 baseUV = float3(tc.xy, cascadeIndex);
|
|
float2 shadowMapSizeInv = 1.0f / shadowMapSize;
|
|
|
|
fc.xy = stc - tcs;
|
|
tc.xy = tcs * shadowMapSizeInv;
|
|
|
|
// Loop over the rows
|
|
UNROLL
|
|
for (row = -FS_2; row <= FS_2; row += 2)
|
|
{
|
|
UNROLL
|
|
for (col = -FS_2; col <= FS_2; col += 2)
|
|
{
|
|
float value = CSMFilterWeights[row + FS_2][col + FS_2];
|
|
|
|
if (col > -FS_2)
|
|
value += CSMFilterWeights[row + FS_2][col + FS_2 - 1];
|
|
|
|
if (col < FS_2)
|
|
value += CSMFilterWeights[row + FS_2][col + FS_2 + 1];
|
|
|
|
if (row > -FS_2) {
|
|
value += CSMFilterWeights[row + FS_2 - 1][col + FS_2];
|
|
|
|
if (col < FS_2)
|
|
value += CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1];
|
|
|
|
if (col > -FS_2)
|
|
value += CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1];
|
|
}
|
|
|
|
if (value != 0.0f)
|
|
{
|
|
// Gather returns xyzw which is counter clockwise order starting with the sample to the lower left of the queried location
|
|
#if CAN_USE_GATHER
|
|
|
|
v1[(col + FS_2) / 2] = shadowMap.GatherCmp(ShadowSampler, baseUV, sceneDepth, int2(col, row));
|
|
|
|
#else
|
|
|
|
float4 gather;
|
|
|
|
gather.x = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(0, 1) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
|
|
gather.y = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(1, 1) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
|
|
gather.z = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(1, 0) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
|
|
gather.w = sceneDepth < shadowMap.SampleLevel(SamplerPointClamp, float3(tc.xy + float2(0, 0) * shadowMapSizeInv, cascadeIndex), 0, int2(col, row)).r;
|
|
|
|
v1[(col + FS_2) / 2] = gather;
|
|
|
|
#endif
|
|
}
|
|
else
|
|
v1[(col + FS_2) / 2] = 0.0f;
|
|
|
|
if (col == -FS_2)
|
|
{
|
|
s.x += (1.0f - fc.y) * (v1[0].w * (CSMFilterWeights[row + FS_2][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2] * fc.x)
|
|
+ v1[0].z * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2 + 1.0f])
|
|
+ CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
|
|
s.y += fc.y * (v1[0].x * (CSMFilterWeights[row + FS_2][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2] * fc.x)
|
|
+ v1[0].y * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2 + 1])
|
|
+ CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
|
|
if(row > -FS_2)
|
|
{
|
|
s.z += (1.0f - fc.y) * (v0[0].x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2] * fc.x)
|
|
+ v0[0].y * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])
|
|
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
|
|
s.w += fc.y * (v1[0].w * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2] * fc.x)
|
|
+ v1[0].z * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1])
|
|
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
|
|
}
|
|
}
|
|
else if (col == FS_2)
|
|
{
|
|
s.x += (1 - fc.y) * (v1[FS_2].w * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2]) + CSMFilterWeights[row + FS_2][col + FS_2])
|
|
+ v1[FS_2].z * fc.x * CSMFilterWeights[row + FS_2][col + FS_2]);
|
|
s.y += fc.y * (v1[FS_2].x * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2] ) + CSMFilterWeights[row + FS_2][col + FS_2])
|
|
+ v1[FS_2].y * fc.x * CSMFilterWeights[row + FS_2][col + FS_2]);
|
|
if(row > -FS_2) {
|
|
s.z += (1 - fc.y) * (v0[FS_2].x * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2])
|
|
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2])
|
|
+ v0[FS_2].y * fc.x * CSMFilterWeights[row + FS_2 - 1][col + FS_2]);
|
|
s.w += fc.y * (v1[FS_2].w * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2])
|
|
+ CSMFilterWeights[row + FS_2 - 1][col + FS_2])
|
|
+ v1[FS_2].z * fc.x * CSMFilterWeights[row + FS_2 - 1][col + FS_2]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s.x += (1 - fc.y) * (v1[(col + FS_2) / 2].w * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 1]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2 + 0] ) + CSMFilterWeights[row + FS_2][col + FS_2 + 0])
|
|
+ v1[(col + FS_2) / 2].z * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 0]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
|
|
s.y += fc.y * (v1[(col + FS_2) / 2].x * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2-1]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2][col + FS_2 + 0])
|
|
+ v1[(col + FS_2) / 2].y * (fc.x * (CSMFilterWeights[row + FS_2][col + FS_2 - 0]
|
|
- CSMFilterWeights[row + FS_2][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2][col + FS_2 + 1]));
|
|
if(row > -FS_2) {
|
|
s.z += (1 - fc.y) * (v0[(col + FS_2) / 2].x * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0])
|
|
+ v0[(col + FS_2) / 2].y * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 0]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
|
|
s.w += fc.y * (v1[(col + FS_2) / 2].w * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 1]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 0])
|
|
+ v1[(col + FS_2) / 2].z * (fc.x * (CSMFilterWeights[row + FS_2 - 1][col + FS_2 - 0]
|
|
- CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]) + CSMFilterWeights[row + FS_2 - 1][col + FS_2 + 1]));
|
|
}
|
|
}
|
|
|
|
if (row != FS_2)
|
|
v0[(col + FS_2) / 2] = v1[(col + FS_2) / 2].xy;
|
|
}
|
|
}
|
|
|
|
return dot(s, 1.0f) / CSMFilterWeightsSum;
|
|
|
|
#endif
|
|
}
|
|
|
|
// Helper function for SampleShadowMapOptimizedPCF
|
|
float SampleShadowMap(Texture2DArray shadowMap, float2 baseUv, float u, float v, float2 shadowMapSizeInv, uint cascadeIndex, float depth)
|
|
{
|
|
float2 uv = baseUv + float2(u, v) * shadowMapSizeInv;
|
|
return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(uv, cascadeIndex), depth);
|
|
}
|
|
|
|
// The method used in The Witness
|
|
float SampleShadowMapOptimizedPCF(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPos, uint cascadeIndex)
|
|
{
|
|
float2 uv = shadowPos.xy * shadowMapSize; // 1 unit - 1 texel
|
|
float2 shadowMapSizeInv = 1.0f / shadowMapSize;
|
|
|
|
float2 baseUv;
|
|
baseUv.x = floor(uv.x + 0.5);
|
|
baseUv.y = floor(uv.y + 0.5);
|
|
float s = (uv.x + 0.5 - baseUv.x);
|
|
float t = (uv.y + 0.5 - baseUv.y);
|
|
baseUv -= float2(0.5, 0.5);
|
|
baseUv *= shadowMapSizeInv;
|
|
|
|
float sum = 0;
|
|
|
|
#if FilterSizeCSM == 2
|
|
|
|
return shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, float3(shadowPos.xy, cascadeIndex), sceneDepth);
|
|
|
|
#elif FilterSizeCSM == 3
|
|
|
|
float uw0 = (3 - 2 * s);
|
|
float uw1 = (1 + 2 * s);
|
|
|
|
float u0 = (2 - s) / uw0 - 1;
|
|
float u1 = s / uw1 + 1;
|
|
|
|
float vw0 = (3 - 2 * t);
|
|
float vw1 = (1 + 2 * t);
|
|
|
|
float v0 = (2 - t) / vw0 - 1;
|
|
float v1 = t / vw1 + 1;
|
|
|
|
sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
return sum * 1.0f / 16;
|
|
|
|
#elif FilterSizeCSM == 5
|
|
|
|
float uw0 = (4 - 3 * s);
|
|
float uw1 = 7;
|
|
float uw2 = (1 + 3 * s);
|
|
|
|
float u0 = (3 - 2 * s) / uw0 - 2;
|
|
float u1 = (3 + s) / uw1;
|
|
float u2 = s / uw2 + 2;
|
|
|
|
float vw0 = (4 - 3 * t);
|
|
float vw1 = 7;
|
|
float vw2 = (1 + 3 * t);
|
|
|
|
float v0 = (3 - 2 * t) / vw0 - 2;
|
|
float v1 = (3 + t) / vw1;
|
|
float v2 = t / vw2 + 2;
|
|
|
|
sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw2 * vw0 * SampleShadowMap(shadowMap, baseUv, u2, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw2 * vw1 * SampleShadowMap(shadowMap, baseUv, u2, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
sum += uw0 * vw2 * SampleShadowMap(shadowMap, baseUv, u0, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw2 * SampleShadowMap(shadowMap, baseUv, u1, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw2 * vw2 * SampleShadowMap(shadowMap, baseUv, u2, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
return sum * 1.0f / 144;
|
|
|
|
#else // FilterSizeCSM == 7
|
|
|
|
float uw0 = (5 * s - 6);
|
|
float uw1 = (11 * s - 28);
|
|
float uw2 = -(11 * s + 17);
|
|
float uw3 = -(5 * s + 1);
|
|
|
|
float u0 = (4 * s - 5) / uw0 - 3;
|
|
float u1 = (4 * s - 16) / uw1 - 1;
|
|
float u2 = -(7 * s + 5) / uw2 + 1;
|
|
float u3 = -s / uw3 + 3;
|
|
|
|
float vw0 = (5 * t - 6);
|
|
float vw1 = (11 * t - 28);
|
|
float vw2 = -(11 * t + 17);
|
|
float vw3 = -(5 * t + 1);
|
|
|
|
float v0 = (4 * t - 5) / vw0 - 3;
|
|
float v1 = (4 * t - 16) / vw1 - 1;
|
|
float v2 = -(7 * t + 5) / vw2 + 1;
|
|
float v3 = -t / vw3 + 3;
|
|
|
|
sum += uw0 * vw0 * SampleShadowMap(shadowMap, baseUv, u0, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw0 * SampleShadowMap(shadowMap, baseUv, u1, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw2 * vw0 * SampleShadowMap(shadowMap, baseUv, u2, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw3 * vw0 * SampleShadowMap(shadowMap, baseUv, u3, v0, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
sum += uw0 * vw1 * SampleShadowMap(shadowMap, baseUv, u0, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw1 * SampleShadowMap(shadowMap, baseUv, u1, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw2 * vw1 * SampleShadowMap(shadowMap, baseUv, u2, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw3 * vw1 * SampleShadowMap(shadowMap, baseUv, u3, v1, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
sum += uw0 * vw2 * SampleShadowMap(shadowMap, baseUv, u0, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw2 * SampleShadowMap(shadowMap, baseUv, u1, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw2 * vw2 * SampleShadowMap(shadowMap, baseUv, u2, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw3 * vw2 * SampleShadowMap(shadowMap, baseUv, u3, v2, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
sum += uw0 * vw3 * SampleShadowMap(shadowMap, baseUv, u0, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw1 * vw3 * SampleShadowMap(shadowMap, baseUv, u1, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw2 * vw3 * SampleShadowMap(shadowMap, baseUv, u2, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
sum += uw3 * vw3 * SampleShadowMap(shadowMap, baseUv, u3, v3, shadowMapSizeInv, cascadeIndex, sceneDepth);
|
|
|
|
return sum * (1.0f / 2704);
|
|
|
|
#endif
|
|
}
|
|
|
|
// Samples the shadow from the shadow map cascade
|
|
float SampleShadowCascade(Texture2DArray shadowMap, float2 shadowMapSize, float sceneDepth, float2 shadowPosition, uint cascadeIndex)
|
|
{
|
|
float shadow = SampleShadowMapFixedSizePCF(shadowMap, shadowMapSize, sceneDepth, shadowPosition, cascadeIndex);
|
|
//float shadow = SampleShadowMapOptimizedPCF(shadowMap, shadowMapSize, sceneDepth, shadowPosition, cascadeIndex);
|
|
return shadow;
|
|
}
|
|
|
|
// Samples the shadow for the given directional light (cascaded shadow map sampling)
|
|
float SampleShadow(LightData light, LightShadowData shadow, Texture2DArray shadowMap, float3 worldPosition, float viewDepth)
|
|
{
|
|
// Create a blend factor which is one before and at the fade plane
|
|
float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.NumCascades - 1] + shadow.FadeDistance) / shadow.FadeDistance);
|
|
BRANCH
|
|
if (fade >= 1.0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// Figure out which cascade to sample from
|
|
uint cascadeIndex = 0;
|
|
for (uint i = 0; i < shadow.NumCascades - 1; i++)
|
|
{
|
|
if (viewDepth > shadow.CascadeSplits[i])
|
|
cascadeIndex = i + 1;
|
|
}
|
|
|
|
// Project into shadow space
|
|
float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cascadeIndex]);
|
|
shadowPosition.xy /= shadowPosition.w;
|
|
shadowPosition.z -= shadow.Bias;
|
|
|
|
// Sample shadow
|
|
float result = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex);
|
|
|
|
// Increase the sharpness for higher cascades to match the filter radius
|
|
const float SharpnessScale[MaxNumCascades] = { 1.0f, 1.5f, 3.0f, 3.5f };
|
|
float sharpness = shadow.Sharpness * SharpnessScale[cascadeIndex];
|
|
|
|
#if CSM_BLENDING
|
|
// Sample the next cascade, and blend between the two results to smooth the transition
|
|
const float BlendThreshold = 0.1f;
|
|
float nextSplit = shadow.CascadeSplits[cascadeIndex];
|
|
float splitSize = cascadeIndex == 0 ? nextSplit : nextSplit - shadow.CascadeSplits[cascadeIndex - 1];
|
|
float splitDist = (nextSplit - viewDepth) / splitSize;
|
|
BRANCH
|
|
if (splitDist <= BlendThreshold && cascadeIndex != shadow.NumCascades - 1)
|
|
{
|
|
// Find the position of this pixel in light space of next cascade
|
|
shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cascadeIndex + 1]);
|
|
shadowPosition.xy /= shadowPosition.w;
|
|
shadowPosition.z -= shadow.Bias;
|
|
|
|
// Sample next cascade and blur result
|
|
float nextSplitShadow = SampleShadowCascade(shadowMap, shadow.ShadowMapSize, shadowPosition.z, shadowPosition.xy, cascadeIndex + 1);
|
|
float lerpAmount = smoothstep(0.0f, BlendThreshold, splitDist);
|
|
lerpAmount = splitDist / BlendThreshold;
|
|
result = lerp(nextSplitShadow, result, lerpAmount);
|
|
|
|
// Blur sharpness as well
|
|
sharpness = lerp(shadow.Sharpness * SharpnessScale[cascadeIndex + 1], sharpness, lerpAmount);
|
|
}
|
|
#endif
|
|
|
|
// Apply shadow fade and sharpness
|
|
result = saturate((result - 0.5) * sharpness + 0.5);
|
|
result = lerp(1.0f, result, (1 - fade) * shadow.Fade);
|
|
return result;
|
|
}
|
|
|
|
// Samples the shadow for the given directional light (cascaded shadow map sampling) for the material surface (supports subsurface shadowing)
|
|
float SampleShadow(LightData light, LightShadowData shadow, Texture2DArray shadowMap, GBufferSample gBuffer, out float subsurfaceShadow)
|
|
{
|
|
subsurfaceShadow = 1;
|
|
|
|
// Create a blend factor which is one before and at the fade plane
|
|
float viewDepth = gBuffer.ViewPos.z;
|
|
float fade = saturate((viewDepth - shadow.CascadeSplits[shadow.NumCascades - 1] + shadow.FadeDistance) / shadow.FadeDistance);
|
|
BRANCH
|
|
if (fade >= 1.0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// Figure out which cascade to sample from
|
|
uint cascadeIndex = 0;
|
|
for (uint i = 0; i < shadow.NumCascades - 1; i++)
|
|
{
|
|
if (viewDepth > shadow.CascadeSplits[i])
|
|
cascadeIndex = i + 1;
|
|
}
|
|
|
|
#if defined(USE_GBUFFER_CUSTOM_DATA)
|
|
// Subsurface shadowing
|
|
BRANCH
|
|
if (IsSubsurfaceMode(gBuffer.ShadingModel))
|
|
{
|
|
// Get subsurface material info
|
|
float opacity = gBuffer.CustomData.a;
|
|
|
|
// Project into shadow space
|
|
float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[cascadeIndex]);
|
|
shadowPosition.xy /= shadowPosition.w;
|
|
shadowPosition.z -= shadow.Bias;
|
|
|
|
// Sample shadow map (single hardware sample with hardware filtering)
|
|
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, float3(shadowPosition.xy, cascadeIndex), 0).r;
|
|
subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
|
|
|
|
// Apply shadow fade
|
|
subsurfaceShadow = lerp(1.0f, subsurfaceShadow, (1 - fade) * shadow.Fade);
|
|
}
|
|
#endif
|
|
|
|
float3 samplePosWS = gBuffer.WorldPos;
|
|
|
|
#if !LIGHTING_NO_DIRECTIONAL
|
|
// Skip if surface is in a full shadow
|
|
float NoL = dot(gBuffer.Normal, light.Direction);
|
|
BRANCH
|
|
if (NoL <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Apply normal offset bias
|
|
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
|
|
#endif
|
|
|
|
// Sample shadow
|
|
return SampleShadow(light, shadow, shadowMap, samplePosWS, viewDepth);
|
|
}
|
|
|
|
// Samples the shadow for the given spot light (PCF shadow map sampling)
|
|
float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, float3 worldPosition)
|
|
{
|
|
float3 toLight = light.Position - worldPosition;
|
|
float toLightLength = length(toLight);
|
|
float3 L = toLight / toLightLength;
|
|
#if LIGHTING_NO_DIRECTIONAL
|
|
float dirCheck = 1.0f;
|
|
#else
|
|
float dirCheck = dot(-light.Direction, L);
|
|
#endif
|
|
|
|
// Skip pixels outside of the light influence
|
|
BRANCH
|
|
if (toLightLength > light.Radius || dirCheck < 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// Negate direction and use normalized value
|
|
toLight = -L;
|
|
|
|
// Project into shadow space
|
|
float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[0]);
|
|
shadowPosition.z -= shadow.Bias;
|
|
shadowPosition.xyz /= shadowPosition.w;
|
|
|
|
float2 shadowMapUVs = shadowPosition.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f);
|
|
|
|
#if FilterSizeSpot == 2
|
|
|
|
// Use single hardware sample with filtering
|
|
float result = shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, shadowMapUVs, shadowPosition.z);
|
|
|
|
#else
|
|
|
|
float3 sideVector = normalize(cross(toLight, float3(0, 0, 1)));
|
|
float3 upVector = cross(sideVector, toLight);
|
|
|
|
float shadowMapSizeInv = 1.0f / shadow.ShadowMapSize.x;
|
|
sideVector *= shadowMapSizeInv;
|
|
upVector *= shadowMapSizeInv;
|
|
|
|
// Use PCF filter
|
|
float result = 0;
|
|
UNROLL
|
|
for(int i = 0; i < FilterSizeCube; i++)
|
|
{
|
|
float2 samplePos = shadowMapUVs + sideVector.xy * PCFDiscSamples[i].x + upVector.xy * PCFDiscSamples[i].y;
|
|
result += shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, samplePos, shadowPosition.z);
|
|
}
|
|
result *= (1.0f / FilterSizeCube);
|
|
|
|
#endif
|
|
|
|
// Apply shadow fade and sharpness
|
|
result = saturate((result - 0.5) * shadow.Sharpness + 0.5);
|
|
result = lerp(1.0f, result, shadow.Fade);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Samples the shadow for the given spot light (PCF shadow map sampling) for the material surface (supports subsurface shadowing)
|
|
float SampleShadow(LightData light, LightShadowData shadow, Texture2D shadowMap, GBufferSample gBuffer, out float subsurfaceShadow)
|
|
{
|
|
subsurfaceShadow = 1;
|
|
float3 toLight = light.Position - gBuffer.WorldPos;
|
|
float toLightLength = length(toLight);
|
|
float3 L = toLight / toLightLength;
|
|
#if LIGHTING_NO_DIRECTIONAL
|
|
float dirCheck = 1.0f;
|
|
#else
|
|
float dirCheck = dot(-light.Direction, L);
|
|
#endif
|
|
|
|
// Skip pixels outside of the light influence
|
|
BRANCH
|
|
if (toLightLength > light.Radius || dirCheck < 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#if defined(USE_GBUFFER_CUSTOM_DATA)
|
|
// Subsurface shadowing
|
|
BRANCH
|
|
if (IsSubsurfaceMode(gBuffer.ShadingModel))
|
|
{
|
|
// Get subsurface material info
|
|
float opacity = gBuffer.CustomData.a;
|
|
|
|
// Project into shadow space
|
|
float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[0]);
|
|
shadowPosition.z -= shadow.Bias;
|
|
shadowPosition.xyz /= shadowPosition.w;
|
|
|
|
// Sample shadow map (use single hardware sample with filtering)
|
|
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, shadowPosition.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f), 0).r;
|
|
subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
|
|
|
|
// Apply shadow fade
|
|
subsurfaceShadow = lerp(1.0f, subsurfaceShadow, shadow.Fade);
|
|
}
|
|
#endif
|
|
|
|
float3 samplePosWS = gBuffer.WorldPos;
|
|
|
|
#if !LIGHTING_NO_DIRECTIONAL
|
|
// Skip if surface is in a full shadow
|
|
float NoL = dot(gBuffer.Normal, L);
|
|
BRANCH
|
|
if (NoL <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Apply normal offset bias
|
|
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
|
|
#endif
|
|
|
|
// Sample shadow
|
|
return SampleShadow(light, shadow, shadowMap, samplePosWS);
|
|
}
|
|
|
|
// Samples the shadow for the given point light (PCF shadow map sampling)
|
|
float SampleShadow(LightData light, LightShadowData shadow, TextureCube<float> shadowMap, float3 worldPosition)
|
|
{
|
|
float3 toLight = light.Position - worldPosition;
|
|
float toLightLength = length(toLight);
|
|
float3 L = toLight / toLightLength;
|
|
|
|
// Skip pixels outside of the light influence
|
|
BRANCH
|
|
if (toLightLength > light.Radius)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// Negate direction and use normalized value
|
|
toLight = -L;
|
|
|
|
// Figure out which cube face we're sampling from
|
|
int cubeFaceIndex = GetCubeFaceIndex(toLight);
|
|
|
|
// Project into shadow space
|
|
float4 shadowPosition = mul(float4(worldPosition, 1.0f), shadow.ShadowVP[cubeFaceIndex]);
|
|
shadowPosition.z -= shadow.Bias;
|
|
shadowPosition.xyz /= shadowPosition.w;
|
|
|
|
#if FilterSizeCube == 2
|
|
|
|
// Use single hardware sample with filtering
|
|
float result = shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, toLight, shadowPosition.z);
|
|
|
|
#else
|
|
|
|
float3 sideVector = normalize(cross(toLight, float3(0, 0, 1)));
|
|
float3 upVector = cross(sideVector, toLight);
|
|
|
|
float shadowMapSizeInv = 1.0f / shadow.ShadowMapSize.x;
|
|
sideVector *= shadowMapSizeInv;
|
|
upVector *= shadowMapSizeInv;
|
|
|
|
// Use PCF filter
|
|
float result = 0;
|
|
UNROLL
|
|
for (int i = 0; i < FilterSizeCube; i++)
|
|
{
|
|
float3 cubeSamplePos = toLight + sideVector * PCFDiscSamples[i].x + upVector * PCFDiscSamples[i].y;
|
|
result += shadowMap.SampleCmpLevelZero(ShadowSamplerPCF, cubeSamplePos, shadowPosition.z);
|
|
}
|
|
result *= (1.0f / FilterSizeCube);
|
|
|
|
#endif
|
|
|
|
// Apply shadow fade and sharpness
|
|
result = saturate((result - 0.5) * shadow.Sharpness + 0.5);
|
|
result = lerp(1.0f, result, shadow.Fade);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Samples the shadow for the given point light (PCF shadow map sampling) for the material surface (supports subsurface shadowing)
|
|
float SampleShadow(LightData light, LightShadowData shadow, TextureCube<float> shadowMap, GBufferSample gBuffer, out float subsurfaceShadow)
|
|
{
|
|
subsurfaceShadow = 1;
|
|
float3 toLight = light.Position - gBuffer.WorldPos;
|
|
float toLightLength = length(toLight);
|
|
float3 L = toLight / toLightLength;
|
|
|
|
// Skip pixels outside of the light influence
|
|
BRANCH
|
|
if (toLightLength > light.Radius)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
// Negate direction and use normalized value
|
|
toLight = -L;
|
|
|
|
// Figure out which cube face we're sampling from
|
|
int cubeFaceIndex = GetCubeFaceIndex(toLight);
|
|
|
|
#if defined(USE_GBUFFER_CUSTOM_DATA)
|
|
// Subsurface shadowing
|
|
BRANCH
|
|
if (IsSubsurfaceMode(gBuffer.ShadingModel))
|
|
{
|
|
// Get subsurface material info
|
|
float opacity = gBuffer.CustomData.a;
|
|
|
|
// Project into shadow space
|
|
float4 shadowPosition = mul(float4(gBuffer.WorldPos, 1.0f), shadow.ShadowVP[cubeFaceIndex]);
|
|
shadowPosition.z -= shadow.Bias;
|
|
shadowPosition.xyz /= shadowPosition.w;
|
|
|
|
// Sample shadow map (use single hardware sample with filtering)
|
|
float shadowMapDepth = shadowMap.SampleLevel(SamplerLinearClamp, toLight, 0).r;
|
|
subsurfaceShadow = CalculateSubsurfaceOcclusion(opacity, shadowPosition.z, shadowMapDepth);
|
|
|
|
// Apply shadow fade
|
|
subsurfaceShadow = lerp(1.0f, subsurfaceShadow, shadow.Fade);
|
|
}
|
|
#endif
|
|
|
|
float3 samplePosWS = gBuffer.WorldPos;
|
|
|
|
#if !LIGHTING_NO_DIRECTIONAL
|
|
// Skip if surface is in a full shadow
|
|
float NoL = dot(gBuffer.Normal, L);
|
|
BRANCH
|
|
if (NoL <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Apply normal offset bias
|
|
samplePosWS += GetShadowPositionOffset(shadow.NormalOffsetScale, NoL, gBuffer.Normal);
|
|
#endif
|
|
|
|
// Sample shadow
|
|
return SampleShadow(light, shadow, shadowMap, samplePosWS);
|
|
}
|
|
|
|
#endif
|