第11章:图形渲染

返回

渲染管线

概述

计算机中通过运算将三维虚拟空间投影为二维图像,因此生成图形和输出图形的相关技术是密不可分的。
将生成的三维图形输出为二维图像的过程就是渲染(Renderer)。

渲染指的是在计算机中生成几何图形数据经过运算后生成二维图像的过程。 渲染分为两类,一类是预渲染(Pre-Rendering),渲染质量好,但运算量大,常用于动漫、电影等静态成像领域。
另一类是实时渲染(Real-Time Rendering),渲染质量低,但运算量小,常用于游戏、XR等实时成像领域。
渲染管线(Renderer Pipeline)是一整套复杂的将计算机三维图形数据转换为二维图像的技术,是从获取图形数据开始经过计算机运算得到几何关系再转换成二维图像输出到显示输出装置上的过程。

渲染管线涉及到CPU和GPU的协同,与其他硬件共同完成。
读取三维数据及相关信息是由CPU完成,渲处理染是由GPU完成,CPU使用API调用GPU来实现二维或三维几何关系渲染,最终输出到显示装置上。
整个过程严格依赖软件和硬件的协作,因此并不存在通用的渲染管线。
经典文献中将渲染管线分为应用阶段(Application Stage)、几何阶段(Geometry Stage)和光栅化阶段(Rasterizer Stage)三个概念性阶段。

应用阶段

应用阶段由应用程序实现,通常由CPU来完成,主要有三个任务:获取三维数据;进行粗粒度剔除;设置模型渲染状态。应用阶段可以自主开发和配置。这一阶段输出的是渲染图元(Rendering Primitives),即点、线、面、体等的相关数据。应用阶段生成的渲染图元会被传递给几何阶段。

可以把应用阶段分为加载数据到显存;设置渲染状态;调用Draw Call三个阶段。
计算机首先将需要渲染处理的数据从硬盘中读取并加载到内存(Random Access Memory,RAM)中,然后再从内存中将数据加载到显卡的显存(Video Random Access Memory,VRAM)中。真实渲染中加载到显存的数据不仅包括顶点的几何信息,还包括法线方向、顶点颜色、纹理坐标等信息。
当数据加载到显存中后,可以设置数据的渲染状态,从而达到不同的渲染结果。此时,内存中的数据大部分可以移除,但重要数据可以保留不做移除。
渲染状态用来定义如何使用同一种方式进行渲染,渲染状态设置完成后CPU调用渲染命令来通知GPU执行渲染,这些渲染命令就是Draw Call。CPU向GPU发送Draw Call指令,指向需要被渲染的图像列表,GPU收到指令后根据数据和渲染状态进行计算,生成显示装置上的像素,像素矩阵构成二维图像。
Draw Call是CPU调用图形接口的方法,但是调用Draw Call次数太多,CPU会将大量时间花费在提交Draw Call上,造成CPU过载而导致运行效率降低。通过批处理(Batching)的方法可以减少Draw Call,即把很多小的Draw Call合并成一个大的Draw Call来执行。同时减少Draw Call的运算量还需要避免使用大量很小的网格,避免使用过多材质。

几何阶段

几何阶段处理所有与几何图元绘制相关的事件,通常由GPU来完成。几何阶段负责与每个渲染图元进行逐顶点、逐多边形的操作,主要包括顶点着色、几何曲面细分着色、裁剪、屏幕映射,可以进一步分成子阶段进行,把顶点坐标变换到屏幕空间中交由光栅器进行处理,输出屏幕空间中每个顶点的二维顶点坐标、深度值、着色等信息,并传递给光栅化阶段。

