一个月前的21日我写完了pcss的作业,满怀信心的直接猛冲第五节课
随后发现自己对于pbr理论还有brdf的理解已经忘光光了,听这节课就像听天书
于是用了半个多月补了pbr的理论,做了一个pbr的渲染器和一个ibl的场景
这次轮到我拿下202的ibl啦
Distance field soft shadow and SDF
DFS有很不错的效率,速度比传统的shadow map要快,也没有SM中自遮挡和悬浮的问题
因为它的实现完全不同于之前提到的shadow map。但是no free lunch,缺点是需要大量的存储。
效果比对如上,DFS看起来是相当棒的
这里的SDF指的是某个点到达物体表面的最短距离
如图所示上图是直接将颜色进行插值的结果,但我们并不想要这种结果,我想要得到一个在中间的边界
因此我们就获取两个图像的SDF,并将其进行插值,最后就能得到我们所需要的中间的边界,同时这也能反应阴影的移动
SDF的好处是可以表示好物体的边界,可以做任意形状的Blending
SDF Usage 1 RAY Marching(sphere tracing)
已知整个场景的sdf,在任意一点我都可以算出一个安全距离(就例如光标的那一点)
因为那一点的sdf指的是到达场景内最近的物体的距离,因此我们就可以得出一个安全距离
在这个安全距离之内不会有其他的任何物体存在(碰不到任何物体)
假如我们有这么一根光线,我们就可以让光线每次按着他的安全距离前进,然后每次再重新前进新的安全距离
直到我已经trace了很远的距离或者sdf已经小到了某种程度
SDF若要在三维场景生成,则需要比较大的存储(毕竟空间中每个点都要储存一个sdf)
SDF可以比较好的支持运动的刚体(得是刚体哦,这也是sdf的好处),但是对于形变的物体得重新生成sdf
SDF Usage 2 Soft Shadow
这样生成的软阴影实际上是不准的,但是效果是不错的看上去也不违和
我们把之前所说的没一点的安全距离往前推一步,得到一个安全角度
这个安全角度越小,则意味着能看到的东西越少,那么这个点的阴影就该越黑
(我们可以想象如果这个点安全角度很大,这意味着我们看光源基本上没什么遮挡,可见性接近1)
那么我们怎么求这个点的安全角度呢,我们可以用ray Marching的方法求出每步的安全距离和安全角度
去其中安全角度的最小值就是我们最终所需要的安全角度,按照这个安全角度我们转化成阴影的软硬程度
但其中所需要用到arcsin是我们希望尽量避免的(计算量大),因此有大佬优化了这个计算(其中p-o是长度)
完全可以用sdf的值除以走过的距离,这样依然可以表示这个安全角度的大小,而不需要求出准确的角度
然后我们乘以一个系数k,再和1做一个min(因为我们不希望visibility超过1)
这个k反应了最终visibility对于安全距离的敏感程度,k越大则最后越敏感
若k为100,为了保证最终区间为(0,1),sdf/p-o的区间就为(0,0.01),则0.01的变化都能引起visibility从0到1
0.01的变化都能改变visibility的0和1,则证明最后的阴影是特别硬的
sdf场最后的表现就像是描绘了物体的表面(实际上也确实如此,毕竟sdf描述了和物体表面的距离)
总结一下用sdf生成的dfs的优缺点吧(有那么些绕了)
优点:用sdf生成的dfs阴影对比传统的shadow map是高质量且快速的
但这种比较是不公平的,因为没有算入sdf生成的时间(我们都是假设sdf已知)
缺点:需要预计算(生成sdf),sdf需要大量的储存空间,存在走样(锯齿)问题
为了解决sdf的存储问题,会用到一些空间划分的hierarchy数据结构,比如八叉树kd树,离场景中物体都很远的点自然就不需要存sdf
这里闫神说的很好:实时渲染渲染从头到尾都是近似
但这些近似不会牺牲很大的质量,同时做的也很聪明,这才是我们要学习的东西
Shading from Environment lighting
核心思想是用环境贴图(spherical map,cube map)来记录场景中往任意方向看所看到的的光照
这意味着所有光照都是无限远的(记得我们之前所说的pbr需要遵循基于物理的光照吗,这里就没那么pbr了)
根据我的图像来取得各个方向上光照的渲染,工业界上称为IBL(Image Based Lighting)
我要算一个点的Shading相当于就是算这个点的Lo了,理所当然是的要解渲染方程
render equation定义内,积分指的是正半球内的所有光照(即和shading point处normal点乘结果大于0的)
所以我要根据图像上给的irradiance,对于p点正半球内的所有光照,最后到达我眼睛(Wo也就是出射方向)的radiance是什么样的
对于求解积分,我们常用的一个通用解法就是蒙特卡洛积分还有重要性采样(之前第一次听这节课讲到这里已经懵了)
这里得好好说明一下,蒙特卡洛积分就是用来求解积分用的思想,是以高效的离散方式对连续的积分求近似
我当然不可能采样大量的样本数据来计算,最后来使得结果收敛近正确的值,所以我还需要一种采样方式
重要性采样是我拿离散数的方式,指的是不改变统计量,只改变概率分布,可以用来降低方差,是蒙特卡洛积分的一种采样策略
但可以的话我还是希望可以避免采样,所以我们会先从避免采样的出发点来延伸
我们首先搬出之前所说的近似方案:
还记得这个公式什么时候准确吗?答案是在g(x)的support比较小或者g的值比较smooth的情况下
BRDF的式子很满足这个性质,因此我们就有了Split Sum大法
Pre-filtering Lighting
第一步因此我们可以做一个拆分,我们把光照的那一项拿出来:
你可能听说过Epic Games的分割求和近似法,将镜面反射积分拆成两个独立的积分,说的就是这个Split Sum
我们先看前面那一项,实际上我们是将一个区域的所有光照积分起来,然后再做一个normalize
求和然后再除以某个数进行normalize,这样求平均的操作你能想到什么呢?没错,那就是filter,卷积!
也就是我需要将ibl的这张图进行模糊(每个点上取周围的一个范围取一个平均然后再写回这个点)
因此将light项拆出来这一步本身就定义了模糊的操作,我们称之为生成/预计算预滤波环境贴图(irradianceMap)
(课程里面没有提到这个说法,这个说法是笔者在learnOpenGL中看到的)
我们可以提前预计算几张不同尺寸的做好模糊的图,等到需要时我再去取
比如说我想要一个半径为x的卷积,我需要在哪张图去取呢?这个时候我们就会想起mipmap的概念
即生成几张不同filter大小的图,要查询时再做三线性插值
其实最后计算时我需要多大的filter取决于微表面模型用到的一个参数粗糙度
因为随着粗糙度的增加,参与环境贴图卷积的采样向量会更分散,导致反射更模糊(微表面模型理论)
最后我们对于卷积的每个粗糙度级别,我们将按顺序把模糊后的结果存储在预滤波贴图的 mipmap 中
左边的图表示我从某个i方向看一个点,最终反射出去的是一个lobe(波瓣)区域,我根据材质的glossy roughness分布一些采样点
我想取最终的结果,相当于对这个波瓣内的这些采样点做一个平均,然后最后得到我shading point的值
而我也可以直接对于environment lighting直接都做好一个filter,在我镜面反射的那个方向取一个准确的值,最终效果是差不多的
有这么个预计算的过程,我就可以在shading的时候直接采样所需要的值,结果合理同时取值也是很效率的√
多提一些课程没提到的内容,随着粗糙度的增加,lobe的大小增加;随着入射光方向不同,lobe形状会发生变化。
大多数光线最终反射到一个基于半程向量向量的lobe内大部分其余的向量都被浪费掉了
采样时尽量以lobe方向选取采样向量是有意义的,这个过程称为重要性采样。
Pre-filtering BRDF
我们前面算完了光照的那一项,成功的避免了大量的采样,接下来我们来看看BRDF项该怎么解决
这里我们的思想和第一步类似,我们也预生成一个类似的什么东西,但这里的预计算会麻烦一点
因为我需要考虑所有的参数所有的可能性,假如我们引入微表面的BRDF(带有菲涅尔项和NDF项)
(之前我写微表面的时候总是下意识的写成pbr,后边改过来了因为实际上微表面是pbr的前提而并非pbr)
菲涅尔项决定了物体的基础反射率(F0)和随着不同角度反射的颜色各不相同
NDF决定了微表面的不同法线分布(可以理解成是和roughness相关的一维函数)
这样一来我们的参数有F0(菲涅尔项),roughness,Wo等变量,想做预计算几乎是不可能的,因此我们还得将式子继续简化
接下来比较高能不太好理解:
我们根据Schlick公式将菲涅尔项替换成右边的R0巴拉巴拉巴拉,R0指的是基础反射率(也就是平常说的F0)
而Rθ指的是θ角时的菲涅尔项,在图形学中角度(入射,出射,半角)是很容易替换的概念
你可以看看右上角那张图观察一下他们的相似程度,
别问公式为什么这样为什么能这样替换,再不行的话直接嗯记能这样子替就是了
我们可以把菲涅尔项单独拆出来,然后用一些简单的数学原理(其实左右相加最后还是等于菲涅尔项,看不懂的话底下有一个更简单的推导)
最后这么一来我成功的将基础反射率(F0啦R0啦)拆到了积分的外面来,F0一般是一个常数,这样积分就不依赖他了
此时cook torrance式子里边的F已经消去了,brdf里面只剩下D和G,而他们都只依赖roughness和cosθ
也就是说此时我们现在剩下的参数cosθ和roughness,此时我们可以对 BRDF 方程求卷积,将其结果打印在一张表内
以cosθ和roughness为坐标轴(也就是我查询的输入)来打印出一张表(纹理),这和之前的卷积环境贴图类似
这种纹理称之为 2D 查找纹理(Look Up Texture, LUT),这张纹理被称为 BRDF 积分贴图
稍后我们会将其用于光照着色器中,以获得间接镜面反射的最终卷积结果
(如何用cosθ和roughness生成最终的卷积结果,也涉及到重要性采样,在202中没有提及)
这一步完成之后,我们同样通过预计算的方式避免了大量的采样
(因为没有用到采样大量数据,因此也没有噪声,得出来的结果还相当的不错,可能这就是为什么unreal的pbr做的这么叼了)
也可以看这个learnOpenGL中给出的具体推导过程(虽然更简短但是我感觉其实看着很易懂而且不劝退):
瞄了一眼下节课虽然标题依然还是Environment Mapping,但实际上已经在讲PRT(precomputed radiance transfer)了
所以202的ibl部分应该到这里就完结了,感觉内容其实并不是那么的全,不过讲的真的很棒
现在是4/15的4:35,明天就可以开整ibl的作业啦