实时阴影渲染(三):软阴影深度校正
上一篇介绍的软阴影技术已经可以生成很好的软阴影
再结合第一篇介绍的PSSM就可以实现不错的阴影效果
但在实际应用中的会遇到一个很重要的问题:阴影渲染中的自阴影问题
这种问题的产生的原因和锯齿原因类似:
视空间中的像素和灯光空间像素不一致
比如视空间中的一块区域(多个像素)在渲染阴影时对应同一个像素,因而仅产生一个深度值(中心深度)
对于灯光空间中倾斜的三角面来说,这些值是应该不同的
一般对这种问题的解决办法是通过一个小的深度偏移量,或者再通过背面渲染缓解这种问题
而采用多个采样的深度软阴影技术,这种问题会更加显著,这样仅采用一个小小的偏移就不够了
但如果深度偏移太大又会导致本该出现阴影的地方没有阴影
下图以水平3像素的软阴影采样为例说明这种问题:
如上图,假设场景中仅存在ABC所在的平面,ABC为相邻的三个阴影像素,
采用水平3采样计算c点阴影时,会得到强度为0.333的阴影,因为 depth(a)>depth(c)+depthBias
这样渲染那出来的结果是:整个平面都在强度为0.333阴影中;而实际上整个平面都不应该有阴影
除非我们将depthBias设的足够大,但AB平面越倾斜、采样数越多所需要的depthBias的值就越大
这样通过增大depthBias的值是不能解决问题的
思考一下,如果我们能知道ac点的深度差dz = depth(c)-depth(a)的值,就可以通过计算比较出:
depth(a) - dz < depth(c)+depthBias;
depth(c) < depth(c)+depthBias;
depth(b) + dz < depth(c)+depthBias;
从而得出整个平面都不在阴影中的结果
计算深度差dz
在GPU设计体系中,片段程序执行时并不知道其它片段的信息
但好在GPU的实现中提供了ddx ddy两个例外的指令,通过这两条指令可以得出相邻片段间的偏导,也就是相邻片段之间的数据差值
因为实际采样一般为3*3,5*5,我们需要计算水平和垂直两个方向上的深度差zx、zy
假设在当前片段所在三角面对应一个阴影纹理像素的深度差为float2(zx, zy), 阴影纹理宽高为float2(w,h)
通过求解二元一次方程组:
ddx(uv) * float2(zx, zy) * float2(w,h)= ddx(z)
ddy(uv) * float2(zx, zy) *float2(w,h)= ddy(z)
就可以得到zx、zy
注:uv 为当前片段的深度纹理uv坐标, z 为当前片段深度值 ,这些都是已知
2017-9-22修改: +方程求解说明
之前的方程写的意思不明确,想表达的是:
ddx(u)*zx*w + ddx(v)*zy*h = ddx(z) 即 x方向z变化量【ddx(z)】 = x方向u变化量【ddx(u)】 * 单位u变化对应的z变化量【zx】 + x向v变化量【ddx(v)】 * 单位v变化对应的z变化量【zy】
ddy(u)*zx*w + ddy(v)*zy*h = ddy(z) 同上
求解我就不写了,相当于求解 方程组:
a* zx + b * zy = c
d* zx + e * zy = f 即利用a b c d e f六个已知量,计算两个未知量zx\zy,但写出来挺长,还不如自己推导便于理解
方程ddx(u)*zx*w + ddx(v)*zy*h = ddx(z) 成立的意思是:
当前片段x方向变化对应纹理坐标uv两个变化,而阴影纹理u、v的变化分别产生各自的z变化值,累加就是ddx(z)