乐趣区

关于计算机图形学:游戏中的动态阴影下

本篇分为高低两篇,上篇内容请关注:
游戏中的动静暗影(上)


六、基于 Shadowmap 实现软暗影

1. Percentage-Closer Filtering(PCF)
采样 Shadowmap 时,咱们往往这样来实现一些软暗影的成果:在指标采样点四周,进行四次采样,而后取平均值,作为最终后果。留神这里的取平均值,并不是取平均值后进行比拟,而是对四个采样点,别离进行深度测试,而后每个采样点的 0 或 1 的后果值进行均匀,这样在半影区域就能失去软暗影成果。

这种将采样后果进行均匀的形式叫做 Percentage-Closer Filtering(PCF)[6],PCF 通过将指标点左近的采样后果均匀,来模拟出半影的成果。

当初的硬件都间接提供四周四点采样的加权 PCF 深度测试,比方 OpenGL 中的 sampler2DShadow,DirectX 中的 SampleCmp。这种采样的加权形式相似于一般像素采样时的双线性采样,在指标地位左近 2X2 像素中,逐像素进行深度比拟,失去后果值 0 或 1,而后将后果依照绝对四周像素地位进行加权均匀。

间接应用硬件 PCF,只能采样到 2X2 的像素点,失去的半影过渡,往往不够柔和。如果想要更加柔和的暗影过渡,或者把半影区域扩充,就须要将采样点散布范畴扩充,也须要减少采样点的个数。

简略的形式,是间接在指标点四周依照 Grid 模式进行采样,然而这样往往会在半影中看到分层的瑕疵。

因而咱们更加罕用的形式,是应用预计算好的 Possion 散布的采样点,来进行采样。为了使后果进一步平滑,咱们还能够应用逐像素的噪声值,对采样点地位进行旋转,这样每两个相邻的像素点,采样的模式都是不同的,能够无效地平滑半影区域。

从左到右顺次是:4X4 的 Grid 采样
12 点 Possion 采样
12 点 Possion 采样 + 旋转
Possion 分布图

2. PCF 软暗影的 Bias 问题
在后面咱们曾经讲过 Bias 的问题,在 PCF 采样中,因为 PCF 采样 Shadowmap 的范畴会比拟大,因而会进一步暴露出 Shadow Acane 的问题。当然咱们也有响应的伎俩来解决这些问题。

一种简略的形式,是依据 PCF filter kernel 的大小,来动静扭转 Shadow Bias 的大小,当然这样做的毛病也很显著,就是 PCF kernel 越大,就会损失越多的暗影精度信息。

另外一种形式是Bias Cone,依据以后采样点到采样核心的地位,来缩放 Bias 的大小,如下图右边所示。是一种绝对简略无效的缓解 Bias 问题的计划。

左:Bias Cone;右:Receiver Plane Depth Bias

上图左边显示的一种逐采样点来做准确 Bias 的算法:Receiver Plane Depth Bias。这种形式须要假设承受暗影的是一个立体,而后会依据每个暗影采样点到核心的地位,来计算偏移。个别能产生十分好的后果[7]。

3. Percentage-Closer Soft Shadows(PCSS)
PCF 暗影的一个毛病,就是半影的宽度十分固定,无论产生暗影的地位间隔光照有多远,半影的宽度都是一样的。

PCSS[8]通过判断半影到遮挡物和半影到光源的间隔,来动静确定半影的宽度。半影宽度越大,采样暗影的模式散布也越大,就能失去越柔和的暗影。这样就能失去如下图左边所示的,随间隔变动的暗影成果。

左:PCF 硬暗影,中:PCF 软暗影,右:PCSS 暗影

PCSS 算法分成这样几个步骤:

  1. 计算出区域内均匀 Blocker 深度;
  2. 依据 Blocker 深度,计算出须要的半影宽度;
  3. 用半影宽度,作为 PCF kernal 的大小,计算出暗影。

PCSS 的计算其实很简略,就是依据三角形类似,来计算出采样所需的散布间隔,而后将间隔内的采样值进行均匀。

