LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

Games202 Lecture5 (Environment Mapping)

2022/4/14

一个月前的21日我写完了pcss的作业,满怀信心的直接猛冲第五节课

随后发现自己对于pbr理论还有brdf的理解已经忘光光了,听这节课就像听天书

于是用了半个多月补了pbr的理论,做了一个pbr的渲染器和一个ibl的场景

这次轮到我拿下202的ibl啦

Distance field soft shadow and SDF

DFS有很不错的效率,速度比传统的shadow map要快,也没有SM中自遮挡和悬浮的问题

因为它的实现完全不同于之前提到的shadow map。但是no free lunch,缺点是需要大量的存储

image-20220414171957924

效果比对如上,DFS看起来是相当棒的


这里的SDF指的是某个点到达物体表面的最短距离

image-20220414175724579

如图所示上图是直接将颜色进行插值的结果,但我们并不想要这种结果,我想要得到一个在中间的边界

因此我们就获取两个图像的SDF,并将其进行插值,最后就能得到我们所需要的中间的边界,同时这也能反应阴影的移动

SDF的好处是可以表示好物体的边界,可以做任意形状的Blending


SDF Usage 1 RAY Marching(sphere tracing)

image-20220414180250039

已知整个场景的sdf,在任意一点我都可以算出一个安全距离(就例如光标的那一点)

因为那一点的sdf指的是到达场景内最近的物体的距离,因此我们就可以得出一个安全距离

在这个安全距离之内不会有其他的任何物体存在(碰不到任何物体)

假如我们有这么一根光线,我们就可以让光线每次按着他的安全距离前进,然后每次再重新前进新的安全距离

直到我已经trace了很远的距离或者sdf已经小到了某种程度

SDF若要在三维场景生成,则需要比较大的存储(毕竟空间中每个点都要储存一个sdf)

SDF可以比较好的支持运动的刚体(得是刚体哦,这也是sdf的好处),但是对于形变的物体得重新生成sdf


SDF Usage 2 Soft Shadow

这样生成的软阴影实际上是不准的,但是效果是不错的看上去也不违和

image-20220414182509470

我们把之前所说的没一点的安全距离往前推一步,得到一个安全角度

这个安全角度越小,则意味着能看到的东西越少,那么这个点的阴影就该越黑

(我们可以想象如果这个点安全角度很大,这意味着我们看光源基本上没什么遮挡,可见性接近1)

image-20220414183056119

那么我们怎么求这个点的安全角度呢,我们可以用ray Marching的方法求出每步的安全距离和安全角度

去其中安全角度的最小值就是我们最终所需要的安全角度,按照这个安全角度我们转化成阴影的软硬程度

但其中所需要用到arcsin是我们希望尽量避免的(计算量大),因此有大佬优化了这个计算(其中p-o是长度)

image-20220414184129846

完全可以用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,则证明最后的阴影是特别硬的

image-20220414200759280

sdf场最后的表现就像是描绘了物体的表面(实际上也确实如此,毕竟sdf描述了和物体表面的距离)


总结一下用sdf生成的dfs的优缺点吧(有那么些绕了)

image-20220415012626846

优点:用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)

image-20220415014616688

我要算一个点的Shading相当于就是算这个点的Lo了,理所当然是的要解渲染方程

render equation定义内,积分指的是正半球内的所有光照(即和shading point处normal点乘结果大于0的)

所以我要根据图像上给的irradiance,对于p点正半球内的所有光照,最后到达我眼睛(Wo也就是出射方向)的radiance是什么样的

对于求解积分,我们常用的一个通用解法就是蒙特卡洛积分还有重要性采样(之前第一次听这节课讲到这里已经懵了)

这里得好好说明一下,蒙特卡洛积分就是用来求解积分用的思想,是以高效的离散方式对连续的积分求近似

我当然不可能采样大量的样本数据来计算,最后来使得结果收敛近正确的值,所以我还需要一种采样方式

重要性采样是我拿离散数的方式,指的是不改变统计量,只改变概率分布,可以用来降低方差,是蒙特卡洛积分的一种采样策略

但可以的话我还是希望可以避免采样,所以我们会先从避免采样的出发点来延伸


我们首先搬出之前所说的近似方案:

image-20220415022231722

还记得这个公式什么时候准确吗?答案是在g(x)的support比较小或者g的值比较smooth的情况下

BRDF的式子很满足这个性质,因此我们就有了Split Sum大法


