图形 4.3 实时阴影简介
链接: 图形 4.3 实时阴影介绍笔记.
总结实时阴影的优化方案
一.Shadow Mapping 系列技术
1.unity的实时阴影实现(PCF Soft Shadow)
参考链接: Unity实时阴影实现—Shadow Mapping-YOung(知乎).
使用SSAO项目的场景(为显示阴影,着色器材质修改为只接受与产生阴影)
效果显示
1.1渲染视空间深度渲染
1.2.光源的shadow map渲染
1.3.阴影收集计算(Shadows Collector)
1.get√屏幕空间深度,2.get√光源的Shadow Map。3.将二者转到光源空间进行比较,绘制物体时,用物体屏幕坐标UV采样3中生产屏幕空间阴影贴图。
1.4优化部分
1.4.1自阴影问题解决
在灯光设置中,针对阴影映射分辨率问题的优化,Biasl/Depth Bias/Normal Bias
效果:
1.4.2阴影映射形成的走样问题
级联阴影映射 shader cascades
1.4.3重采样误差–Percentage Closer Filtering(PCF)滤波方式解决
关于滤波知识,查看往期课程或查看GAMES101第6节课以及GAMES202实时阴影部分。
左开启:Soft Shadows(Unity中这样写,但不是真实软阴影),右:Hard Shadows
2.Percentage Closer Soft Shadows(PCSS)
(引用自博客园-KillerAery.)
2.1观察解释
Shadow Mapping 还存在硬阴影(Hard Shadow)的问题,因为现实世界的影子往往是软阴影(Soft Shadow)。
一个现实观察是,当投影物与阴影之间的距离越远,则阴影越软(如下图:笔尖阴影由于与笔尖的距离较近,因此阴影边缘较为锐利;而远处笔身阴影则因与笔身距离较远,阴影边缘较为发散且模糊)。
这是因为较大的光源面会有一些区域被遮蔽一部分光又接受一部分光,从而产生半影(Penumbra),直观看就是没那么暗的边缘处阴影。
2.2 Penumbra Size(半影尺寸):理解PCF与PCSS核心区别关键
用二维平面的图去描述,实际上就是光源段 wLight两端与遮挡物连直线后打在被投影物上的即是 半影段 wPenumbra ,也就是说这段半影需要有渐变的阴影效果。假如我们用 PCF 算法中的圆盘半径大小等同于这个半影段的尺寸 wPenumbra,就能实现这段的渐变阴影效果。
现在,由下图的几何关系容易推出:
wPenumbra =(dReceiver −dBlocker )⋅wLight /dBlocker
其中,wLight 是光源面积尺寸,dBlocker 是遮挡物的深度,dReceiver 是被投影物(实际上就是shading point)的深度。
但是 PCF 算法的圆盘半径大小是固定的,因此处处的边缘看起来都带有相同的渐变范围,这和我们看到的笔尖阴影现象不符合(近处边缘渐变应该更少些,远处边缘渐变应该多些),所以我们可以只要根据不同位置动态地修改圆盘半径大小(实际上就是动态地计算 wPenumbra ),这个也就是PCSS的核心部分。
2.3 分块研究–算法核心(如何获取遮挡物 Blocker Search 步骤)
1.使用固定比例
2.动态比例:我们不能简单把一个投影点变换成Shadow Map的坐标后,直接拿单个坐标采样 ShadowMap 的深度来作为 dBlocker 。这是因为投影点的单次采样实际上就是单一直线连向了光源面的中心,而这条直线要是没有碰到遮挡物(即 dBlocker=dReceiver ),从而得出该投影点为全亮的结论。
但实际很多场景中(如下图),投影点和光源面处处连线后会发现有相当一部分光线会碰到遮挡物,因此该投影点应该属于半影范围内。
为此,我们可以对 ShadowMap 的一定范围内进行多重采样,每次采样得到的深度若小于 dReceiver 则认为遇到遮挡物并算入平均遮挡深度的贡献,这样多重采样之后得到的平均遮挡深度就作为 dBlocker。
如何确定采样的范围半径呢?两个参数决定:wLight 的尺寸、投影点与光源的距离
SampleSize = wLight ⋅ zReceiver ⋅ c
这样,计算 Blocker 平均遮挡深度的整个过程为:
/*
https://www.cnblogs.com/KillerAery/p/15201310.html#percentage-closer-soft-shadows%EF%BC%88pcss%EF%BC%89
*/
float findBlocker( sampler2D shadowMap, vec2 uv, float zReceiver ) {
float dBlocker = zReceiver * 0.01;
const float wLight = 0.006;
const float c = 100.0;
float sampleSize = wLight * zReceiver * c;
float sum = 0.01; // 取0.01一是为了避免出现0除问题,二是当多重采样没有贡献时的dBlocker/sum将等于zReceiver
for(int i = 0;i<BLOCKER_SEARCH_NUM_SAMPLES;++i){
float depthInShadowmap = unpack(texture2D(shadowMap,uv+disk[i]*sampleSize).rgba);
if(depthInShadowmap < zReceiver){
dBlocker += depthInShadowmap;
sum += 1.0;
}
}
return dBlocker/float(sum);
}
2.4 PCSS 算法过程
Percentage Closer Soft Shadows(PCSS) 的算法过程:
1.**Blocker Search:**通过多重采样,计算出平均遮挡深度 dBlocker
2. **Penumbra Size:**计算圆盘半径大小 wPenumbra = (dReceiver −dBlocker )⋅wLight /dBlocker
3. **Filtering:**通过多重采样,计算出平均 Visibility(实际上就是调用PCF算法)
/*
https://www.cnblogs.com/KillerAery/p/15201310.html#percentage-closer-soft-shadows%EF%BC%88pcss%EF%BC%89
*/
float visibility_PCSS(sampler2D shadowMap, vec4 coords){
poissonDiskSamples(coords.xy);
// STEP 1: avgblocker depth
float dBlocker = findBlocker(shadowMap,coords.xy,coords.z);
// STEP 2: penumbra size
const float wLight = 0.006;
float penumbra = (coords.z-dBlocker)/dBlocker * wLight;
// STEP 3: filtering
const float bias = 0.005;
float sum = 0.0;
for(int i = 0;i<PCF_NUM_SAMPLES;++i){
float depthInShadowmap = unpack(texture2D(shadowMap,coords.xy+disk[i]*penumbra).rgba);
sum += ((depthInShadowmap + bias)< coords.z?0.0:1.0);
}
return sum/float(PCF_NUM_SAMPLES);
}
PCSS 算法问题:原始性能消耗严重危机。
3 针对PCSS与PCF算法的多重采样优化:Variance Soft Shadow Mapping(VSSM)
3.1 一句话概括
Variance Soft Shadow Mapping(VSSM) :简单来说,VSSM 算法就是依据 ShadowMap 的深度符合正态分布的假设来快速完成 PCSS 中的第一步(Blocker Search)和第三步(PCF算法)的一种阴影算法。
3.2 概论(VSSM与PDF与CDF)
1.为了避免多重采样的计算,Variance Soft Shadow Mapping(VSSM) 假定一定范围内的深度的分布符合 正态分布(Normal Distribution)
2.那么只要知道该段范围的 均值(实际上就是期望值)E 、方差 Var,就能先得到该范围的正态分布模型(即知道对应的 概率密度函数 PDF)。
其中,μ=E,σ2=Var。
3.接着可以通过该正态分布模型的 累计分布函数(即 CDF),就能快速推算出该范围内有多少比例的 x 大于(或小于)给定的某个值。
3.3 补充说明
3.3.1计算平均值 & 方差
为了快速查询得到某段范围的均值、方差,我们可以先选以下一种数据结构来快速查询 Shadow Map 某段范围的均值(期望值)E(X) 。
硬件 Mipmap:当 Shadow Map 更新时,需要重新生成 Mipmap,不过GPU硬件实现的 Mipmap 算法非常快的开销非常小;查询某段方形范围时,需要根据方形中心所在的位置(相对于周围四个纹素的坐标)、上下层级做三线性插值(Trillinear interpolation),得到的结果即是近似的均值(期望值)。
前缀和数组(Summed Area Tables/SAT):当 Shadow Map 更新时,需要重新进行二维前缀和计算;需要编写 Compute Shader 实现该算法,比Mipmap方法更慢一些,但百分百精准;查询某段方形范围时,就可以通过如下图方法快速查询得到某段范围的总和,除掉范围面积就能得到均值(期望值)。
我们需要存储 E(X)、E(X2) ,这样就能计算某段范围的平均值、方差:
1.平均值 E(X)
2.方差 Var(X)=E(X2)−E2(X)
3.3.2 计算累计分布函数(CDF)
有了上面的期望值与方差,我们就能确定一个正态分布。但是它对应的 CDF 函数是没有解析解的,而有数值解(称为 Error Function),但是计算比较繁琐。
为简化计算积分的过程,使用了切比雪夫不等式
切比雪夫不等式:.
将这个不等式改造一下,就成了一个大胆的近似公式:
注意:这里求的是 x>t 的部分,即 P(t)=1−CDF(t)。
当然这个近似公式肯定不是精确的,但是计算开销非常小,也就被用在 VSSM 算法中。
3.3.3 加速 VSSM (Blocker Search )算法
PCSS 算法中的 Blocker Search 步骤:在一定范围内多重采样,每次采样得到的深度若小于 dReceiver 则认为遇到遮挡物并算入平均遮挡深度的贡献,这样多重采样之后得到的平均遮挡深度 Zocc 就作为 dBlocker。
如下图5X5的采样结果若设 dReceiver 为7,那么平均遮挡深度 zocc 则为红色部分的平均值。
设该采样范围的面积为 N,无遮挡的面积占有 N1,有遮挡的面积则占有 N2 ,则有:
我们做出两个假设:
- 这个假设基于认为深度分布为正态分布,通过切克比夫不等式获得近似解(即上面两节的内容)。
- 这个假设基于认为绝大部分没被遮挡的情况都属于同一个深度(相当于在同一个垂直于光方向的平面),即可认为均为深度 dReceiver。
那么 VSSM 加速该算法的公式表示为:
3.3.4 VSSM 的缺陷
- 并不是任何深度的分布都是符合正态分布模型的,例如对于图右的简单几何体反而用正态分布表示会很不适合。
- 漏光(Light Leaking)现象,在一些应当被阴影完全遮蔽的内部有可能仍产生亮度。
- 在加速 Blocker Search 算法中的假设 zunocc=dReceiver 基于认为绝大部分没被遮挡的情况都属于同一个深度,但实际上有些不被遮挡的地方深度并不等于 dReveiver 。
4. 解决VSSM缺陷算法–Moment Shadow Mapping
Moment Shadow Mapping 正是为了解决 VSSM 缺陷的一种算法,它主要想法是:使用高阶的矩去描述一个分布的 CDF。这样就能通过记录 m 阶的矩,就能复原成足够接近实际 CDF 函数的效果,从而能适应不同的深度分布模型(有些地方可能接近正态分布,有些地方可能奇奇怪怪的分布)。
Moment Shadow Mapping将使用最简单的形式来标识矩:z,z2,z3,z4,…
实际上,VSSM 本质便是记录 2 阶的矩来复原 CDF 函数,而 Moment Shadow Mapping 一般使用4阶的矩就已经足够接近实际 CDF 了。
虽然 Moment Shadow Mapping 效果相当不错,很好的解决了 VSSM 绝大部分缺陷,但是它仍需要相当的额外空间开销和重建矩的额外性能开销。
二. Distance Field Soft Shadows(有向距离场的软阴影)
链接: GDC2018分享-基于GPU的光线追踪.
1. Distance Field Soft Shadows主要想法:
- 将点 o(Shading Point)与光源面中心点 plight 相连形成一条方向为 l 的中心线段,而这条中心线上各个点 pi 都可以通过 SDF 查得与其最近几何物体的距离并且推算出安全角度(点o 能打到光源面的直线与中心线的最大夹角)为θi,则
- 那么所有这些点中对应的安全角度之中取最小的安全角度 θ=min{θi} ,这个安全角度与最大角度的比例决定了光源面的光照覆盖率,也就决定了点 o 的Visibility(可见性)。
2. Distance Field Soft Shadows 算法过程
具体算法过程:
-
将 o 点(shading point)设为第一个步进点,即 p0=o
-
每次算出下一个步进点 pi+1=pi+l⋅SDF(pi) 并记录安全角度
-
重复 “步骤2”,直到满足
-
取所有次步进的最小安全角度 θ=min{θi} ,则可见度则为 Visibility = θ/c (其中 c 为点 o 与光源面连接的最大角度)
3. 一步一步慢慢说
3.1 Signed Distance Field(有向距离场)
3.2 Sphere Tracing(跟踪范围)
3.3 Distance Field Soft Shadows(有向距离场的软阴影)的优化
让我们来看如何基于SDF和ray marching实现软阴影。假设我们已经有了场景的SDF,通过使用函数float map(vec3 p) 进行查询。那么map函数中则包括了所有的几何信息。通常来说,当要计算某点p的阴影信息时,我们可以通过朝着light vector 进行raymarch,直到找到一个相交。
上面的方法只会生成准确的硬阴影,缺乏真实感
优化后:
实际上,计算某个点 pi 的安全角度时,直观的几何关系便是:
而在实践中,往往会使用:
这样的近似公式实际效果相当接近原几何关系,而且也能减少复杂的 arcsin 运算开销,最后它还能通过 k 这个参数来调整阴影的硬软程度。
如下图分别为 k=32 、k=8、k=2 的效果:
进一步优化
4. 使用 Distance Field Soft Shadows 的优劣
好处很多:
- 计算阴影很快(假设已经生成了SDF的情况下,比传统Shadow Mapping类技术是要快的多)
- 阴影质量很高,而且完美解决 Shadow Ance / Peter Panning / 采样噪声等传统Shadow Mapping会出现的问题
那么代价是什么:
- SDF 需要预计算,这就意味着场景物体需要是静态的,当然也可以使用一些算法使能和动态物体相结合,尽量减少重新生成SDF的成本。
- SDF 需要较大的存储空间(一般采用三维数组表示空间各个网格的SDF值,但是可以使用八叉树等空间数据结构或者其它方法做进一步优化)。
实时阴影系统实现
未完待续。。。。