Press "Enter" to skip to content

## 前言

DirectX11 With Windows SDK完整目录

## Variance Shadow Map

$f(d_r)=P(d_o\geq d_r)$

$\mu=E(d_o)\\ \sigma^2=E(d_o^2)-E(d_o)^2$

$E(x)=\int xp(x)dx\\ E(x^2)=\int x^2p(x)dx$

$E(d_o)\approx\sum w_i d_i\\ E(d_o^2)\approx\sum w_i d_i^2$

$P(d_o\geq d_r)\leq p_{max}(d_r)\equiv \frac{\sigma^2}{\sigma^2+(\mu-d_r)^2}$

float ChebyshevUpperBound(float2 moments,
float receiverDepth,
float minVariance,
float lightBleedingReduction)
{
float variance = moments.y - (moments.x * moments.x);
variance = max(variance, minVariance); // 防止0除

float d = receiverDepth - moments.x;
float p_max = variance / (variance + d * d);

// 单边切比雪夫
return (receiverDepth <= moments.x ? 1.0f : p_max);
}

## 对VSM滤波

// Shadow.hlsl
Texture2DMS<float, MSAA_SAMPLES> g_ShadowMap : register(t0);   // 用于VSM生成
float2 VarianceShadowPS(float4 posH : SV_Position,
float2 texCoord : TEXCOORD) : SV_Target
{
float sampleWeight = 1.0f / float(MSAA_SAMPLES);
uint2 coords = uint2(posH.xy);

float2 avg = float2(0.0f, 0.0f);

[unroll]
for (int i = 0; i < MSAA_SAMPLES; ++i)
{
float depth = g_ShadowMap.Load(coords, i);
avg.x += depth * sampleWeight;
avg.y += depth * depth * sampleWeight;
}
return avg;
}

// Shadow.hlsl
#ifndef BLUR_KERNEL_SIZE
#define BLUR_KERNEL_SIZE 3
#endif
static const int BLUR_KERNEL_BEGIN = BLUR_KERNEL_SIZE / -2;
static const int BLUR_KERNEL_END = BLUR_KERNEL_SIZE / 2 + 1;
static const float FLOAT_BLUR_KERNEL_SIZE = (float)BLUR_KERNEL_SIZE;
Texture2D g_TextureShadow : register(t1);                      // 用于模糊
SamplerState g_SamplerPointClamp : register(s0);
float2 VSMHorizontialBlurPS(float4 posH : SV_Position,
float2 texcoord : TEXCOORD) : SV_Target
{
float2 depths = 0.0f;
[unroll]
for (int x = BLUR_KERNEL_BEGIN; x < BLUR_KERNEL_END; ++x)
{
depths += g_TextureShadow.Sample(g_SamplerPointClamp, texcoord, int2(x, 0));
}
depths /= FLOAT_BLUR_KERNEL_SIZE;
return depths;
}
float2 VSMVerticalBlurPS(float4 posH : SV_Position,
float2 texcoord : TEXCOORD) : SV_Target
{
float2 depths = 0.0f;
[unroll]
for (int y = BLUR_KERNEL_BEGIN; y < BLUR_KERNEL_END; ++y)
{
depths += g_TextureShadow.Sample(g_SamplerPointClamp, texcoord, int2(0, y));
}
depths /= FLOAT_BLUR_KERNEL_SIZE;
return depths;
}

## 漏光(Light Bleeding)

VSM最大的问题在于漏光现象，见下图（不得不说这漏光是真的严重）。

### 减少漏光的近似算法

float Linstep(float a, float b, float v)
{
return saturate((v - a) / (b - a));
}
// 令[0, amount]的部分归零并将(amount, 1]重新映射到(0, 1]
float ReduceLightBleeding(float pMax, float amount)
{
return Linstep(amount, 1.0f, pMax);
}

float ChebyshevUpperBound(float2 moments,
float receiverDepth,
float minVariance,
float lightBleedingReduction)
{
float variance = moments.y - (moments.x * moments.x);
variance = max(variance, minVariance); // 防止0除

float d = receiverDepth - moments.x;
float p_max = variance / (variance + d * d);

p_max = ReduceLightBleeding(p_max, lightBleedingReduction);

// 单边切比雪夫
return (receiverDepth <= moments.x ? 1.0f : p_max);
}

## 使用梯度对级联阴影采样

#### 使用各项异性滤波由于动态流控制导致在级联之间出现的接缝

float CalculateVarianceShadow(float4 shadowTexCoord,
float4 shadowTexCoordViewSpace,
int currentCascadeIndex)
{
float percentLit = 0.0f;

float2 moments = 0.0f;

// 为了将求导从动态流控制中拉出来，我们计算观察空间坐标的偏导
// 从而得到投影纹理空间坐标的偏导
float3 shadowTexCoordDDX = ddx(shadowTexCoordViewSpace).xyz;
float3 shadowTexCoordDDY = ddy(shadowTexCoordViewSpace).xyz;
shadowTexCoordDDX *= g_CascadeScale[currentCascadeIndex].xyz;
shadowTexCoordDDY *= g_CascadeScale[currentCascadeIndex].xyz;

moments += g_TextureShadow.SampleGrad(g_SamplerShadow,
float3(shadowTexCoord.xy, (float) currentCascadeIndex),
shadowTexCoordDDX.xy, shadowTexCoordDDY.xy).xy;

percentLit = ChebyshevUpperBound(moments, shadowTexCoord.z, 0.00001f, g_LightBleedingReduction);

return percentLit;
}