不过当半影宽度十分大时,就须要十分多的采样点,这样采样 Shadowmap 的开销也会变大。因而 PCSS 是一种不太稳固的软暗影计划,在游戏中的理论利用并不是特地多。

七、基于 Shadowmap 的逐物体暗影 /Per Obejct Shadow

1. Modulated shadow 的实现
后面咱们讲到的立体暗影,只能投射暗影到立体上,在应用 Shadowmap 保留深度后,就能够将暗影投射到任意的曲面上,具体放办法如下:

首先咱们失去须要渲染暗影物体的 AABB,而后将 AABB 转换到 Light Sapce,失去新的 Orthogonal Light Space ABB。而后咱们将物体的深度渲染到一张 Shadowmap 中。

咱们将 Light Sapce 的 AABB 沿着光照方向进行缩短,就失去了一个 Shadow Volume。

接下来咱们就能够应用这个 Shadow Volume 来失去投射暗影了。将 Shadow Volume 作为几何体进行渲染,在 Shader 中读取以后地位的 Depth 值,反算出世界坐标,再通过投影矩阵算出光照空间下的深度值,在 Shadowmap 中进行采样,失去暗影。将最终输入后果的混合形式为 DstColor Zero,这样,被遮挡区域有暗影的地位,色彩都这样乘以一个暗影系数,失去一个染色的成果,也就实现了 Modulated shadow。

留神,为了避免在不须要暗影的区域渲染出暗影,咱们须要在代码中进行 clip,如果计算出 Shadowmap 中对应的 uv 坐标超出 0~1 的范畴,就不再渲染暗影。在 Unity 中实现的 Shader 代码大抵如下:

                float4 frag (v2f i) : SV_Target
{float2 uv = i.vertex.xy * (_ScreenParams.zw - 1);
float depth = tex2D(_CameraDepthTexture, uv).r;


#if !UNITY_REVERSED_Z
depth = depth * 2 - 1;
#endif

#if UNITY_REVERSED_Z
uv.y = 1 - uv.y;
#endif
float4 clipPos = float4(2.0f * uv - 1.0f, depth, 1.0);//
// 反算出世界空间坐标
float4 worldSpacePos = mul(UNITY_MATRIX_I_VP, clipPos);
worldSpacePos /= worldSpacePos.w;//
// 失去 shadowmap 中 uv 坐标
float4 projectorPos = mul(_WorldToProjector, worldSpacePos);


#if UNITY_REVERSED_Z
projectorPos.z = clamp(projectorPos.z, 0.0001, 1);
#else
projectorPos.z = clamp(projectorPos.z, 0, 0.9999);
#endif
// uv 不在 0~1 范畴内,不须要暗影
clip(projectorPos.xy);
clip(1 - projectorPos.xy);
projectorPos.xy = projectorPos.xy * _ShadowmapTex_ST.xy + _ShadowmapTex_ST.zw;
float shadow = SAMPLE_TEXTURE2D_SHADOW(_H3D_GroundShadowmapTex, sampler_H3D_GroundShadowmapTex, projectorPos.xyz).r;
return shadow;
}

Modulated shadow 有这样两个显著的毛病:

  1. 无奈完全正确还原暗影成果,因为 Modulated shadow 是通过将原色乘以某个系数来实现的暗影,而非遮蔽光照造成暗影,因而成果会有误差。而且多个 Modulated shaodw 会屡次叠加在一起。
  2. 在特定的察看角度下,Modulated shadow 可能会穿过被投射暗影的物体。

UE4 中 Modualted shaow 的成果
能够看到两个人物的暗影呈现谬误的叠加

《咫尺明月刀》手游中的 Modualted Shadow,谬误地穿过了树干

在游戏实际中,最罕用到 Modulated shadow 的中央,就是将人物投影在高空上。咱们晓得,Modulated shadow 的成果是有偏差的,特地在人物身上这种十分高频的区域,就会非常明显。因而咱们通常会应用模板的形式,将人物身上的 Modulated shadow 剔除掉,只显示在高空上。对于人物身上的自暗影,咱们会依照失常的 Shaodwmap 来渲染。

