光栅化
反走样
我们把经过光栅化算法得到的图像放大,可以看到边缘有非常明显的锯齿,这在信号处理领域称为走样(Aliasing)。
图元是一个连续的几何图形,而我们的图像是一些离散的像素点,图元经过光栅化算法得到片元就是一个采样(Sampling)过程,而用这些离散的片元表示原来的图元称为重建(Reconstruction)。
图元可以用一个连续函数 f(x)f(x) 表示,称为模拟信号,采样就是从这个函数中得到一个点集 {xi,f(xi)}{x_i,f(x_i)} ,称为数字信号。重建就是通过一个**滤波(filter)**构造出另一个函数 f(x)\widetilde{f(x)} 。使得 f(x)\widetilde{f(x)} 尽可能逼近 f(x)f(x) 。但毕竟 f(x)~≠f(x)\widetilde{f(x)} \neq f(x) ,所以采样重建的过程必然是有损的,当这个损失令我们不能接受时,那么就发生了走样。
对模拟型号施加傅里叶变换,可以把它从时域变换到频域,频域图像反应了信号更多的特征。最重要的自然是信号的频率。在图元的边界属于高频部分,图元内部属于低频部分。走样往往发生在信号的高频部分。通过采样定理,我们可以得到反走样的方法。
采样定理(The Sampling Theorem):简单的说,当采样频率大于等于原始模拟信号 f(x)f(x) 最大频率两倍时,离散信号能重建出原始信号。
5.1 超采样反走样
超采样反走样(Super Sampling Anti-Aliasing,SSAA)是最简单,效果最好,开销最大的一种反走样的技术,它直接对症下药,走样是因为采样率不足导致的,那么超采样就直接提高采样频率。
对于光栅化算法来说,以最终生成 200×200200\times 200 的图像为例,超采样技术直接用 400×400400\times 400 的分辨率生成一张图像,再用一个盒子滤波器,将每 2×22\times 2 像素的颜色平均成一个像素的颜色,写入目标图像对应像素中。
它相当于把原来的一个像素,拆成了 4 个小像素,用着四个小像素去采样图元得到各自的片元。每个小片元单独执行逐片元。每个小片元都要执行片元着色,深度测试,颜色混合,模板测试的操作,所以帧缓存也要跟着翻倍。
相比与原来,需要处理的像素翻了 4 倍,帧缓存也翻了 4 倍。自然我们可也可以用 800×800, 1600×1600800\times 800,\ 1600\times 1600 的分辨率去生成图像,这无非是更大的开销和更好的反走样效果。
简单来说,SSAA 算法就是先生成一张更高分辨率的图像,再把它缩放到目标分辨率。SSAA 算法是暴力反走样,虽然效果最好,但开销也大。
5.2 多重采样反走样
**多重采样反走样(Multi-Sampling Anti-Aliasing,MSAA)**是在超采样的基础上稍微的妥协了一下。在逐片元操作中,最费时的操作是片元着色,MSAA 在这里做出了妥协。
还是以生成 200×200200\times 200 的图像为例,多重采样技术同样用 400×400400\times 400 的分辨率生成一张图像,同样将原来一个像素拆成四个小像素,但图元的每个小片元不会单独计算片元着色,而是计算原来 200×200200\times 200 时得到的那个大片元的颜色。如果四个小片元中,有一个片元没有被图元覆盖,或者没有通过片元测试,则赋给它颜色缓存中的颜色;如果片元被图元覆盖,或者通过了片元测试,则覆盖它片元着色的颜色。最后还是用一个盒子滤波,将每 2×22\times 2 个像素的颜色平均起来写入目标图像对应像素中。
将四个小片元直接平均的结果可能并不好,通常会通过小片元的覆盖率加权平均。
总结一下多重采样技术,其相比于 SSAA,其减少了片元着色操作,但同样需要原来 4 倍的逐片元操作和 4 倍的帧缓存。开销同样不小。
多重采样反走样是被广泛支持的否走样技术,并且得到了硬件的支持。上面提到的走样有属于同一种分类 —— 几何走样。还有另外一种走样称为着色走样,因为渲染方程也是一个连续函数,对某些部分(比如法线,高光等)在空间变化较快(高频部分)采样不足也会造成走样,反映在视觉上一般是图像闪烁或者噪点。SSAA 即可以减轻几何走样,也可以减轻着色走样;但 MSAA 只能减轻几何走样。
在工业级还有很多种反走样算法,比如 FXAA,TAA,DLSS 等,这里就不在深究了。
实时渲染管线:(一)基本概念与发展简史
1 实时渲染管线基本概念
实时渲染管线可以拆分出两个关键词,一是实时渲染(Real-time Rendering),二是管线(Pipeline)。
在计算机图形学中,渲染算法有两大框架:一是光线投射(光线追踪);二是光栅化;实时渲染就是基于这两大框架,并要求在 33ms 内完成一副图像的渲染。这两种算法都需要解决三个核心问题:
- 找到三维空间点,对应图像空间的哪个像素(投影)
- 计算着色点的颜色
- 解决着色点与着色点之间的遮挡关系
光线投射算法的灵感来自于物理上的光线传播,利用光的可逆性原理,从图像空间的每个像素发出射线(Ray),利用解析几何的手段,找到光线与场景中距离观察点最近的交点,然后着色,同时解决上面三大问题。
光栅化算法是从线性变换的角度解决投影问题,因为投影变换是一个线性变换,所有的线性变换都可以用一个矩阵表示,所以光栅化算法是遍历场景中的每一个物体的每一个顶点乘以投影矩阵,把顶点变换到图像空间。然后通过解析几何的手段,找到投影后的几何图形覆盖的像素区域,计算这些像素的颜色。最后用一个额外的存储空间,记录距离观察点最近的片元,解决遮挡问题。
下面用伪代码表示这两种算法,假设场景中的物体都是三角形网格模型:
RayCasting()
{
foreach pixel in Image {
cast ray;
foreach object in Scene {
foreach triangle in object {
find visible shading point
}
}
}
}
Rasterization(){
foreach object in Scene {
foreach triangle in object {
find visible shading point;
}
}
}不管是光线投射算法还是光栅化算法,都需要遍历大量的物体,处理大量的像素,而且对每个像素、每个物体进行的操作都很相似,因此可以将循环算法改成并行算法,可以显著地缩短渲染时间。我们将图形渲染任务,拆分成相互独立的流水线阶段,每个阶段交由多核处理器并行执行。通过不断的优化算法,将有可能在 33ms 内完成渲染任务。
这个多核处理器就是 GPU,GPU 中有数千颗计算核心,适合光栅化算法和光线追踪算法这种数据密集型算法。CPU 和 GPU 最大的差异在于核心数,CPU 专门为复杂**串行作业(Serial Task)**设计,主流 CPU 的核心数是 4 核或 8 核。CPU 需要负责整个计算机的运行,因此有复杂的控制单元。CPU 对内存通常是随机读写,要求高可靠,低延迟(通常 200-300 个时钟周期),同时不需要太高的带宽,主流的高速 DDR4 内存的带宽可达 25.6 GB/s。
GPU 的出现不是为了取代 CPU,而是辅助 CPU 完成复杂的计算任务。GPU 不能独立运行,需要 CPU 派发指令控制 GPU 执行。GPU 不是单纯的 many cores CPU,虽然 GPU 能作通用计算,但并不是所有的算法都适和 GPU 执行。GPU 是专门为**大数据并行作业(Parallel Task)**设计,并且每个线程相互独立(比如图形渲染)。GPU 的核心数量是通常有好几千个,GPU 同一时间会处理大量相似的数据、执行大量相似的指令,产生大量计算结果,因此显存的设计也突出大吞吐(Throughput)、高带宽(Bandwidth),主流旗舰显卡的 GDDR6 显存的带宽是 512GB/s,是 CPU 与内存带宽的数十倍。显存高带宽的代价是读写高延迟,通常有 400 到 600 个时钟周期。 GPU 只负责特定任务的执行,不需要控制整个计算机,因此缩小了控制单元的面积,并将核心大部分面积让给了计算单元。
开发者可以在应用程序中调用专用的接口控制 GPU 运行。在图形程序或游戏中,可以使用 Direct3D 12、Direct3D 11、OpenGL、OpenGL ES、Vulkan、Metal 等接口,在 GPU 通用计算领域,可以使用 CUDA、OpenCL 等。这些程序都是运行在 CPU 上,操作系统和显卡驱动会将接口翻译成 GPU 可执行的指令,提交给 GPU。图形接口的任务是控制 GPU 完成图形渲染的任务,在 API 层面抽象出了四种渲染管线:
- 光栅化渲染管线
- 计算管线
- 光线追踪渲染管线
- 网格渲染管线
并不是所有的 GPU 都支持这四种管线,下面简单介绍一下 GPU 与图形 API 的发展史,了解相关技术的由来。
实时渲染管线:(三)逻辑管线 - 知乎 (zhihu.com)