几何阶段将应用阶段加载到显存中的数据作为输入,GPU接收到CPU发送的Draw Call指令开始计算,进入几何阶段。
为了提高计算速度,几何阶段由GPU采用流水化操作,从而大大提高渲染速度。开发者对GPU没有绝对的控制权来控制渲染细节,但GPU提供可配置或可编程性的方法。
几何阶段可以分成很多子阶段,主要包括顶点着色器、曲面细分着色器、几何着色器、裁剪和屏幕映射。
Draw Call指令首先传递数据进入顶点着色器。
顶点着色器(Vertex Shader)是完全可编程的,用来实现顶点的空间变换、顶点着色等功能,主要完成坐标变换和逐顶点光照两项工作。坐标变换把顶点坐标从模型空间转换到齐次裁剪空间。逐顶点光照就是计算顶点接收光照后的颜色。
顶点着色器通过CPU的Draw Call指令对输入的每个顶点都调用一次顶点着色器,不创建也不销毁任何顶点,因此无法得到顶点与顶点之间的关系,只是并行化处理每一个顶点。
顶点着色器后续流程还包括两个可选的着色器:曲面细分着色器(TessellationShader)用于细分图元;几何着色器(Geometry Shader)用于执行逐图元着色操作或生成更多图元。
顶点着色器完成以上步骤后输出后续阶段所需要的数据,进入裁剪阶段(Clipping)。裁剪是将不在视野范围内的顶点裁剪掉并剔除不必要的图元,这个阶段是可配置的,但不可编程。
图元和视野的关系有3种:完全在视野内、部分在视野内、完全在视野外。完全在视野外的图元不需要渲染,完全在视野内的图元进入下一步。部分在视野内的图元需要进行裁剪。通过图元与视野面的交点对图元进行裁剪,剔除视野范围外的部分。
几何阶段最后一步是屏幕映射(Screen Mapping),把每个图元的坐标转换为屏幕坐标系的坐标,不可配置,不可编程。
屏幕坐标系是二维坐标系,与分辨率有很大关系。屏幕映射得到的屏幕坐标决定图元顶点在屏幕上的像素位置和距离,以便完成最终在屏幕上输出的结果。

光栅化阶段

光栅化阶段将几何阶段的传递过来的数据逐顶点计算插值后生成屏幕空间上的像素并渲染出最终图像,包括三角形设置、三角形遍历、片元着色、混合,通常由GPU来完成。

光栅化阶段对几何阶段得到的顶点数据逐个进行插值,再进行逐像素处理,决定每个渲染图元中的哪些像素会被绘制到屏幕上,将不会绘制的渲染图元进行全部或部分剔除,此阶段也可以进一步分成子阶段进行。
几何阶段操作完成后进入光栅化阶段。光栅化阶段有两个任务,计算每个图元覆盖多少像素以及计算像素的颜色。
光栅化第一个阶段称为三角形设置(Triangle Setup),计算三角形网格数据和几何关系,不可配置,不可编程。
光栅化第二个阶段称为三角形遍历(Triangle Traversal),又称为扫描变换(Scan Conversion),不可配置,不可编程。
三角形遍历会检查每个像素是否被某个三角形网格覆盖,并使用三角形网格三个顶点数据对整个覆盖区域像素进行插值计算生成片元,进入下一个阶段。
光栅化第三个阶段称为片元着色器(Fragment Shader),实现逐片元着色操作,可配置,可编程。
光栅化最后一个阶段是逐片元操作(Per-Fragment Operations),负责执行最后的输出操作,不可编程,但可配置。
逐片元操作有通过测试决定每个片元的可见性和将片元颜色值和缓冲区颜色值混合两个任务。
片元通过模板测试、深度测试来解决可见性问题,通不过测试的会被舍弃掉。测试通过后,进行混合(Blend)。如果是不透明的物体,则关闭混合操作,片元着色器计算得到的颜色值直接覆盖掉颜色缓冲区的颜色值。如果是透明的物体,则开启混合操作,片元着色器通过混合函数计算源颜色和目标颜色的混合效果。
为了避免在显示装置上看到正在进行光栅化的图元,GPU使用双重缓冲(Double Buffering)策略来完成,对渲染在幕后发生的放在后置缓冲(Back Buffer)中,通过交换后置缓冲和前置缓冲(Front Buffer)的内容显示到屏装置上,保证显示装置上的图像总是连续的。