这样一来,咱们为人物独自生成一张 Shadowmap,会同时在两个中央用到:一是用于产生人物身上的自暗影,二是用于高空投射的 Modulated Shadow。这也是手机游戏中罕用的一种解决人物角色暗影的计划。

比方在《咫尺明月刀》手游中,就是应用这样的实现形式。如下图所示,右边是 Shadowmap,左边是渲染的后果,Shadowmap 同时用来实现人物身上的自暗影和高空上的 Modualted Shadow。

右边是仅针对人物生成的 Shadowmap、同时用于人物的自暗影,和高空的投影

2. 应用 Shadowmask 混合多种暗影
在游戏开发中,咱们经常会同时应用不同品种的暗影,或者应用多个 PerObject 暗影。如果在一个 Pass 中同时判断多个暗影,那么解决起来会十分麻烦。一种通用的解决方案,是将暗影事后绘制到 Shadowmask 上,而后再进行相应的光照计算。

比方咱们应用了 Stationary 模式的灯光,对于动态的物体,咱们应用了烘焙的暗影。而对于动静的物体,咱们就须要实时来渲染暗影了。这样,咱们能够先将动态的暗影输入到一张 Shadowmask 上,而后在绘制动静物体的暗影,实现两种类型暗影的叠加。

3. 逐物体暗影的几种利用场景
这里,咱们来小结一下逐物体暗影的罕用利用场景。

  1. Stationary 模式的光照,烘焙动态物体的暗影;
  2. 高精度角色暗影,和场景暗影拆散;
  3. 超出 CSM 暗影范畴的物体,独自解决暗影;
  4. 挪动端便宜的 Modulated Shadow 实现。

八、基于 Z -Buffer 的 Filterable Shadowmap

后面介绍的是应用 PCF 来失去软暗影,在每次计算暗影时,须要进行很屡次的采样和计算,如果想要更加柔和的暗影过渡,就只能通过减少采样次数来实现。在这里,咱们将介绍一些能够预过滤的暗影技术,这些技术能够将失去的 Shadowmap 进行含糊预处理,来失去软暗影,这样能够升高计算软暗影的开销。

1. Variance Shadow Map(VSM)[9]
VSM 应用两张 Shadowmap,别离存储深度值和深度值的平方,具体原理如下:

已知切比雪夫不等式为:

这样,咱们应用两张 Shadowmap 别离存储深度值和深度值的平方。这样,将两张 Shadowmap 进行 Filter 解决(应用 Mipmap 或者双 Pass 高斯含糊),就能够间接失去 E(x)和 E(x2),已知方差 σ 2 = E(x2)-E(x)2,这样,咱们能够间接将失去的 P(x)值作为暗影系数值来应用,不便地失去软暗影。

当然,从下面的切比雪夫不等式咱们能够看出,这里的 P(x),其实只是一个概率值的上界,咱们这里是间接应用这个上界来作为最终的暗影系数来应用了。

上面,咱们就来证实下,在简略的光照环境下,这种间接应用上界失去的暗影系数是正当的。

当初有一个深度值为 d 1的立体,投射暗影到深度值为 d 2的立体上。当初咱们在采样暗影时进行 Filter,设 p 为未被遮挡的比例,也就是咱们冀望失去的暗影系数值,由此咱们能够失去:

咱们从切比雪夫不等式中失去的概率上界为:

和咱们的期望值是相等的,证实咱们这样来应用切比雪夫不等式的概率上界是正确的。

这样,咱们就能够通过对 Shadowmap 进行预处理,来失去软暗影。

咱们的实现过程大抵如下:

  1. 在光照空间下,将深度值和深度值的平方别离存储到两张 flaot 格局的 Shadowmap 中;
  2. 将两张 Shadowmap 进行 Mipmap 解决,或者应用双 Pass 高斯含糊;
  3. 在渲染时进行暗影计算,如果以后像素点的深度值小于均匀深度 μ,阐明该点没有被暗影遮挡。如果深度值大于均匀深度,就是用后面的公式来计算暗影系数。

