山大软件23年下半年实时绘制

wl老师讲课很赞

同样,图片丢失太多,结合PPT复习吧

阴影Shadow

投射阴影Projection Shadows

遮挡物(occluders)是遮挡光线形成阴影的对象,接收体(receivers)是接受阴影投射的对象。本影(umbra)是完全被遮挡物遮挡而产生阴影的部分,半影(penumbra)则是部分阴影区域,本影和半影的区域范围随光源面积,光源与遮挡物,以及遮挡物与接受者的距离变化而改变

Untitled

过程:绘制投影平面,绘制投影三角面片,绘制其他几何体

阴影体Shadow Volume

Depth Pass/Fail method

Depth Pass计算视点和着色点之间的阴影体表面,前向面+1,后向面-1 Depth Fail计算视点和着色点这条射线上的着色点之后的阴影体表面,前向面-1,后向面+1

两者的区别在于

Depth Pass在处理视点在阴影体内的情况时会导致计数错误

而Depth Fail在处理阴影体时要求阴影体必须封闭,否则在视线方向与阴影体表面平行时可能无法射出阴影体。

Stencil Buffer:模版缓冲

模板缓冲类似于深度缓冲。事实上,它使用深度缓冲的一部分(因此,深度缓冲常常被称做depth-stencil缓冲)模板测试的结果决定了像素的颜色值是否要被写入到渲染目标,像素的深度值是否要被写入深度缓冲。

在实际流水线中这一步发生在片元着色器之后,深度测试之前

Untitled

具体流程

  1. 关闭光源,关闭颜色缓冲区写入,开启深度缓冲区写入。以正常相机视角绘制场景,绘制完成后可以获得当前相机视角下的深度缓冲区深度值。

  2. 关闭深度缓冲区写入功能,开启深度缓冲区测试,清理模板缓冲区(默认为0)

Z-Pass方式:

  1. 绘制阴影体所有正面(法线方向正对相机,也就是渲染面正对相机的面,如下图中下面A处的红色箭头所在面),通过深度测试的像素模板值自增1(当然没通过深度测试的保持当前值不变)。

  2. 绘制阴影提所有背面(法线方向背对相机,也就是渲染面背对相机的面,如下图中下面C处的红色箭头所在面),通过深度测试的像素模板值自减1(当然没通过深度测试的保持当前值不变)。

  3. 此时模板缓冲区值不为0的部分就是在阴影体内的像素(正面+1 反面-1如果正反中间没有物体(深度值测试相同)则模板值正好为0,否则模板值不为0)。

  4. 正常绘制场景(带光照等计算),启用模板缓冲测试,模板值不为0的像素做阴影化处理(例如使用黑色表示阴影)

Untitled

Z-Fail方式:

  1. 绘制阴影体所有背面(法线方向背对相机,也就是渲染面背对相机的面,如下图中下面C处的红色箭头所在面),深度测试失败的像素模板值自增1(当然通过深度测试的保持当前值不变)。

  2. 绘制阴影提所有正面(法线方向正对相机,也就是渲染面正对相机的面,如下图中下面A处的红色箭头所在面),深度测试失败的像素模板值自减1(当然没通过深度测试的保持当前值不变)。

  3. 此时模板缓冲区值不为0的部分就是在阴影体内的像素(正面+1 反面-1如果正反中间没有物体(深度值测试相同)则模板值正好为0,否则模板值不为0)。

  4. 正常绘制场景(带光照等计算),启用模板缓冲测试,模板值不为0的像素做阴影化处理(例如使用黑色表示阴影)

Untitled

阴影图Shadow Mapping

SM

Shadow Mapping是经典的2-pass算法

1.第一个pass是通过光源进行,生成阴影图

2.第二个pass通过相机(视点)进行,利用阴影图确定着色点的可见性

同时它也是一个图像(2D)空间的算法

1.优点是不要求任何额外的场景几何的知识(这部分知识来自阴影图)

2.缺点是容易造成自遮挡和走样

  • 自遮挡

    成因:Shadow Map记录的深度不连续,(数据精度问题)在Light与平面趋于平行时最严重(深度变化越剧烈就越严重)

    解决方法:

    1.使用分辨率更高的Shadow Map,(提高数据精度)

    2.在深度比较时添加bias,即在比较时模糊比较的严格性,这样也可能会导致出现悬浮阴影(物体与阴影割裂)的问题

    3.在生成SM时不仅记录最小深度,同时记录第二小的深度,在深度比较时,使用前面二者的平均值,可以有效地消除自遮挡现象。此方法不使用bias。但是开销大

    Untitled

