GAMES104-07:渲染:渲染管线、后处理和其他的一切
七、渲染:渲染管线、后处理和其他的一切
7.1 环境光遮罩 Ambient Occlusion:AO
原理:在物体中,只有一部分能看到天光,其他部分会被遮挡住 \[
AO(\bold{p,n})=\frac{1}{\pi}\int_{\Omega}V(\bold{p},\omega)\bold{n}·\omega
\ d\omega
\]

7.1.1 预计算AO
使用光线追踪的算法,离线计算AO并将其保存到纹理中
- 需要额外的存储
- 只能应用于静态物体

7.1.2 SSAO:Screen Space Ambient Occlusion
思想:
- 在渲染的每一帧中,可以得到每个像素的颜色&深度
- 将每个像素的深度信息连接到一起,就可以得到height field
- 根据height field可以得到几何关系,从而估算自遮挡
估算自遮挡:
- 在观察点p的给定半径的球形空间内,随机采样N个点
- 对这N个采样点,通过相机去投射,可以得到这些点的深度,与Z-Buffer中的深度进行对比
- 深度比Z-Buffer中的近,则证明其可以看见光
- 深度比Z-Buffer中的远,则证明其被当前绘制中的某个几何挡住了
- 则最后的自遮挡为:\(A(p)=1-\frac{Occlusion}{N}\)

7.1.3 SSAO+
- 沿着p点的法线方向,采样半球面

7.1.4 HBAO:Horizon Based Ambient Occlusion
思想:
- 要求的实际上是球面空间上的可见性
- 从p点出发,沿着各个方向去转,找到光线能够越过最高邻居的仰角,称为pitch angle
- 如果在每个方向都找到了,则会得到Occluded Area
- 根据Occluded Area,可以估算出有多大面积的天顶是可见的
- attenuation function:如果某个山离p点过远,则认为其对我没有影响
\[ A=1-\frac{1}{2\pi}\int_{\theta=-\pi}^{\pi}\int_{\alpha=t(\theta)}^{h(\theta)}W(\vec{\omega})\cos(\alpha)\ d\alpha d\theta \]

具体实现:ray marching
- 将Z-Buffer视为height field
- 以P点为圆心,以几个像素为固定步长进行采样,采样一圈,得到最大horizon angle
- 注意:对于每个像素,要随机抖动步长、旋转方向

7.1.5 GTAO:Ground Truth-based Ambient Occlusion
问题:不同方向的光,对于P点的反射贡献是不同的,有一个 \(\cos\theta\) 的修正 \[
\hat{A}(x)=\frac{1}{\pi}\int_{0}^{\pi}\int_{\theta_1(\phi)}^{\theta_2(\phi)}\cos(\theta-\gamma)^+|\sin(\theta)|\
d\theta d\phi\\
\gamma=angle(\vec{n},\vec{v})
\]

- 根据计算出的AO值,可以猜测光射进山谷后,多次Bounce后形成的亮度
- 例如山谷虽然被周围的山遮挡住了,但是底部仍旧有光,并且底部的光与周围的山的面的颜色有关系
- 根据不同的AO值,与对应的Multi Scattering值进行分析,得到一个多项式的关联
- 从而根据一个多项式,将Single Bounce转换为Multi Bounce的效果
- AO的值类似于BRDF中的roughness值,表示周围区域对当前点的遮挡性
- GTAO的发现类似于用多项式方程拟合了积分的效果

7.1.6 Ray-Tracing Ambient Occlusion

7.2 雾效
7.2.1 Depth Fog
思想:从眼睛看过去,随着距离,能见度逐渐下降

7.2.2 Height Fog
思想:不同高度,雾的浓度不同
- Fog有一个最大值:当低于某个高度时,都是最大的Fog;当高于该高度时,Fog是以指数递减的
- 当人的位置\(O_z\)高于目标位置时,Fog要通过Ray Marching算法,从目标位置到当前位置进行积分

7.2.3 Voxel-Based Volumetric Fog
将相机空间非均匀体素化:根据视锥进行切分(如左上图所示)
- 原因:如果进行均匀体素化,那么近处的颗粒太大,远处的颗粒太细
- 一般会用一个3D纹理存储运算结构

7.3 抗锯齿
7.3.1 锯齿产生的原因
产生锯齿的原因:屏幕的分辨率是有限的,而几何世界的分辨率是无限的
- 边界的采样
- 纹理的采样
- 高光的采样

7.3.2 Anti-aliasing
多采样几次,进行平均,从而产生过渡区域