左:间接进行 VSM 计算
右:先进行 Mipmap 解决,再计算 VSM</center>

在一些简单光照环境下,VSM 可能会呈现一些瑕疵。

右边是失常的 VSM 计算
在左边,增加了一个三角形后,造成了显著的漏光

2. Exponential Shadow Map(ESM)[10]
ESM 也是一种相似 VSM 的 Filtered Shadow Map,在空间中有一点 x,设 d(x)为 x 到光源的间隔,z(p)示意以后方向上最近的遮挡物的间隔,这样咱们失去暗影函数为:

s(x)失去的后果是 0 或者 1,示意以后的点是否被暗影遮挡。

当初,咱们应用指数函数来代替函数 f,定义这样一个指数的函数,来代替原来的 0 或 1 的大小判断:

从下图中咱们能够看出,当 d -z>0 时,新的函数 f 和原来的暗影判断函数 s 是十分靠近的,且 c 的值越大,就越靠近。

在应用原始的暗影函数计算软暗影时,失去的过滤后的后果为:

这也就是咱们相熟的 PCF 计算软暗影的形式。当初,咱们应用指数函数来代替上述的计算过程:

察看最初这个公式,咱们发现右边的局部是能够间接在计算暗影时求得,左边的局部,其实是能够通过预过滤的形式计算出来。也就是说,咱们生成的 Shadowmap 中存储 e czi的值,而后对 Shadowmap 进行 Filter 解决,就能够失去左边的局部:

从后面的图像中咱们晓得,c 的值越大,指数函数的图形就和实在的暗影判断越靠近。不过在理论计算时,因为精度的限度,咱们不能把 c 的值设置的过大,通常抉择一个适合的值即可。

一个针对 ESM 的改良,是对深度值的编码做出一些改良,将后果保留在 log 空间中,这样能够应用更大的 c 值[11],失去的后果精度天然也会更高:

3. Exponential Variance Shadow Map(EVSM)
EVSM 是一种对 VSM 的改良[12],人们发现,在应用 VSM 的时候,能够将深度应用一个 wrap 函数进行解决,而后间接对 wrap 后的后果进行 VSM 中同样的计算解决,能够失去更好的暗影后果。

借鉴 ESM 中的做法,这个 wrap 函数就是 e cx。在实践中,会应用 -e-cx再求出一个 wrap 值,而后取两个后果的最小值。

因而这种办法叫做 EVSM,联合了 ESM 和 VSM 的长处,毛病就是应用的 Buffer 存储较多,须要 4 通道。

4. Moment Shadow Map(MSM)[13]
Moment 意思是矩[14],示意变量的散布特色,比方一阶矩就是平均值,二阶矩就是方差。

5. Filterable Shadowmap 的小结
各个计划的概览如下,一般来说,应用的通道数越多,成果也越好。

计划应用通道数保留的参数

一个网络上的不同 Shadowmap 技术的示例[15]。

绝对于一般的 PCF 暗影,Filterable Shadowmap 领有一些含糊阶段的固定开销。在采样十分软的暗影的时候,绝对一般的 PCF 是有性能劣势的。然而在硬暗影下,性能反而会降落。

除此之外,Filterable Shadowmap 要产生相似相似 PCSS 的可变柔和度的暗影,实现起来要简单很多[16]。

而且 Filterable Shadowmap 还有思考硬件兼容,数值溢出,以及一些漏光等边界条件。因而集体不是十分举荐应用 Filterable Shadowmap 来代替一般的 PCF 暗影,特地是在挪动平台上。不过在应用动态烘焙暗影时,因为能够进行预处理含糊暗影,应用 Filterable Shadowmap 是一个能够用来尝试升高运行开销的计划。

九、Contact Shadow

后面咱们讲过,仅仅应用 CSM 暗影的话,在近距离察看人物的时候,精度往往是不够的。除了应用 PerObjectShadow 之外,另外一种提供近处高精度暗影的形式是应用 Contact Shadow[17]。

