Lecture 14 A Glimpse of Industrial Solutions
Temporal Anti-Aliasing (TAA)
- 为什么有aliasing
- 光栅化期间SPP不足(样本数量不足)
- 终极解决方案是用更多的样本(MSAA)
- Temporal Anti-Aliasing
- 跨越实际贡献/复用采样
- 思路和在RTRT中如何利用temporal的信息一模一样
思路
先考虑静止的情况
假设当前帧和上一帧都是\(4\times4\)的分辨率,将感知样本分布在像素左,上一帧右上角,再上一帧在右下角,再上一帧在左下角,这样连续的四帧之间有一个移动的sampling patern,它们在时间上分布各不相同
当前帧复用上一帧,就可以复用上一帧感知出的结果,而这个过程是递归的,所有就将temporal的结果都考虑进去了,相当于当前帧做了2倍的sampling
-
采样位置为什么不随机?
随机的效果不一定比固定的好,因为会在temporal中引入额外的高频信息,固定采样点不容易出现采样点不均匀的情况
-
运动的情况?
motion vector
静止时在同一个像素中找之前sample的结果,运动的情况用motion vector找到对应像素
-
temporal的信息不可用的情况
用clamp的方法做
关于Anti-Aliasing
MSAA (Multisample) 和SSAA (Supersampling)
-
SSAA按照更大的分辨率渲染,再降采样
-
这种解决方案是百分百正确的,但是非常耗性能
比如在长宽都是2倍的情况下,一定会造成原先4倍的开销
-
MSAA
-
每个primitive只做一次shading
在primitive中取一个具有代表性的点
为了实现这个,MSAA会在内部维护一张表,记录颜色和深度
-
MSAA允许sample reuse
不是在时间上,是在空间上
通过样本分布方法,比如将样本分布在临接像素的边缘上,这样两个像素都能用这个样本
-
基于图像的抗锯齿方法
image based anti-aliasing
-
SMAA
Enhanced subpixel morphological AA,720p~1080p大约耗时1ms
历史上最早在图像上处理走样的AA叫FXAA (Fast Approximate AA)
后来出现MLAA (Morphological AA),再提升为SMAA
-
图像方法会先找到Aliasing边界,根据各个pixel的占比来填pixel
不同的AA方法区别于图2中找边界方法不同,这种过程叫矢量化,给定一个图,相当于要将其转变为无限分辨率
G-buffer
G-buffer一定不能反走样,比如深度,前景和背景深度会有明显差异,但是这些差异就是表示的深度,不应该反走样
G-buffer是数据,不是显示
Temporal Super Resolution
Super resolution
也叫super sampling,比如Nvdia的DLSS技术
-
第一个思路 DLSS 1.0
没有额外信息,全靠猜,每个游戏都要训练一个神经网络
-
第二个思路 DLSS 2.0
利用temporal信息
DLSS 2.0有一个巨大的问题
右图是我们希望得到的结果
-
如果有temporal failure,不再能使用clamping
-
增加了分辨率,那么势必要知道增加分辨率后小pixel的值是多少,如果用上一帧的结果盲目地做clamp,得到的结果会造成有些小pixel是根据周围点的颜色猜出来的,那么就会造成这些小pixel的颜色很像周围点的颜色,导致看起来很糊
-
TAA没有这个问题,因为TAA没有改变分辨率,而DLSS增大了分辨率
-
因此需要找到一个更好的复用temporal信息的方案
深色是当前帧采样信号,灰色是上一帧采样信号,二者可能采样点不同
要将两帧结果综合在一块,得到当前帧增加了采样点的值(右图)
所以DLSS 2.0的网络没有输出任何混合后的颜色,而是告诉我们关于上一帧temporal的信息应该怎么用
-
另一个问题
-
DLSS是让我们渲染低分辨率的图,通过DLSS超分成高分辨率的图,从而提升帧率,如果DLSS每帧自己损耗过高时间,这么做就没有价值了
所以N卡对DLSS有硬件优化
-
AMD有类似技术,FidelityFX Super Resolution,FSR,FSR并不是只有A卡能用
-
Facebook: Neural Supersampling for Real-time Rendering,类似DLSS 1.0,效果不是很好,工业界不用
-
-
Deferred Shading 延迟渲染
一种节省shading时间的方法
-
传统光栅化过程
-
Triangles\(\rightarrow\)fragments\(\rightarrow\)depth test\(\rightarrow\)shadie\(\rightarrow\)pixel
-
当从远往近渲染时,每个fragment都需要shade,因为每个fragment都会通过深度测试
-
复杂度: O(#fragment * #light),每个fragment需要渲染light数量的次数
-
-
Key observation
- 大多数fragment在最后的图像中都看不到
- 原因是深度测试/遮挡
- deferred shading 想要只渲染可见的fragment
解决思路
光栅化两次
-
Pass 1: 完全不做shading,只更新深度缓冲区
-
Pass 2: 光栅化
这样每个通过深度测试的fragment都是可见的fragment
-
光栅化一遍场景常常比shading所以不可见的fragment开销更小
-
复杂度: O(#fragment * #light)\(\rightarrow\)O(#vis. frag. * #light)
-
缺点
- 很难做AA
- 但是可以用TAA或者图像空间的AA
- 这样就解决了以前不好做AA的问题,让延迟渲染成为了工业界标准
延迟渲染将fragment数量减少很多了,那么后续就应该着手于光源数量
Tiled Shading
Tiled Shading是建立在Deferred Shading的基础上,将屏幕分成一系列小块 (e.g. \(32\times32\)),每个小块表示场景中一个3D的条
-
Key observation
不是所有光源都能照亮特定一个小块,大多数都因为按照距离平方衰减而照不到,那么就可以认为每个光源的覆盖范围是一个圈,每个光源只下渲染能影响到的小块
通过分成一系列小块,可以节省每一个小块要考虑的光源数量
-
复杂度: O(#fragment * #light) \(\rightarrow\) O(#vis. frag. * avg #light per tile),复杂度进一步降低至每一块平均light的数量
Clustered Shading
在Tile Shading的基础上更加复杂度优化
-
不仅要将屏幕分为若干块,还要在深度上进行切片
-
那么就等于将3D空间拆成了grid 网 格
-
Key observation
- 光源的覆盖距离是有限的,而Tile Shading的场景中切出来的长条覆盖的深度非常大
- 因此,很多光也许会被认为对tile有贡献,但不一定会对Clustered Shading中更细的小片有贡献
- 复杂度: O(#vis. frag. * avg #light per tile) \(\rightarrow\) O(#vis. frag. * avg #light per cluster)
-
前面的格子有些内容不会遮挡到后面的格子吗?
对像素来说这么考虑是对的,但对于格子来说不一定,因为格子是一个更大的东西,格子里可能包含一个球、它的边界、以及它的背景,它们能同时出现在一个格子内,所以不存在遮挡问题
Level of Detail Solution
Level of Detail (LoD)十分重要
- 不如texture的mipmap就是一个LoD
- 计算时只要选择一个正确的Level就能节省计算
实时渲染工业界会将运用LoD的方法(选择不同层级的思路)叫做cascaded
例子
-
比如Cascaded shadow maps
生成Shadow map时需要给定一个分辨率,从camera出发,离camera越远的地方,越可以用一个更粗糙的shadow map,那么shading时就可以根据场景中物理离camera的距离,选择用什么Level的Shadow map
这里红色区域表示近处的shadow map,蓝色区域表示远处的shadow map,可以发现二者存在一定重叠区域
因为前面在用红色区域的shadow map,后面突然切换到蓝色区域的shadow map,怎么让二者有一个平滑过渡呢?
为了平滑过渡,人们会重叠一部分shadow map,然后根据距离,偏远的主要用蓝色,偏近的主要用红色,做一个blend即可
-
Cascaded LPV
Cascaded同样可以用在LPV (Light Propagation),radiance刚开始传播时在一个比较精细的格子中传播,当传播得比较远时在更加粗糙的格子中传播
-
Geometric LoD
-
生成一系列高模、低模,根据离cameara的远近选择模型的Level
-
还可以定义一个标准,比如说让所有时刻,三角形的大小不要超过一个像素的大小,让一个物体的一部分用高模,一部分用低模
-
camera的推进是一个渐变的过程,但是突然从高模换成低模会导致看起来像几何突然变化,叫做Poping artifacts,这种情况可以用TAA解决
-
这就是UE5 Nanite的实现(但是Nanite有更多实现方法),一种动态选取LoD的做法,UE5自己写了一套光栅化管线来实现
-
总结
-
Key challenge
-
不同层级之间的转换
从一层直接转到另一层一定会出问题
通常会用一些blending的方法手动做一些平滑过渡
-
不同像素用不同层级的几何可能会出现缝
学术界有解决方案
-
要动态加载和调度不同层级,那所有层级都要先存入GPU吗?
学术界也有研究,比如说虚拟纹理(可以理解成对一个理论上无限大纹理的调度)
-
几何的表示
三角形面?比较麻烦
几何纹理?只需要在几何纹理上做LoD
-
Clipping和culling 裁剪和剔除
-
Global Illumination Solutions
-
SSR会有屏幕空间问题
-
没有任何单一的GI解决方法能完美解决全部情况,除了RTRT,因为RTRT是理论上正确的path tracing
但是RTRT太慢,不能所有部分都用RTRT解决
-
那么工业界只有hybrid solutions 综合考虑所有解决方案
-
Example
- 先做SSR得到一个近似的GI
- SSR不行的地方再用其他方法弥补
- 硬件或软件做ray tracing
-
Software ray tracing
-
需要高质量的SDF,来trace近处的物体
-
远处的物体用低质量SDF来trace
-
如果场景中还有些非常强的方向光或者点光源(比如手电筒),可以用RSM
-
Dynamic Diffuse GI, or DDGI
如果场景本身比较diffuse,可以使用Probes 探针
每个Probe会记录3D网格中的irradiance
-
-
Hardware ray tracing
-
完全没必要用原始geometry
我们只要算间接光照,就可以用简化了的模型来代替原始模型, 来加快trace速度
-
Probes (RTXGI)
-
这里橙色的部分,加上前面先做的一趟SSR,就是UE5的Lumen
Uncovered topics
-
SDF贴纹理很困难
-
涉及透明材质或者依赖渲染顺序的
-
粒子渲染
-
后处理
-
随机数种子和蓝噪声
-
Foveated rendering
比如VR,在盯着的地方投入更多算力,其他地方投入更少算力
-
Probe based global illumination
-
ReSTIR,Neural Radiance Caching等等
-
Many-light理论和light cuts
-
Participating media 云烟雾
-
SSSSS 次表面散射
-
Hair appearance
-
...