# ShadowMap

若用 d 记作物体在 Shadowmap 中采样获取的深度,z 表示物体的光源坐标深度,f 表示阴影值,那么基础 Shadowmap 算法可以表示为:

f(z)=H(zd)f(z)=H(z-d)

H(x)=0(x0),1(x<0)H(x)=0(x\geq0), 1(x<0)

# Shadow

这里直接用 scenecapture2d 作为灯光捕捉的深度,传到材质里,同时也把相机的 VP 矩阵也一起传到材质里,做深度对比。

# PCF

用泊松采样,作为扰乱采样灯光深度图 uv 的 noise,然后在和深度做对比,就会形成周围一圈的软阴影。

# PCSS

  1. 首先要用 PCF 作为遮挡查询,查找这里是完全遮挡还是半遮挡还是没有遮挡,判断是不是半遮挡,就是对 PCF 进行周围查询,所遮挡像素是不是等于采样数
  2. 要是半遮挡的话,所有遮挡的深度加起来除于被遮挡的像素得到 closeDepth。
  3. 然后用 (CurrentDepth - closeDepth)/closeDepth 得到 HalfShadowSize
  4. 最后用 PCF 在采样深度时 * HalfShadowSize

# ESM

原始

  1. 在深度贴图中保存 e^
  2. 在采样阴影时计算 e^
  3. f(z)=e^{c(z-d)}=e^{cz-cd}=e^{-cd}*e^
  4. f(z)=saturate(f(z))f(z)=saturate(f(z))

c 值比较小的时候,ESM 的漏光问题非常严重,即使 c 值比较大的时候依然会有一点漏光。但是若 C 值设得太大,软阴影的效果就不明显了,而且可能会超过浮点数的表示上限。如果用在移动平台上,使用 16 位浮点数纹理,就更容易溢出了。对 32F 来说,c 到 88 就已经到极限了。但为了要让那个近似更接近原始值,c 应该越大越好,否则在 z-d 越接近 0 的时候,误差会越来越大。另一个缺点在于,原始 ESM 要求 depth 在非线性的 projection space,这就给点光源的阴影造成了麻烦。如果用 CSM 的话,projection space 也会因为在不同的层级而需要分别计算,分界线可能出现跳变。

# 改进 ESM

如果 depth 是在线性的 view space,那么点光源和 CSM 都能用上 ESM,也就是各种光源的 shadow 都可以切换到 ESM。这个公式来自于 EGSR2013 上浙大的文章 “Exponential Soft Shadow Mapping”。

ec(dlznzfznzlznzfzn)=eczfzn(dlzn)e^{-c\left(\frac{d_l-z_n}{z_f-z_n}-\frac{z_l-z_n}{z_f-z_n}\right)} = e^{-\frac{c}{z_f-z_n}(d_l-z_n)}

在 SIGGRAPH 2009 的 Advances in Real-Time Rendering in 3D Graphics and Games 里,Lighting Research at Bungie 就提到了 logarithmic space filtering 的方法。这里正是利用 d-z 远小于 d 或 z 的原理,把取值范围缩小了,精度也因此提高。filtering 本身就是完成这个:

i=0Nwiecdi\sum_{i=0}^N w_i e^{cd_i}

其中 w 来自于 gaussian filter 的 kernel 。如果进一步推这个公式,就能得到:

i=0Nwiecdi=ecd0i=0Nwiec(did0)=ecd0eln(w0+i=1Nwiec(did0))\sum_{i=0}^N w_ie^{cd_i} =e^{cd_0}\sum_{i=0}^N w_ie^{c(d_i- d_0)} =e^{cd_0}e^{\ln\left(w_0+\sum_{i=1}^N w_ie^{c(d_i-d_0)}\right)}

这个被称为 log space filtering。最终 filter 的结果是个不会溢出的量

cd0+ln(w0+i=1Nwiec(did0))cd_0 + \ln\left(w_0+\sum_{i=1}^N w_ie^{c(d_i-d_0)}\right)

  1. 在 Shadowmap 中保存 d
  2. blur 中使用 cd0+ln(w0+i=1Nwiec(did0))cd_0 + \ln\left(w_0+\sum_{i=1}^N w_ie^{c(d_i-d_0)}\right)
  3. 在采样阴影时计算 e^
  4. f(z)=e^
  5. f(z)=saturate(f(z))f(z)=saturate(f(z))

代码

ue4 custom node
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int kSize = (mSize-1)/2;
float kernel[64];
float3 FinalColor = float3(0.0f,0.0f,0.0f);

float3 d0 = pow(Texture2DSample(Tex, TexSampler, UV), 2.2);
//create the 1-D kernel
float sigma = 7.0;
float Z = 0.0;
int j = 0, i = 0;
for (j = 0; j <= kSize; j++)
{
kernel[kSize + j] = kernel[kSize - j] = 0.39894 * exp(-0.5 * float(j) * float(j) / (sigma * sigma)) / sigma;
}

//get the normalization factor (as the gaussian has been clamped)
for (j = 0; j < kSize * 2 + 1; j++)
{
Z += kernel[j];
}

//read out the texels
for (i=-kSize; i <= kSize; i++)
{
for (j=-kSize; j <= kSize; j++)
{
float3 di = pow(Texture2DSample(Tex, TexSampler, UV + float2(1.0 * i / 512 * dist, 1.0 * j / 512 * dist)), 2.2);
FinalColor += kernel[kSize+j] * kernel[kSize+i] * exp(c * (di - d0));
}
}

FinalColor = log(FinalColor/(Z * Z)) + c * d0;
return float4(FinalColor, 1.0);

更新于

请我喝[茶]~( ̄▽ ̄)~*

Natsuneko 微信支付

微信支付

Natsuneko 支付宝

支付宝

Natsuneko 贝宝

贝宝