Contact Shadow 的原理比较简单,是在屏幕空间进行逐像素的 RayMaraching,来失去高质量的近距离暗影。因为 RayMarching 的开销较大,Contact Shadow RayMatching 的间隔个别都很短,大概在 0.1m~0.5m 左右。

Contact Shadow 对 CSM 暗影通常是近距离细节补充的关系,个别不会间接应用 Contact Shadow 来代替一般的暗影计算。

Contact Shadow 的另外一个用处,是用于应用了 Parallax Occlusion Mapping 的场景。此时无奈在 Shadowmap 中算出准确的偏移值,就能够应用 Contact Shadow。

十、基于 SDF 的暗影

后面咱们说的暗影,都是通过解决模型来实现的。

SDF(Signed Distance Field)是一种保留空间中信息的场,保留空间中以后地位到最近的模型外表的间隔。在物体内部时应用负数,在物体外部时应用正数。

因为 SDF 信息和模型的面数无关,因而咱们能够应用十分大范畴的 SDF 信息,并且应用 Clipmap 来做 LOD 解决。CSM 暗影,在间隔较远处,因为须要解决的模型较多,开销也会增大。而 SDF 暗影就没有这个问题。

SDF 的另外一个劣势,就是能够十分不便地实现 Cone Tracing[18],进而不便地实现软暗影和面光源暗影成果。绝对于 Shadowmap 暗影,SDF 暗影更加柔和。

SDF 软暗影和一般暗影的成果比照

十一、环境光照的暗影

环境光照的暗影,其实就是咱们常说的 SSAO,即环境光照遮蔽。对于这部分,在我的专栏中 [19] 曾经有具体的介绍。

十二、Capsule AO 和 Capsule Shadow

应用 SSAO 时,失去的 AO 成果范畴较小,会导致人物的 AO 成果不是很好。而人物是动静的,又无奈应用烘焙 AO。Capsule AO 将人物模型简化成胶囊体形态,并进行 AO 计算,来失去范畴更大,更加柔和的 AO。

同样,通过后面的剖析咱们晓得,要应用 Shadowmap 来实现大范畴软暗影,是十分艰难的。Capsule Shadow 是一种用于实现柔和的人物投影的暗影。

十三、基于光线追踪的暗影技术

暗影是实时光追中比较简单的利用,实现起来也非常简单。应用光线追踪,能够十分不便地实现一些面光源的软暗影成果,只须要在面光源上,采样很多个点,而后和指标点之间进行连线并计算遮挡。

十四、体暗影

后面咱们讲的暗影,都没有思考半透明物体的暗影。在思考半透明暗影后,事件就会变得非常复杂。对于这方面的内容,会在将来的文章中具体解说。

参考
[6] https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting…
[7] https://jcgt.org/published/0003/04/08/paper-lowres.pdf
[8] https://developer.download.nvidia.com/shaderlibrary/docs/shad…
[9] https://developer.download.nvidia.com/SDK/10.5/direct3d/Sourc…
[10] http://www.cad.zju.edu.cn/home/jqfeng/papers/Exponential%20So…
[11] http://www.klayge.org/2013/10/07/%e5%88%87%e6%8d%a2%e5%88%b0esm/
[12] Rendering antialiased shadows using warped variance shadow maps
[13] http://momentsingraphics.de/I3D2015.html
[14] https://baike.baidu.com/item/%E7%9F%A9/22856460
[15] https://github.com/TheRealMJP/Shadows
[16] https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-a…
[17] https://docs.unrealengine.com/5.0/en-US/contact-shadows-in-un…,greater%20than%200.%0AClick%20for%20full%20image.%20More%20
[18] https://advances.realtimerendering.com/s2015/DynamicOcclusion…
[19] https://zhuanlan.zhihu.com/p/194198670


这是侑虎科技第 1380 篇文章,感激作者张亚坤供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ 群:465082844)

作者主页:https://www.zhihu.com/people/tc130-52

【USparkle 专栏】如果你深怀绝技,爱“搞点钻研”,乐于分享也博采众长,咱们期待你的退出,让智慧的火花碰撞交错,让常识的传递生生不息!

退出移动版