实时渲染管线:(一)基本概念与发展简史[转]

作者:木头骨头石头
原文地址:https://zhuanlan.zhihu.com/p/440584180

实时渲染管线是一个非常大的话题,这一技术随着游戏市场的扩大发展了二、三十年。有无数科学家和工程师投入其中,笔者能力有限,只能对他们的部分工作做一些简单注解。下面的内容总结自是笔者学习游戏开发的过程中搜集到的资料,原文会表明出处。其中关于硬件的部分,笔者没有任何相关背景和开发经验,可能会有很多错误,还望指出。

1 实时渲染管线基本概念

实时渲染管线可以拆分出两个关键词,一是实时渲染(Real-time Rendering),二是管线(Pipeline)

在计算机图形学中,渲染算法有两大框架:一是光线投射(光线追踪);二是光栅化;实时渲染就是基于这两大框架,并要求在 33ms 内完成一副图像的渲染。这两种算法都需要解决三个核心问题:

  1. 找到三维空间点,对应图像空间的哪个像素(投影)
  2. 计算着色点的颜色
  3. 解决着色点与着色点之间的遮挡关系

光线投射算法的灵感来自于物理上的光线传播,利用光的可逆性原理,从图像空间的每个像素发出射线(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 层面抽象出了四种渲染管线:

  1. 光栅化渲染管线
  2. 计算管线
  3. 光线追踪渲染管线
  4. 网格渲染管线

并不是所有的 GPU 都支持这四种管线,下面简单介绍一下 GPU 与图形 API 的发展史,了解相关技术的由来。


2 GPU 与图形 API 发展史

以下内容总结自 TechSpot 上的系列文章以及 WIKI 百科,简单介绍技术的变迁,不涉及商业冲突或阵营矛盾,如有错误还望指出,推荐阅读原文:

2.1 1970-1990 年图形加速设备出现

上世纪 70 年代,拥有图形用户界面的操作系统成为主流。此时,图形界面的绘制工作由 CPU 一个像素一个像素的绘制。随着界面越来越复杂,屏幕分辨率越来越高,CPU 已经难堪重负,所以有公司开始设计专门的图形加速设备。图形加速设备属于计算机体系的外部设备,专门用于图形图像显示。上世纪 70 年代也是雅达利游戏机的鼎盛时期,雅达利游戏搭载的就是自家的 ANTIC 2D 图形设备。上世纪 80 年代,雅达利退出游戏市场,家用游戏机进入任天堂时代。个人电脑上的图形加速设备也越来越多,Intel,IBM 也推出了自家的图形加速设备,同时专门设计图形加速卡的公司开始成立。1981 年,Silicon Graphics, Inc(SGI) 成立;1985 年,VideoLogic 成立,后来改名为 Imagination Technologies;1985 年,ATI TechnologiesInc 成立,后来被 AMD 收购;1989 年 S3 Graphics 成立。

2.2 1990 年-2000 年图形应用程序接口出现

上世纪 90 年代,N64、PlayStation、Sega Saturn 等游戏主机推出,进入 3D 游戏时代。此时电脑上有太多的图形加速设备,各家的标准不同,导致软件人员开发困难。1992 年,SGI 公司决定将自家的 IRIS GL API 转变为一项开放标准,OpenGL 诞生,并成立了 OpenGL 架构评审委员。OpenGL 允许在非 SGI 的设备上运行。OpenGL 一开始定位于专业图形渲染领域,但因其易用性,越来越多的开发者使用它制作游戏。1995 年,微软推出了用于 Windows 操作系统的 3D 图形接口 —— Direct3D

DirectX 是一个接口集合,其在不同年代包含的接口不同。早期的 DirectX 包含 Direct3D 和 DirectDraw 分别是 3D 图形程序接口和 2D 图形程序接口。此外还有 DirectSound,以及 DirectDraw 的替代者 Direct2D。下面主要介绍 Direct3D

90 年代,加入图形硬件市场的有两家重量级公司:1993 年,NVIDIA ;1994 年,3D/fx, Inc 。1996 年,3D/fx 公司的 Voodoo 加速卡上市,这是历史上最成功的图形加速卡,真正意义上的初代“核弹”,市占率高达 85%。Voodoo 卡的诞生使个人电脑进入 3D 时代。ATI 为了抗衡 3D/fx,推出了 Range Pro 加速卡。此时,NVIDIA 还在蓄势待发,不过 PC 端图形卡的性能竞赛才刚刚开始。3D/fx 的称霸也只是暂时的。

同一时期,VideoLogic 公司开发的 PowerVR 加速卡是市面上首次采用 TBDR(Tiled Based Deferred Rendering)技术的设备。TBDR 技术的优势在于减少帧缓存的带宽消耗,因此相比其他图形卡更加节能,TBDR 也是未来移动端 GPU 主流架构。

1999 年,世纪之交,NVIDIA 推出 GeForce 256,首次提出了 GPU 的概念。此前的 3D 图形加速设备只限于完成图形的光栅化任务,而 GeForce 256 提供了完整的 3D 渲染流水线,其不仅能快速的完成光栅化任务,还能硬件加速 T&L(Transform & lighting),也就是能在 GPU 中加速顶点的坐标变换,计算片元的光照。在此之前,这一技术在家用游戏机上已经普及,但在 PC 上这些工作都是 CPU 的任务。GeForce 256 支持 Direct3D 7.0 和 OpenGL 1.2。此时,ATI 正在开发旗下新的显卡产品 Radeon,也开始支持硬件 T&L。

2.3 2000-2006 年可编程渲染管线出现

2001 年,微软发布 Direct3D 8 ,同时提出了 Shader Model 1.0。Shader Model 是微软提出的概念,其要求显卡厂商支持其中提到的功能和指令。SM 1.0 提到了顶点着色器和像素着色器,用户可以使用微软提供的 HLSL 语言对这两个阶段编程,标志着可编程渲染管线出现。同年 NVIDIA 和 ATI 搭载顶点着色器和像素着色器的 GPU 发布,宣布支持 Direct3D 8 标准 。

不同于微软的财大气粗,OpenGL 是通过拓展(extension)使用 GPU 的新特性。此时,OpenGL 的发展也陷入停滞与迷茫,新特性的支持也总是慢于 DirectX。OpenGL 1.3 以拓展的形式支持可编程渲染管线,OpenGL 提供的着色器语言为 GLSL。直到 2004 年,OpenGL 2.0 发布,可编程渲染管线与 GLSL 才正式成为 OpenGL 标准的一部分。

2002 年,Direct3D 9 发布,引入 Multiple Render Targets(MRT) 技术,至此,渲染管线的渲染目标可以不再是帧缓存,可以是自定义纹理。OpenGL 2.0 开始支持 MRT。

这一时期,GPU 硬件的性能大战也进入最终阶段。3D/fx 被 NVIDIA 收购;ATI 和 AMD 合并;Imagination Technologies 放弃桌面端显卡市场,转向移动端 GPU 的研发。SGI 宣布破产,OpenGL 架构评审委员会投票决定将 OpenGL API 标准的控制权交给 Khronos Group

2.4 2006 年-2013 年 GPU 通用计算

2006 年,主机游戏进入 PlayStation3 和 XBOX 360 时代。同一年,Direct3D 10 发布,一并提出的还有 Shader Model 4.0。SM4.0 新增几何着色器(Geometry Shader)。同时 SM 4.0 还提出统一着色架构(Unified Shader Architecture)的概念。最早宣布支持统一着色架构的硬件是 ATI 推出的,使用在 XBOX 360 上的 GPU。所谓的统一着色器架构是相对于分离式着色架构而言的,分离式着色架构是指顶点着色器和像素着色器运行在不同的 ALU 上,这些处理器使用不同的指令集,因为顶点和像素处理的数量的差别,可能出现像素着色处理器繁忙但顶点着色处理器闲置的情况。统一着色器架构是将顶点处理器和像素处理器合二为一,称为流处理器(Streaming processors,SP),它们使用同一套指令集,拥有能运行顶点着色器代码、几何着色器代码和像素着色器代码的所有指令,GPU 负责调度这些流处理器的均衡,避免处理器闲置,提高计算效率。

2006 年,NVIDIA 以科学家 Tesla 命名的显卡架构支持统一着色架构;AMD 和 ATI 合作的第一代显卡架构 TeraScale 也支持统一着色架构。GPU 支持统一着色架构意味着 GPU 计算核心的指令集相比过去更加复杂,那么也能完成更加通用的计算任务,GPGPU(General Purpose GPU Programming)的概念出现。利用 GPU 多核处理架构和大数据吞吐能力,可以加速一些数据密集型任务。NVIDIA 提出统一计算设备架构(Compute Unified Device Architecture,CUDA),并为软件开发者提供 CUDA SDK ,能在非图形程序中为控制 GPU 执行特定算法;而 AMD/ATI、Apple 等与 Khronos Group 合作,制定开源的 OpenCL(Open Computing Language)标准,是一种跨平台的 GPGPU 接口。

2010 年,Direc3D 11 推出,一并提出的还有 Shader Model 5.0。SM 5.0 给渲染流水线新增了一个可选阶段 —— 细分曲面阶段,加快模型 LOD 的计算。更重要的是,D3D11 受到 CUDA 和 OpenCL 的启发,以及开发者的强烈要求,正式为图形渲染引入一条新管线 —— 计算管线(Compute Pipeline),计算管线是独立于光栅化渲染管线的另一条管线,它利用了 GPU 通用计算特性,通过计算着色器(Compute Shader),可以完成一些非图形渲染任务。

2.4.1 集成显卡

2011 年,AMD 整合旗下资源,第一次将 CPU 和 GPU 集成到一个芯片上,称为 APU(Accelerated Processing Unit),APU 上的显卡称为集成显卡(Integrated GPU)。在过去 GPU 或者说更早的图形加速卡,都是作为外部设备通过总线接入电脑,而 APU 中的多个核心共用主机内存,属于统一内存访问(Uniform Memory Access,UMA)架构,这里 GPU 不再有专用的显存,而是从内存分出连续的区域模拟显存。因为都共享内存了 CPU 和 GPU 的通信就不用经过外部总线,只需要将地址重新映射就可以了,这种技术称为 GART(Graphics Address Remapping Table)。统一内存的好处是缩小了电脑的尺寸,降低了功耗。因为主机内存的带宽不如独立显卡(Dedicated GPU)的显存,而且受芯片面积、功耗、发热的限制,集成显卡的性能能也相对孱弱。

2010 年之后,智能手机的兴起,移动端 GPU 在迅猛发展。2002 年,PowerVR 已经竞争不过 N/A 两家的显卡,转而将重心移到移动端 GPU 的开发上。出于尺寸和功耗的考量,移动端计算设备都是 SoC(System on A Chip)集成电路,也即将 CPU、GPU、内存、外存、网卡、声卡…… 都集成到一张芯片上。移动端 GPU 主要有四:1. ARM-Mali GPU;2.Qualcomm-Adreno GPU;3. PowerVR;4. Apple A Series SoC 集成的 GPU,这些 GPU 都采用 TBDR 技术节省带宽,移动端的 CPU/GPU 也是 UMA 架构。移动端 GPU 支持的图形接口是 OpenGL ES,算是 OpenGL 的子集。在苹果的设备上,使用自家的 Metal API。另外 Vulkan 也开始在移动设备上得到支持。

2.5 2013 年-至今 实时光线追踪

2013 年,PlayStation 4 和 Xbox One 游戏机开始在全球发售,这一时代游戏机都采用了 AMD 提供的 GCN 架构 Integrated GPU。此时,在各个领域都被竞争对手压一头的 AMD 主导推出了一套新的图形接口 Mantle。并在 PlayStation 4,XBox One 等主机上得到支持。但 Mantle API 不久后被 AMD 砍掉,随后将其捐赠给了 Kronos Group。Mantle 短暂的生命周期却影响了下一个世代图形 API 的设计思路,Direc3D 12,Vulkan,Metal 都能看到 Mantle 的影子。

Mantle 最大的突破是认识到 GPU 性能的突飞猛进,CPU 逐渐成为渲染管线的性能瓶颈。Mantle 优化了绘制命令从 CPU 到 GPU 中冗余的操作,并且向开发者开放了命令缓存(Command Buffer)命令队列(Command Queue)的接口。所谓的命令队列是在显卡驱动上的队列,调用图形接口后,CPU 会把相关命令放在这个队列上,GPU 从这个队列中取命令执行。如果命令队列满了,CPU 会等待 GPU 完成命令;如果命令队列为空,GPU 会等待 CPU 发出命令。在过去 GPU 的性能较差,很少会发生 GPU 等待 CPU 的情况,因此在 Mantle 之前的图形接口都是基于单核设计的,CPU 只有一个线程向命令队列提交命令,这足以使 GPU 满负荷工作。随着 GPU 性能的提升,单核 CPU 命令提交的速度逐渐跟不上 GPU 处理的速度。为了让 CPU 和 GPU 都能满负荷运行,发挥 CPU 多核并行潜力,Mantle 开放了命令缓存接口,图形程序可以启用多个线程,每个线程将命令放到各自的命令缓存中,然后将命令缓存中的命令提交(Submit)到命令队列中供 GPU 执行。

Mantle 带来的改进肯定不止如此,开发者如果能合理利用新特性,可以充分发挥 CPU 和 GPU 的并行特性,使它们都满负荷运行。2015 年,Direct3D 12 推出,Shader Model 也升级到 6.0,其继承了 Mantle 设计的核心理念,API 的设计更加底层,给了开发者更多的操作权。同一年,Khronos Group 宣布停止 OpenGL 的更新,新时代的 3D 图形接口变为 Vulkan。Vulkan 是 Mantle 真正的继承者。

2.5.1 实时光线追踪

2018 年,NVIDIA 推出搭载加速光线追踪核心 RT Core 的 Turing 架构。同一年,Direct3D 12 推出 DXR,供软件开发者使用。2020 年,AMD 的 RDNA2 架构 GPU 也宣布支持光线追踪。Vulkan 也在近期的更新中,推出了光线追踪拓展。光线追踪管线不同于光栅化管线,GPU 为光线追踪管线提供了加速光线与物体相交的硬件,属于光线追踪管线的固定功能阶段。光线追踪的其他阶段都是基于 GPGPU 的。目前,工业界普遍采用的是结合了光栅化管线、光线追踪管线、计算管线的混合图形渲染管线。用光线追踪完成软阴影、环境光遮蔽、间接光照、半透明物体等传统光栅化非常难做的任务。2020 年,PlayStation 5,XBox Series X 发售,新时代主机使用了 AMD RDNA2 架构定制 GPU,支持实时光线追踪。

2.5.2 网格渲染管线

光栅化图形渲染管线软硬件技术已经发展了 20 多年,自 2001 年可编程渲染管线出现之后,光栅化管线的基本流程没有大的变化,也即输入装配、顶点着色、光栅化、像素着色、输出合并。如此拆分渲染管线是有历史原因的,在 GPGPU 时代之前,显卡中有非常多的专用集成电路(Application Specific Integrated Circuit ,ASIC)单元,它们分别负责光栅化流水线的一个阶段的工作。

而在 GPGPU 已经普及的今天,几何处理阶段仍然分为多个阶段:顶点着色、外壳着色、细分曲面、域着色、几何着色,其中大部分任务都是运行在流处理上,拆分成如此多的阶段不仅增加显卡驱动的开销(追踪和验证每个阶段状态)、还会增加数据在阶段之间传递的开销。受到 GPU Driven Pipeline 的影响,越来越多的开发者呼吁利用 GPGPU 的特性重构几何处理阶段,使其更加符合现代 GPU 的设计。

2017 年,AMD 率先做出尝试,它将所有光栅化管线的几何处理任务交给一个可编程的 Primitive Shader。不同于顶点着色器,开发者可以把一个图元所有的数据输入 Primitive Shader,让它完成所有的几何处理任务(比如图元剔除、细分曲面、LOD、MVP 变换),而顶点着色器只能输入一个顶点的数据。但这一想法最终并没有落实到 AMD 的新显卡上。

2018,NVIDIA 宣布 Turning 架构 GPU 上将支持的全新着色器 Task Shader 和 Mesh Shader,取代传统光栅化管线的几何处理阶段,称为 Mesh Shader Pipeline。2019 年,Direct3D 12 和 Vulkan 宣布支持 Mesh Shader Pipeline。目前,这一管线还在初期阶段,还没有得到大规模的应用。