VSM具有如下优点：

## Exponential Shadow Map

$f(z)=saturate(e^{c(d-z)}), d<z, c>0$

## 提升精度

\begin{aligned}\sum_{i=0}^N w_i e^{cd_{o_i}}&= e^{cd_{o_0}}(w_0+\sum_{i=1}^Nw_i e^{c(d_{o_i}-d_{o_0})})\\ &=e^{cd_{o_0}}\cdot e^{ln(w_0+\sum_{i=1}^Nw_i e^{c(d_{o_i}-d_{o_0})})}\\ &=e^{cd_{o_0} + ln(w_0+\sum_{i=1}^Nw_i e^{c(d_{o_i}-d_{o_0})})} \end{aligned}

$cd_{o_0} + ln(w_0+\sum_{i=1}^Nw_i e^{c(d_{o_i}-d_{o_0})})$

## HLSL代码

float ESMLogGaussianBlurPS(float4 posH : SV_Position,
float2 texcoord : TEXCOORD) : SV_Target
{
float cd0 = g_TextureShadow.Sample(g_SamplerPointClamp, texcoord);
float sum = g_BlurWeights[FLOAT_BLUR_KERNEL_SIZE / 2] * g_BlurWeights[FLOAT_BLUR_KERNEL_SIZE / 2];
[unroll]
for (int i = BLUR_KERNEL_BEGIN; i < BLUR_KERNEL_END; ++i)
{
for (int j = BLUR_KERNEL_BEGIN; j < BLUR_KERNEL_END; ++j)
{
float cdk = g_TextureShadow.Sample(g_SamplerPointClamp, texcoord, int2(i, j)) * (float) (i != 0 || j != 0);
sum += g_BlurWeights[i - BLUR_KERNEL_BEGIN] * g_BlurWeights[j - BLUR_KERNEL_BEGIN] * exp(cdk - cd0);
}
}
sum = log(sum) + cd0;
sum = isinf(sum) ? 84.0f : sum;  // 防止溢出
return sum;
}

//--------------------------------------------------------------------------------------
// ESM：采样深度图并返回着色百分比
//--------------------------------------------------------------------------------------
float CalculateExponentialShadow(float4 shadowTexCoord,
float4 shadowTexCoordViewSpace,
int currentCascadeIndex)
{
float percentLit = 0.0f;

float occluder = 0.0f;

float3 shadowTexCoordDDX = ddx(shadowTexCoordViewSpace).xyz;
float3 shadowTexCoordDDY = ddy(shadowTexCoordViewSpace).xyz;
shadowTexCoordDDX *= g_CascadeScale[currentCascadeIndex].xyz;
shadowTexCoordDDY *= g_CascadeScale[currentCascadeIndex].xyz;

occluder += g_TextureShadow.SampleGrad(g_SamplerShadow,
float3(shadowTexCoord.xy, (float) currentCascadeIndex),
shadowTexCoordDDX.xy, shadowTexCoordDDY.xy).x;

percentLit = saturate(exp(occluder - g_MagicPower * shadowTexCoord.z));

return percentLit;
}

ESM具有如下优点：

## 后记

#### VSM

Shadow MSAA：记录阴影图时开启MSAA，然后生成VSM的时候进行Resolve
Light Bleeding：将 [0, amount] 映射到0，将 [amount, 1] 映射到 [0, 1]
Enable Mipmap：级联阴影开启mipmap
Sampler：采样VSM使用的滤波

#### ESM

Blur Sigma：Log高斯滤波用于控制权重分散情况
Magic Power：控制 $$e^{cd}$$ 和 $$e^{cz}$$ 的c项

GPU Profile那边开Release来查看各个Pass下。至于EVSM和MSM等，可以尝试跑 TheRealMJP/Shadows 的项目，但需要一些动手修改的能力，它那边可以调的参数更多。

## Fixed-Size Penumbra

PCF(Percentage Closer Filtering)
VSM(Variance Shadow Maps, 2006)
LVSM(Layered Variance Shadow Maps)
ESM(Exponential Shadow Maps, 2008)
EVSM(Exponential Variance Shadow Maps)
MSM(Movement Shadow Maps, 2015)
Virtual Shadow Map(这个估计只能在DX12做)

## Variable-Size Penumbra

PCSS(Percentage Closer Soft Shadows)
VSSM = PCSS + VSM(Variance Soft Shadow Maps)
SAVSM = VSM + SAT(Summed Area Table)

## Others

Reflective Shadow Maps

## Cascade Optimization & Technique

Sample Distribution Shadow Map
GPU-Driven Cascade Setup and Scene Submission
Deferred Shadow

[14] VSSM

VarianceShadows11

TheRealMJP/Shadows

DirectX11 With Windows SDK完整目录