7.3.3 SSAA(Super-sample AA) & MSAA(Multi-sample AA)
SSAA:
- 在屏幕分辨率的基础上,绘制更高分辨率的图片,然后对高分辨率的图片进行下采样,得到屏幕上的图片
- 缺点:所有buffer、shading均变成原来的4倍
MSAA:
- 对空间仍是4倍采样,但是在渲染时,如果这4个sub-pixel对三角形的贡献相同的话,只进行一次shading;如果不止一个贡献的话,则对四个采样点均进行一次shading,然后计算平均值
- 只有在最后的shading时,会省略一些不需要的渲染
- 缺点:当三角形数量高于像素数量时,MSAA会彻底失效

7.3.4 FXAA:Fast Approximate Anti-Aliasing
在采样率不变的情况下,进行抗锯齿
思想:产生锯齿的地方,一般都是边界,此时会产生颜色的跳变,只需要对边界进行抗锯齿即可
对每个像素点,进行十字滤波:
- 计算上下左右四个邻居以及自己的差异值,当差异值大于某个阈值时,就是边界
- 将颜色转化为亮度,只计算亮度的差异值:\(L=0.299*R+0.587*G+0.114*B\)

计算差异的方向:
- 使用两个卷积矩阵,分别计算横向与纵向的差异权重,判断差异方向
- 确定方向后,比较两个邻居,判断谁的差异更大

边界搜索算法:
- 从当前点出发,与应该与其进行混合的点形成一对,求亮度平均值\(L_{avg}\)、亮度差异值\(L_{offset}\)
- 朝着垂直于这一对的方向的两边进行搜索,找到像素对的颜色变化与当前点像素对不同为止
- 此时就找到了边界的两个端点

计算混合系数
- 比较当前点距离左端和右端的距离,找到更近的端点\(targetP\)
- 根据平均亮度、当前点亮度、端点亮度,计算混合系数

与相邻的像素进行混合

7.3.5 TAA:Teamporal Anti-Aliasing
- 利用前一帧的数据进行混合
- 每个点都需要计算上一帧的对应位置,生成motion vector


7.4 后处理
7.4.1 Blooming 光晕效果

首先,取出当前帧中高亮的部分
- 计算每一个像素的亮度,与平均光场亮度做对比,超过阈值的像素取出其RGB值

然后,对取出的像素进行高斯模糊

先进行几次下采样,对下采样之后的结果进行Blur,然后再进行上采样,与原有的图像进行混合,此时模糊的区域就会很大

7.4.2 Tone Mapping
将HDR颜色转化为SDR颜色,从而显示HDR图像

关键在于定义一条Tone Mapping曲线,用于转换颜色
filmic曲线

ACES:Academy Color Encoding System

7.4.3 Color Grading
通过一个Lookup Table,整体改变图像的色彩空间,从而快速修改游戏画面的颜色



7.5 渲染管线

7.5.1 Forward Rendering
对每一个mesh、每一个light,渲染一遍

- 透明物体必须在不透明物体之后绘制
- 多个透明物体按照由远及近的顺序绘制
- 天空最后绘制

7.5.2 Deffered Rendering 延迟渲染
- 先计算所有物体的材质信息,将其保存到一个巨大的G-Buffer中
- 然后再计算所有的光,将其应用到G-Buffer中

优点:
- 对光的处理非常方便
- 渲染很容易debug
缺点:
- 需要更多的内存、读写开销
- 不支持透明物体
- 对MSAA不友好

7.5.3 Tile-based Rendering
将画面拆成一小块一小块,降低对读写的压力

从而可以判断每个光会对哪些tile产生影响,进行光的裁剪

对于每一个tile,可以在计算Z-Buffer的时候,得到该tile的深度范围,从而计算出光源可能影响到的tile

7.5.4 Forward+ Rendering
分块进行Forward Rendering

7.5.5 Cluster-based Rendering
直接对Z空间进行切分,划分为一个个的四棱锥

7.5.6 Visibility Buffer
将几何信息与材质信息分开,存储几何信息,反向找到材质
- 在Visibility Buffer中,记录每个像素属于哪个几何体的哪个三角形,从而可以反向查该三角形应用哪种材质

7.6 Frame Graph
用一个有向无环图表示资源之间的依赖关系,由系统自动管理实际资源的使用和释放

7.7 V-Sync & G-Sync
7.7.1 Screen Tearing 屏幕撕裂
引擎渲染的刷新率是不一定的,而屏幕的刷新率是固定的,从而会导致画面的撕裂

7.7.2 V-Sync 垂直同步
垂直同步(VSync) :将游戏或应用程序的图像帧速率与显示监视器的刷新速率进行同步,有助于建立稳定性。 如果不同步,则可能会导致画面撕裂,即图像看起来在整个屏幕上呈现水平方向毛刺或重影的效果。 启用VSync 后,便会获得完美对齐的画面而不会出现毛刺
将整个Frame渲染完成后,等到下一个屏幕刷新时间,统一写进去
- 可能会导致画面一会快一会慢