Pre-filtering Lighting

第一步因此我们可以做一个拆分,我们把光照的那一项拿出来:

image-20220415022538992

你可能听说过Epic Games的分割求和近似法,将镜面反射积分拆成两个独立的积分,说的就是这个Split Sum

我们先看前面那一项,实际上我们是将一个区域的所有光照积分起来,然后再做一个normalize

求和然后再除以某个数进行normalize,这样求平均的操作你能想到什么呢?没错,那就是filter,卷积!

也就是我需要将ibl的这张图进行模糊(每个点上取周围的一个范围取一个平均然后再写回这个点)

因此将light项拆出来这一步本身就定义了模糊的操作,我们称之为生成/预计算预滤波环境贴图(irradianceMap)

(课程里面没有提到这个说法,这个说法是笔者在learnOpenGL中看到的)

image-20220415024250764

我们可以提前预计算几张不同尺寸的做好模糊的图,等到需要时我再去取

比如说我想要一个半径为x的卷积,我需要在哪张图去取呢?这个时候我们就会想起mipmap的概念

即生成几张不同filter大小的图,要查询时再做三线性插值

其实最后计算时我需要多大的filter取决于微表面模型用到的一个参数粗糙度

因为随着粗糙度的增加,参与环境贴图卷积的采样向量会更分散,导致反射更模糊(微表面模型理论)

最后我们对于卷积的每个粗糙度级别,我们将按顺序把模糊后的结果存储在预滤波贴图的 mipmap 中

image-20220415025145837

左边的图表示我从某个i方向看一个点,最终反射出去的是一个lobe(波瓣)区域,我根据材质的glossy roughness分布一些采样点

我想取最终的结果,相当于对这个波瓣内的这些采样点做一个平均,然后最后得到我shading point的值

而我也可以直接对于environment lighting直接都做好一个filter,在我镜面反射的那个方向取一个准确的值,最终效果是差不多的

有这么个预计算的过程,我就可以在shading的时候直接采样所需要的值,结果合理同时取值也是很效率的√

多提一些课程没提到的内容,随着粗糙度的增加,lobe的大小增加;随着入射光方向不同,lobe形状会发生变化。

大多数光线最终反射到一个基于半程向量向量的lobe内大部分其余的向量都被浪费掉了

采样时尽量以lobe方向选取采样向量是有意义的,这个过程称为重要性采样。


Pre-filtering BRDF

我们前面算完了光照的那一项,成功的避免了大量的采样,接下来我们来看看BRDF项该怎么解决

image-20220415030411332

这里我们的思想和第一步类似,我们也预生成一个类似的什么东西,但这里的预计算会麻烦一点

因为我需要考虑所有的参数所有的可能性,假如我们引入微表面的BRDF(带有菲涅尔项和NDF项)

(之前我写微表面的时候总是下意识的写成pbr,后边改过来了因为实际上微表面是pbr的前提而并非pbr)

菲涅尔项决定了物体的基础反射率(F0)和随着不同角度反射的颜色各不相同

NDF决定了微表面的不同法线分布(可以理解成是和roughness相关的一维函数)

这样一来我们的参数有F0(菲涅尔项),roughness,Wo等变量,想做预计算几乎是不可能的,因此我们还得将式子继续简化

接下来比较高能不太好理解:

image-20220415034221825

我们根据Schlick公式将菲涅尔项替换成右边的R0巴拉巴拉巴拉,R0指的是基础反射率(也就是平常说的F0)

而Rθ指的是θ角时的菲涅尔项,在图形学中角度(入射,出射,半角)是很容易替换的概念

你可以看看右上角那张图观察一下他们的相似程度,

别问公式为什么这样为什么能这样替换,再不行的话直接嗯记能这样子替就是了

image-20220415035919603

我们可以把菲涅尔项单独拆出来,然后用一些简单的数学原理(其实左右相加最后还是等于菲涅尔项,看不懂的话底下有一个更简单的推导)

最后这么一来我成功的将基础反射率(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中给出的具体推导过程(虽然更简短但是我感觉其实看着很易懂而且不劝退):

image-20220415034341690


瞄了一眼下节课虽然标题依然还是Environment Mapping,但实际上已经在讲PRT(precomputed radiance transfer)了

所以202的ibl部分应该到这里就完结了,感觉内容其实并不是那么的全,不过讲的真的很棒

现在是4/15的4:35,明天就可以开整ibl的作业啦