走样:说白了就是精度问题 解决方案就是对visibility项做滤波

  • PCF(反走样)(软阴影

    PCF的思想很简单,在Shadow Mapping深度比较的阶段,不仅考虑当前着色点深度与对应shadow map上那一个像素深度值的比较,我还考虑其周围的像素值记录的深度(滤波核内的其他像素),最终返回一个代表visibility的由0-1组成的矩阵,最后求平均值作为真正的visibility。

    考虑到性能问题,在滤波核比较大的情况下,可以考虑对滤波核内的像素再进行一次采样来计算最后的visibility[0,1]

PCSS

光源照射下产生的阴影区域,由于光源存在一定的体积,所以主要分为本影区和半影区。在本影区中,无法接收到来自光源的任何光线,看不到光源的任何部分;而在半影区中,光源仅部分被遮挡,因此与本影相比,半影的阴影要浅得多。

距遮挡物越远的阴影区域,阴影越软。 阴影接受物与阴影投射物的距离越小,阴影越锐利。

好好好,下面来看PCSS过程:

  • STEP1:Blocker Search

    简单来讲就是算一下在一个范围内(动态大小)里的所有遮挡物的平均深度

    根据光源大小确定这块区域大小(虽然我们用的点光源,但可以定义一个虚拟的面光源尺寸) 如下图shadow map其实就是在light为原点的近平面上记录着的,我们可以在空间中,把着色点连接到light的端点处,看连线所围成的这个棱锥体在近平面覆盖多少区域,就用这个区域计算平均深度。这是很对的思想,因为理论上只有这个棱锥内部的物体才有可能挡住着色点

    Untitled

  • STEP2:Penumbra Estimation

    用平均遮挡物深度计算PCF用的滤波核大小

  • STEP3:PCF

    你就拿刚才那滤波核算就完了

    问题是你要不要全采样

    全采样就慢,随机采样就有噪声,工业的处理的方式就是先稀疏采样得到一个有噪声的visibility的图,接着再在图像空间进行降噪。

  • 缺点

    1,3两步速度挺慢的

    第一步要多次采样去查询深度信息

    第三步阴影越软,滤波核尺寸越大,采样就多,他就慢

VSSM

VSSM的做法采用了非常多的大胆假设,同时非常的快,没有任何噪声,本质上其实也没有用正态分布,是直接用切比雪夫不等式来进行近似。但是现在最主流的方法仍然是PCSS。

优化的PCSS,看看怎么事儿

  • 对第三步的:

    1.计算可视性太慢了,那咋整呢,切比雪夫秒了

    $$ P(X>t)\le{\sigma^2\over\sigma^2+{(t-\mu)}^2} $$

    t是啥呢,t是现在那个着色点的深度,X是各个采样点的深度,那好办了,给右边算出来当可视性就成了,key idea就是咋个快速算均值和方差

    2.咋个算呢,首先我们知道有$\mu$咋算$\sigma$,没错就是$Var(X)=E(X^2)-E^2(X)$ 也就是说只有一个depthMap存$X$不太够,再加个通道存$X^2$就能算方差了。

    3.你猜怎么着,那还是不够快,我们可以想到一些数据结构来加速均值的计算,比如MIPMAP或者SAT(二维前缀和)做到一个区域内的均值的快速查询

    4.做完上面这些算是差强人意完成优化,那我们就使用切比雪夫那个值就省去了原来要做的随机采样或者是全采样。

  • 对第一步的:

    虽然第一步的范围并不是滤波核,但是这儿同样用切比雪夫秒了

    对这个范围里的像素可以如下方式建立等式

    $$ {N_1\over N}Z_{unocc}+{N_2\over N}Z_{occ} = Z_{avg} $$

    这里面我们要求的是$Z_{occ}$,其中${N_1\over N} = P(x>t)$ ${N_2\over N} =1 - P(x>t)$

    那我们继续做个大胆的假设认为$Z_{unocc}$就是t,这式子就解出来了,显然在接受面为曲面或者与光源不平行时会出问题,但是他快这么多你要啥自行车。

pass1:生成阴影图时,同时生成阴影图深度值的平方的map

pass2:计算出分布函数(均值,方差)(mipmap)

pass3:使用切比雪夫不等式,直接算出遮挡的百分比

  • VSSM和PCSS比较:

    PCSS:存储:阴影图

              计算:第三步对范围查询,慢。第一步。。。
    

    VSSM:存储:阴影图,(阴影图)**2,MipMap

               计算:第三步对点查询O(1),第一步
    

    $N_1/NZ_{unocc}+N_2/NZ_{occ}=Z_{avg} 近似Z_{unocc}为t$

MSM(不考不看了)

DFSS

来点距离场,那么我们先定义一下这玩意SDF(Signed Distance Function)

首先,Distance Function是空间中任何一点到某个物体的最小距离,同时这个距离是有方向的,对于封闭物体而言,可以理解为物体内部为负距离,物体外部为正距离。

那么这玩意儿是干嘛滴呢,其实可以用来

1.Ray Marching:假设我们已经知道场景的SDF,现在有一根光线,我们试图让光线和SDF所表示的隐含表面进行求交,也就是我们要用sphere tarcing(ray marching)进行求交,那么显然,如果我们在射线方向上每次前进SDF的距离,肯定不会出现穿过某些平面的问题,等到SDF足够小时也说明离物体足够近了,在这儿在进行求交就节省了算力,如果在一个方向trace了非常远的距离啥也trace不到,那就说明你完犊子啦,这线上啥也没有。

2.软阴影:我们从着色点向面光源打出一条光线,光线上的一点a会有一个a的SDF值,这就说明a点在以SDF_a为半径的球内不会碰到物体,那么着色点到这个球面上的最大角度就记为这个点的Angle,那在这条光线上的最小Angle为着色点的Safe Angle,这个值越小阴影越趋近于硬阴影,这个最小Angle的计算方法也是近似,在使用Ray Marching的过程中对光线上的点进行计算,取其中最小的近似代替光线上最小的点作为Safe Angle,那我们又不喜欢使用反三角算度数,因为太慢了,于是又有了渲染界的近似

$$ min\{{k\cdot SDF(p)\over p-o},1.0\} $$

上面这个东西就作为这个着色点的可见性了

SDF是一个快速的高质量的软阴影生成方法(比shadow map快是忽略了SDF生成的时间),但是在存储上的消耗非常大,而且生成SDF的花的时间也要很久,SDF是预计算,在有动态的物体的情况就得重新计算SDF。

环境光Environment Lighting

着色Shading

用一张环境光贴图作为场景中所有物体的光源

通常情况来讲这里的着色就是环境光着色

Unshadow Shading

unshadow挺好,不用算可见性项了,那么渲染方程就被简化为

$$ L_0(p,\omega_0)=\int_{\Omega^+}L_i(p,\omega_i)f_r(p,\omega_i,\omega_o)\cos\theta_id\omega_i $$

那这个式子还是没法直接用,我们只能考虑通过大量的采样得到Texture上的光照信息,这种费力的操作显然不能满足实时的需求,怎么对上面的式子进行优化呢

我们又要用到一些渲染近似(差不多得了)的方法来加速计算

我们看到了$\int_{\Omega^+}f_r(p,\omega_i,\omega_o)\omega_i$这一部分,咋整呢

  1. 如果BRDF是glossy的,那说明球面上的积分域很小($\Omega^+$小),虽然BRDF不是很smooth,但是也能接受
  2. 如果BRDF是diffuse的,那说明积分在整个球面上进行,但是BRDF是个常数,无敌smooth

那就好办了,给这一项拆出来就是了,也就是The Split Sum的方法,有

$$ L_0(p,\omega_0)\simeq {\int_{\Omega_{f_r}}L_i(p,\omega_i)d\omega_i\over\int_{\Omega_{f_r}}d\omega_i}\cdot\int_{\Omega^+}f_r(p,\omega_i,\omega_o)\cos\theta_id\omega_i $$

现在我们可以具体讲讲这两部分怎么求解了

1.左半部分的计算 还是要先对两种不同的BRDF进行讨论,如果BRDF为glossy,这部分求解的应该就是镜面反射方向一小圈的Light求和再平均;如果BRDF为diffuse,那就直接对整张Texture做均值,所以这部分总的来说就是采样BRDF范围的Texture上的Light然后求均值,但是我们还是不想采样啊,那就又有了一个办法 Pre filtering 预计算环境光照 根据原始环境光贴图,预先计算一系列不同大小卷积核滤波的环境光图像,查询时可以通过三线性插值得到所有位置的Light

2.右半部分计算 先来点微表面BRDF(Microfacet BRDF)

$$ f(i,o) = {F(i,h)G(i,o,h)D(h)\over 4(n,i)(n,o)} $$

  • 这里面都是啥

    F(i,h):Fresnel Term 菲涅尔项。给定入射方向 ωi 和半程向量h方向,返回反射方向的反射率,值域[0,1] D(h) :Distribution of normals 微表面的法线分布。给定一个半程向量h,返回法线位于该方向的微表面数量 G(i,o,h):Shadowing Masking Term 阴影遮蔽,也叫几何项。 当入射光以非常平(Grazing Angle 掠射角度)的射向表面时,有些凸起的微表面就会遮挡住后面的微表面。这一项其实就起修正作用,当入射为掠射角度时,这一项可能就会返回一个比较小的数比如0.5 0.4之类的,把BRDF的返回值拉低一点。 如果没有这一项,假如我们渲染一个球,球的边界上,就会是掠射角度,会特别亮。(看分子,菲涅尔项F(i,h) 返回值接近1,D(h) 的返回值也会很大,因为微表面法线分布基本是正态分布,又因为入射很平,半程向量很居中,微表面的数量是最多的,返回值就大。最终就造成BRDF返回值比较大,反射光的能量只会减少很少的量,因此边缘着色特别亮。)

  • 菲涅尔项可以用Schlick’s approximation来表示

  • 两个变量:入射角度θ、基础反射率Ro(入射角度为0的)

$$ R(\theta) = R_0+(1-R_0){(1-\cos\theta)}^5 \\R_0 = {({n_1-n_2\over n_1+n_2})}^2 $$

  • NDF Term 表面法线分布,有很多不同的分布函数,这里以beckmann distribution为例
  • 两个变量:粗糙度α、半程向量与法线的夹角

$$ D(h) = {e^{-{\tan^2\theta_h\over\alpha^2}} \over \pi\alpha^2\cos^4\theta_h} $$

NDF中$\theta_h$可以用入射角$\theta$通过一定的方式来表示,从而降低至三维,对预计算来说,三维依然太高,如何能将参数的纬度降低至2维?

把积分中的BRDF项做一个处理

$$ \int_{\Omega^+}f_r(p,\omega_i,\omega_o)\cos\theta_id\omega_i \simeq R_0\int_{\Omega^+}{f_r\over F} (1-(1-\cos\theta_i)^5)\cos\theta_id\omega_i+\int_{\Omega^+}{f_r\over F} (1-\cos\theta_i)^5\cos\theta_id\omega_i $$

此时对于这两个积分,参数空间仅剩2维:入射角$\theta_i$、NDF中的粗糙度$\alpha$

预计算拆分后的这两个积分的计算结果的所有组合,由于其只包含2个变量(入射角、粗糙度),所以只需要两张texture(分别对应两个积分的预计算结果),甚至省一点,一张texture的两个通道存放预计算结果。渲染时根本不用计算,直接查询

shadow Shading

这里其实主要就是用到PRT的方式来计算,在这先介绍一下SH球谐函数

球谐函数—Spherical Harmonics,简称SH,是一系列的二维基函数,并且每个基函数都是定义在球面上的。此处二维怎么理解呢?球谐函数的二维基函数实际上定义了三维空间中的一个方向,很容易理解,我们知道两个参数$\theta$和$\phi$就可以表示三维空间中的一个方向。

Diffuse的BRDF是smooth的,这点我们之前提到过,也就是说,Diffuse的BRDF是低频的,那我们求Environment的Shading的时候,要把BRDF和环境光的函数相乘然后半球积分,这正是我们之前提到的乘积再积分(product integral)的操作。那么此时,因为BRDF是低频的,我们可以把BRDF看作一个低通滤波器,当我们把它投影到球谐函数的时候,只需要0,1,2阶就足够了,因为Diffuse的BRDF是低频的,所以根本没有高阶基函数描述的高频信息。一个更通用的结论,对于任何的一个光照条件,只要BRDF是Diffuse的,我们都可以用前3阶的SH来描述光照来照亮Diffuse的物体。

Shadow/GI: Precomputed Radiance Transfer (PRT)

我们还是从渲染方程入手,它此时包括Light,visibility以及BRDF三项,这三项又都可以表示为一个球面函数,也就是一张CubeMap,那最终结果的颜色自然就是这三张CubeMap的乘积,但是这显然有点浪费时间,我们不妨将渲染方程拆成两部分:

Untitled

在这种条件下,我们假设只有Light可以改变,而其他条件不变(即动态光源静态场景),这样在预计算的时候,PRT把Lighting项拆成了一系列SH基函数,又因为我们前面提到,场景中除了光照其他条件都不变,那么显然light transport项也不会发生变化,它相当于着色点自身的性质,既然light transport不变,那我们就可以在渲染之前把它预计算好。

上面的意思简单来说,就是把渲染方程拆成两部分(Lighting和LightingTransport)然后对着两项分别预计算SH进行存储,在渲染的时候只需要将对应的SH进行相乘就行了。

下面对不同表面的PRT进行分析

Diffuse的可以拆成

$$ L(o)=k_d\sum l_iT_i $$

接下来看Glossy的情况,我们之前说Diffuse的PRT好做是因为,Diffuse的BRDF是一个常数,而Glossy的BRDF显然不是一个常数,它是一个完整的四维的函数。这里我们仍然把Lighting投影到SH上,然后把Light transport也投影到SH上,但是最后得到的结果就不是简单的两个向量的点乘了,Ti变成了T(o),原因正是因为BRDF此时不再是常数了,此时任给一个方向o,我们都可以得到一个BRDF和相应的T(o),也就是说,不同的o得到的向量不同,也就是说我们最后得到的不再是一个向量Ti,而是一个函数T(o)。

对于特别高频的情况(接近镜面反射),一般会采用其它的基函数来投影,因为SH表达高频的效果很差,或者另一种解决思路就是直接采样就可以了,因为镜面反射已经知道了是如何反射的。

PRT的局限

⦿ 球谐函数只适合于描述低频的函数,描述高频要用很高阶的基函数

⦿ 因为预计算,所以只适用于静态场景。材质,场景都不能发生改变

⦿ 大量的预计算数据需要存储和读取0

小波

2D Haar小波。与SH相同,它也是一系列基函数,但不同的是,SH定义在球面上,而它定义在图像块上,并且不同的小波定义域不同,如图中只有黑白的地方才是定义域,并且小波支持全频率的表示。其次,与SH不同的是,我们用SH近似的时候是取了SH有限阶的基函数去近似,而小波不同,我们把函数投影到小波的每个基函数上,会发现有些基函数的系数接近0,这样我们就可以定义一个系数大小,小于一定值的基函数丢掉就可以了。

Untitled

全局光照GI (3D)

全局光照=直接光照+间接光照,一般就是比直接光照多一次bounce的光照结果

Reflective Shadow Maps (RSM)

咋算呢:通过改变积分域将立体角积分转换到次级光源的单位面积上进行积分,同时这种方法只能解决LD(D|G)E的情况

RSM的思路是通过shadow map获取直接光照的影响区域,再根据shadow map的分辨率将这些区域微元化,把其中每一个像素(或者说surface patch)都当做一个次级光源进行计算

RSM会将所有次级光源的表面设为diffuse,并且间接光通常不会考虑visibility的大小

3D空间接近用2D空间近似表示,存储降低

SM中存:q(次级光源)的depth、位置、法向、(反照率albedo),光强flux

Untitled

  • 缺点:
    • 有多少个光源,就需要多少RSM
    • 假设反射物是diffuse的(次级光源不能是glossy)
    • 不计算反射物到接受物间的可见性,导致不真实
    • 计算距离接受物近的次级光源时,2Dshadow map上距离假设能够反映三维空间实际距离,导致误差
    • 距离接受物近的次级光源采样多效果好;采样少效果差
  • 优点:
    • 容易实现;pass1:生成RSM次级光源(4个map, depth, normal, coordinate, flux——for q);pass2:计算RSM次级光源对p的贡献
    • 存储小:屏幕空间存储相对于光源深度depth(用于计算阴影)、对应点的世界坐标(方便后面做p-q距离)、反射物(次级光源)的法线(计算cos)、存flux(和光源相关,和法向无关)
    • 速度不慢

优化:不再采用逐一计算虚拟光源的方法,而选择以着色点为中心,做一个有侧重的选取。

Light Propagation Volumes (LPV)

核心思想是通过将场景划分为一系列三维网格(体素化)来模拟光线的传播,随后利用其Irradiance在传播过程中保持恒定这一特征,计算出每个shading point上的间接光照。

解决RSM区域查询的问题,提高速度,不需要任何的预计算;数据结构更复杂一些。

**原则:**radiance信息在空间传播的过程中在某条直线上是不变的量。

  • 流程:

    1. 生成,利用SM完成次级光源的定义,得到位置信息和出射radiance信息(diffuse不记方向,光滑记方向)
    2. 注入,把次级光源依据position注入格子(采样能量小的就不注入了)。格子内得到出射信息(用SH表示,一般两阶SH就能做到比较好的拟合)
    3. 传播,对每个体素依据其存储的radiance分布计算它周围六个体素(不算对角)的radiance,然后分别对他们使用SH进行简化,然后计算这六块体素各自除了原体素以外的另外五个格子,迭代三次以至传播的radiance稳定
    4. 渲染,对任意着色点,找到其所在体素并叠加其所有方向的radiance后正常渲染即可。
  • 缺点:

    • 物体小于格子时,背面漏光
    • 有烟雾等的时候,不能用
  • 优点

    • 对任何着色点p,都可以快速检索得到任意方向照射到p的radiance信息,也就是间接光照的信息,全局光照效果好,速度快
  • 比较

    速度:LPV更快

    存储:RSM更小

    质量:LPV更好

Voxel Global Illumination (VXGI)

与RSM和LPV类似,Voxel Global Illumination(体素全局光照)也是一个基于体积渲染的2-pass实时全局光照算法,但它与二者的区别在于:

① VXGI 会在对场景离散体素化之后,再对数据做一次稀疏八叉树划分,所以从一开始,它的“次级光源”就是一系列体素;

② 在渲染过程中,VXGI 会采用Cone-Tracing的方式计算间接光的贡献,在速度方面会比LPV慢很多(准但不快

  • 流程
    • 预处理阶段light-pass
      • 从光源绘制场景,得到次级光源后,将其入射radiance以及光源法向存储到八叉树
      • 八叉树每个格子中,filter所有次级光源irradiance和方向
    • 渲染阶段camera-pass
      • 对于glossy表面,只需要沿着反射方向trace一个圆锥cone,查找八叉树中和圆锥相交的体素,随着圆锥半径增加,就能找到八叉树上层的体素(和mipmap有点像哈),进而用相交像素中存储的次级光线信息计算在该方向的出射信息,进而照亮着色点
      • 对于diffuse表面,就比较暴力了,VXGI将出射方向看成一堆圆锥,忽略间隙,这样一来diffuse比glossy多一层循环,效率较低
  • 优点
    • 支持反射物是glossy的全局光照计算
    • 全局光照质量高
  • 问题
    • 需要八叉树体素存储反射物入射光分布,存储代价高
    • 每个着色点都需要做cone tracing操作,计算代价高

全局光照GI (Screen Space)

Screen Space Ambient Occlusion (SSAO)

  • AO(环境光遮蔽)

    • AO描述物体与物体之间环境光相互遮挡的程度
    • 在计算全局光照过程中,由于我们无法在屏幕空间中直接获得间接光照,所以一般会假设间接光照强度为一个定值,随后通过AO来决定GI的亮度。比如在下图中,左边的shading point的AO比右图小,那么显然左边的间接光就会比右边亮

    Untitled

    • AO实现原理:环境的间接光照来自远处(一个合适的判定半径)的未被遮挡的光线

    Untitled

    约等式的前半部分就是**在单位半球面 延法线垂直向下投影 所得单位圆内(投影立体角) 对可见性按照cos进行加权平均;**后半部分我们假设间接光照强度为定值,并且材质为diffuse,那么积分出来就是常数。最后公式化简为:

    $$ L_0(p,\omega_0)={\rho\over\pi}L_i(p)\int_{\Omega+}V(p,\omega_i)cos\theta_id\omega_i $$

  • SSAO流程

    • 首先从相机出发,得到屏幕空间的深度信息,写入z-buffer
    • 对屏幕空间任意一个像素,在以它为中心、R为半径的球体范围内随机采样,判断采样点的可见性(若采样点的深度大于它在屏幕空间对应的深度,那么这个采样点可见性为0)
    • 计算可见性为1的采样点的占比,作为当前像素的visibility值(有点像PCF哈)
  • 问题

    • 屏幕空间接触物体,会产生错误的接触阴影

Screen Space Directional Occlusion (SSDO)

SSDO是对SSAO的一种改进算法

SSDO只能解决小范围间接光照(流程和RSM有点像)

  • 实现原理(流程)

    camera第一轮渲染得到depth-buffer、color-buffer、normal、position等信息,对于每个着色点p,在以其为圆心的法向所在半球内部采样,并将采样点投影到camera,判断采样点是否被遮挡

    • 直接光照

      如果采样点没有被遮挡,那么不需要计算间接光照,只计算直接光照就可以

    • 间接光照

      如果采样点被遮挡,那么就找到所有被遮挡的采样点在着色点p法向半球内对应的表面微元,将它们设置为diffuse的次级光源,再依据次级光源的法线信息判断其有效性,对所有满足条件的次级光源计算对着色点p的间接光贡献

      Untitled

  • 优点

    可以实现色溢等间接光照效果

  • 缺点

    渲染只记录了屏幕空间的信息,只记录了距离视点最近的点,导致缺少部分间接光照

Screen Space Reflection (SSR)

  • 流程

    • camera渲染生成color、normal、depth buffer
    • 对每个glossy/镜面的着色点:
      • 沿着镜面反射方向trace光线(trace过程:试探步)(如果BRDF是glossy,那么反射方向附近采样几个方向进行trace),和camera场景壳求交
      • 找到交点后,求交点对shading point的贡献
  • 加速

    • 试探步:

      Untitled

    • Depth Mip-Map

      Untitled

  • 优点

    • 存储小、速度快
    • 可以实现镜面和光泽表面反射效果;反射物可以说具有复杂几何形状的表面
  • 缺点

    • 只能反射平面空间的信息,产生“截断”、反射信息缺失等
  • 比较:

    • 相比于3D空间的RSM和LPV,SSR质量较差,但速度较快

材质Material

微表面BRDF Microfacet BRDF

微表面BRDF(Microfacet BRDF)

$$ f(i,o) = {F(i,h)G(i,o,h)D(h)\over 4(n,i)(n,o)} $$

  • 这里面都是啥

    F(i,h):Fresnel Term 菲涅尔项。给定入射方向 ωi 和半程向量h方向,返回反射方向的反射率,值域[0,1] D(h) :Distribution of normals 微表面的法线分布。给定一个半程向量h,返回法线位于该方向的微表面数量 G(i,o,h):Shadowing Masking Term 阴影遮蔽,也叫几何项。 当入射光以非常平(Grazing Angle 掠射角度)的射向表面时,有些凸起的微表面就会遮挡住后面的微表面。这一项其实就起修正作用,当入射为掠射角度时,这一项可能就会返回一个比较小的数比如0.5 0.4之类的,把BRDF的返回值拉低一点。 如果没有这一项,假如我们渲染一个球,球的边界上,就会是掠射角度,会特别亮。(看分子,菲涅尔项F(i,h) 返回值接近1,D(h) 的返回值也会很大,因为微表面法线分布基本是正态分布,又因为入射很平,半程向量很居中,微表面的数量是最多的,返回值就大。最终就造成BRDF返回值比较大,反射光的能量只会减少很少的量,因此边缘着色特别亮。)

$$ E(\mu_0)=\int_0^{2\pi}\int_0^1f(\mu_0,\mu,\phi)\mu_id\mu_id\phi $$

菲涅尔项 Fresnel Term

  • 菲涅尔项告诉我们反射能量的多少取决于入射光的角度,入射光与法线接近垂直时,反射光是最多的。当然由于光路可逆,我们也可以理解为视线和平面法线越接近垂直,反射越强。可以用Schlick’s approximation来表示:两个变量:入射角度θ、基础反射率Ro(入射角度为0的)

$$ R(\theta) = R_0+(1-R_0){(1-\cos\theta)}^5 \\R_0 = {({n_1-n_2\over n_1+n_2})}^2 $$

NDF

  • NDF Term 表面法线分布,有很多不同的分布函数

Untitled

通常使用的NDF模型为Beckmann模型和GGX模型

其中GGX模型有拖尾,它不会衰减到0,而Beckmann高光边缘锐利(衰减比较快)

还有一种在GGX模型之上做的扩展的模型,叫GTR模型。GTR有一个参数γ,通过调整γ的值,可以调整曲线的走势以达到不同的效果,如上图所示γ越小,拖尾越长,而γ=2的时候就是GGX模型,而γ增大到一定程度的时候就变成了Beckmann模型,所以说GTR模型把Beckmann和GGX整合在了一起。

几何遮蔽

  • Shadowing-Masking Term,也就是G项,我更倾向于叫几何遮蔽。它解决的是微表面之间相互遮挡的问题。尤其是Grazing angle的情况下,自遮挡现象会更严重,无论是入射方向还是视线方向,如上图所示。我们把从光出发,光线被微表面遮挡的现象叫做Shadowing。从眼睛出发看不到一些微表面的现象叫做Masking。整体来说G项就是为了提供一个darkening的操作,因为由于自遮挡,我们实际看到的现象肯定是偏暗的,而G项就是一个使结果变暗的操作。

如果没有G项,仅凭我们的Fresnel和法线分布,以及分母的视线与法线点积,会导致Grazing angle情况下,我们会看到物体的边缘会发白,会非常亮,不符合物理,这也是为什么G项很重要。

The Kulla-Conty Approximation

人们发现在上述情况中,随着Roughness的增大,物体光照能量出现了损失。

究其原因在于我们只考虑了微表面上的一次反射,而随着粗糙度的提升,这一次反射越来越不容易将光线射出,就导致了能量损失不断增大。

我们采用The Kulla-Conty Approximation的方式来补全这部分能量

在反射一次后的出射能量为:

$$ E(\mu_o) = \int_0^{2\pi}\int_0^1f(\mu_o,\mu_i,\phi)\mu_id\mu_id\phi $$

其中$\mu_i = \sin\theta$,那么光线丢失的能量就是$1-E(\mu_o)$,且有

$$ f(\mu_o,\mu_i,\phi) = {(1-E(\mu_o))(1-E(\mu_i)) \over \pi (1 - E_{avg})} $$

对于$E_{avg}$我们可以:预计算、打表,由于这里的积分只与粗糙度和μ有关,我们可以得到预计算图

Untitled

但此时依然有问题,因为如果BRDF是有颜色的,则能量会吸收,能量积分和不为1了。Kulla-Conty方法是先考虑没有颜色的情况,再考虑由颜色引起的能量损失

菲涅尔项体现了不同波长的反射率,我们从菲涅尔项入手,定义了平均菲涅尔项(不考虑20观察方向)

$$ F_{avg} = {\int_0^1F(\mu)\mu d \mu \over \int_0^1\mu d\mu} $$

在有颜色时,第一次反射的能量为$F_{avg}E_{avg}$第二次有$F_{avg}E_{avg}F_{avg}(1-E_{avg})$,可以得到累加颜色项${F_{avg}E_{avg} \over 1- F_{avg}(1-E_{avg})}$

实时光追RTRT (Real-Time Ray Tracing)

Untitled

首先最基础的光路spp就需要四条光线构成,如上图所示,首先是primary ray和它的shadow ray,这构成了直接光照,其次就是secondary ray和它的shadow ray,这构成了一次反射的间接光照。当然后续还会有其它更多次反射的间接光照,但以上提到的4条光路是最基本的构成全局光照的光路,我们把这4个光路当成1个spp的样本。

这里可以看到primary ray的前面写了个rasterization,很容易想到,因为本来primary ray实现的效果和光栅化就是相同的,并且光栅化速度更快,这样我们1个spp就可以只用3条光线了。

现代的硬件已经允许我们去做1spp的路径追踪了,但是我们之前介绍过路径追踪,如果spp只有1的话,那么得出的结果是有严重的噪声的。所以RTRT的关键技术其实就是降噪

而事实上,RTRT降噪的关键就是Temporal。首先我们是假设画面变化是连续的而没有突变,帧和帧之间有一定的连续性,其次我们认为当前帧的前一帧是已经被滤波好了的,也就是一个递归的思想,这是Temporal filtering的一个基本思路。

并且我们引入一个新概念Motion vector,它的作用就是去对应上一帧和当前帧任意一个点的对应位置,也就是它可以告诉我们物体在画面上是怎么运动的。简单地说就是可以知道世界空间下相同物体在不同帧的时候在屏幕上的不同位置。这样也就有一个好处,我们上面说过我们假设画面是连续的,那么Shading一定程度自然也是连续的,也就是说上一帧已经滤波好了的画面可以拿到当前帧复用,并且由于我们的Motion vector已经帮我们对应了同一个物体的在两帧上的不同位置,所以我们很容易知道当前帧的某个像素的颜色应该参考上一帧的哪一个像素。而这也就是时间上的复用的思路,它间接的增加了spp,并且不是简单的从1增加到2,因为我们前面提到它是一个递归,用来降噪的上一帧一定程度上来源于上上帧,所以每一帧对下面所有帧的贡献是一个指数衰减。

那我们再引入G-buffer,几何缓冲区,用于额外存储一些信息

Untitled

G-Buffer也可以存储per pixel depth,normal,世界坐标(通过rgb三通道来存x,y,z)等,只需要将这些信息各自生成一张图存储在g-buffer里,当需要的时候拿出来用,但是它是基于屏幕空间的,所以也收到屏幕空间的限制。

Back Projection

介绍这玩意之前先回顾一下MVP和视口变换矩阵(忘完了)

Untitled

Untitled

  • MVP+视口

    M模型矩阵:模型矩阵(Model Matrix)用于描述物体在世界坐标系中的位置、方向和大小等属性。通过对模型矩阵进行变换,可以实现物体的平移、旋转和缩放等效果。

    V视图矩阵:视图矩阵(View Matrix)用于描述摄像机在世界坐标系中的位置和方向等属性。通过对视图矩阵进行变换,可以实现摄像机的移动、旋转和缩放等效果。

    P透视矩阵:投影矩阵(Projection Matrix)用于描述将3D场景投影到2D平面的方式和参数。通过对投影矩阵进行变换,可以实现对场景的投影和呈现。

    视口(Viewport)变换,正常来讲就是将投影矩阵变换后的2D平面内容归一化到屏幕空间中。

好好好下面推导Back Projection,来看个式子

$$ S = M^{-1}V^{-1}P^{-1}E^{-1}x $$

s是世界坐标,x是screen space的坐标,由于在screen space是2D坐标,因此我们需要深度这一信息来作为Z值

那么很显然了,我们如果知道物体在世界坐标系中咋动的那就知道他上一帧在哪了。

$$ S^{'} = T^{-1}S $$

唉,这MotionVector不就出来了吗

降噪

铺垫了这么多,终于到降噪了,降噪又分为时间和空间上

现在我们来顺一下时间上的降噪的过程:

  1. 如果在进行rasterization(primary ray)时得到了世界坐标信息的图存储在G-buffer中则直接得到世界坐标
  2. 如果没有其信息,则在当前帧的像素通过逆视口变换和逆MVP变换得到世界坐标
  3. 已知motion矩阵,将世界坐标逆motion得到上一帧的世界坐标
  4. 将上一帧的世界坐标进行正MVP变换和视口变换得到当前帧中的像素在上一帧中的位置
  5. 将两个值线性blending起来从而得到当前帧最后的结果

滤波绝不会让一张图变暗或者变亮

对于时间上的复用,我们还要通过clamping和detection来优化一下避免鬼影之类的效果。

clamping简单来说就是把上一帧的色彩拉进到在这一帧相近的范围内再进行计算

detection则是考虑对motion vector计算之后再判断一下是不是同一个物体,如果是就加权,不是的话可以直接丢掉上一帧。(但是这种情况规避了错误却又引入了噪声)

failure case:

  • 突然切换场景或者光源会因为上一帧没有对应的信息从而出现错误的显示
  • 对于倒退的场景无法在上一帧找到对应的像素.
  • 出现残影/拖尾现象
  • 场景不动的情况下 motion vector为0,如果shading改变会出现错误的显示
  • 地板上反射出场景的情况来说,由于地板是不动的,其上面的每一个像素点的motion vector为0,当我们移动物体时,其地板需要一定的时间适应,之后再在地板上反射出当今场景中的物体,也就是反射滞后.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值