本篇分为高低两篇,上篇内容请关注:
游戏中的动静暗影(上)
六、基于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算法分成这样几个步骤:
- 计算出区域内均匀Blocker深度;
- 依据Blocker深度,计算出须要的半影宽度;
- 用半影宽度,作为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有这样两个显著的毛病:
- 无奈完全正确还原暗影成果,因为Modulated shadow是通过将原色乘以某个系数来实现的暗影,而非遮蔽光照造成暗影,因而成果会有误差。而且多个Modulated shaodw会屡次叠加在一起。
- 在特定的察看角度下,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. 逐物体暗影的几种利用场景
这里,咱们来小结一下逐物体暗影的罕用利用场景。
- Stationary模式的光照,烘焙动态物体的暗影;
- 高精度角色暗影,和场景暗影拆散;
- 超出CSM暗影范畴的物体,独自解决暗影;
- 挪动端便宜的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),其实只是一个概率值的上界,咱们这里是间接应用这个上界来作为最终的暗影系数来应用了。
上面,咱们就来证实下,在简略的光照环境下,这种间接应用上界失去的暗影系数是正当的。
当初有一个深度值为d1的立体,投射暗影到深度值为d2的立体上。当初咱们在采样暗影时进行Filter,设p为未被遮挡的比例,也就是咱们冀望失去的暗影系数值,由此咱们能够失去:
咱们从切比雪夫不等式中失去的概率上界为:
和咱们的期望值是相等的,证实咱们这样来应用切比雪夫不等式的概率上界是正确的。
这样,咱们就能够通过对Shadowmap进行预处理,来失去软暗影。
咱们的实现过程大抵如下:
- 在光照空间下,将深度值和深度值的平方别离存储到两张flaot格局的Shadowmap中;
- 将两张Shadowmap进行Mipmap解决,或者应用双Pass高斯含糊;
- 在渲染时进行暗影计算,如果以后像素点的深度值小于均匀深度μ,阐明该点没有被暗影遮挡。如果深度值大于均匀深度,就是用后面的公式来计算暗影系数。
左:间接进行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中存储eczi的值,而后对Shadowmap进行Filter解决,就能够失去左边的局部:
从后面的图像中咱们晓得,c的值越大,指数函数的图形就和实在的暗影判断越靠近。不过在理论计算时,因为精度的限度,咱们不能把c的值设置的过大,通常抉择一个适合的值即可。
一个针对ESM的改良,是对深度值的编码做出一些改良,将后果保留在log空间中,这样能够应用更大的c值[11],失去的后果精度天然也会更高:
3. Exponential Variance Shadow Map(EVSM)
EVSM是一种对VSM的改良[12],人们发现,在应用VSM的时候,能够将深度应用一个 wrap函数进行解决,而后间接对wrap后的后果进行VSM中同样的计算解决,能够失去更好的暗影后果。
借鉴ESM中的做法,这个wrap函数就是ecx。在实践中,会应用-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专栏】如果你深怀绝技,爱“搞点钻研”,乐于分享也博采众长,咱们期待你的退出,让智慧的火花碰撞交错,让常识的传递生生不息!
发表回复