# ShadowMap若用 d 记作物体在 Shadowmap 中采样获取的深度,z 表示物体的光源坐标深度,f 表示阴影值,那么基础 Shadowmap 算法可以表示为:
f ( z ) = H ( z − d ) f(z)=H(z-d) f ( z ) = H ( z − d )
H ( x ) = 0 ( x ≥ 0 ) , 1 ( x < 0 ) H(x)=0(x\geq0), 1(x<0) H ( x ) = 0 ( x ≥ 0 ) , 1 ( x < 0 )
# Shadow这里直接用 scenecapture2d 作为灯光捕捉的深度,传到材质里,同时也把相机的 VP 矩阵也一起传到材质里,做深度对比。
# PCF用泊松采样,作为扰乱采样灯光深度图 uv 的 noise,然后在和深度做对比,就会形成周围一圈的软阴影。
# PCSS首先要用 PCF 作为遮挡查询,查找这里是完全遮挡还是半遮挡还是没有遮挡,判断是不是半遮挡,就是对 PCF 进行周围查询,所遮挡像素是不是等于采样数 要是半遮挡的话,所有遮挡的深度加起来除于被遮挡的像素得到 closeDepth。 然后用 (CurrentDepth - closeDepth)/closeDepth 得到 HalfShadowSize 最后用 PCF 在采样深度时 * HalfShadowSize # ESM原始
在深度贴图中保存 e^ 在采样阴影时计算 e^ f(z)=e^{c(z-d)}=e^{cz-cd}=e^{-cd}*e^ f ( z ) = s a t u r a t e ( f ( z ) ) f(z)=saturate(f(z)) f ( z ) = s a t u r a t e ( 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”。
e − c ( d l − z n z f − z n − z l − z n z f − z n ) = e − c z f − z n ( d l − z n ) 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)} e − c ( z f − z n d l − z n − z f − z n z l − z n ) = e − z f − z n c ( 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 = 0 N w i e c d i \sum_{i=0}^N w_i e^{cd_i} i = 0 ∑ N w i e c d i
其中 w 来自于 gaussian filter 的 kernel 。如果进一步推这个公式,就能得到:
∑ i = 0 N w i e c d i = e c d 0 ∑ i = 0 N w i e c ( d i − d 0 ) = e c d 0 e ln ( w 0 + ∑ i = 1 N w i e c ( d i − d 0 ) ) \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)} i = 0 ∑ N w i e c d i = e c d 0 i = 0 ∑ N w i e c ( d i − d 0 ) = e c d 0 e l n ( w 0 + ∑ i = 1 N w i e c ( d i − d 0 ) )
这个被称为 log space filtering。最终 filter 的结果是个不会溢出的量
c d 0 + ln ( w 0 + ∑ i = 1 N w i e c ( d i − d 0 ) ) cd_0 + \ln\left(w_0+\sum_{i=1}^N w_ie^{c(d_i-d_0)}\right) c d 0 + ln ( w 0 + i = 1 ∑ N w i e c ( d i − d 0 ) )
在 Shadowmap 中保存 d blur 中使用 c d 0 + ln ( w 0 + ∑ i = 1 N w i e c ( d i − d 0 ) ) cd_0 + \ln\left(w_0+\sum_{i=1}^N w_ie^{c(d_i-d_0)}\right) c d 0 + ln ( w 0 + ∑ i = 1 N w i e c ( d i − d 0 ) ) 在采样阴影时计算 e^ f(z)=e^ f ( z ) = s a t u r a t e ( f ( z ) ) f(z)=saturate(f(z)) f ( z ) = s a t u r a t e ( 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);