首页 Real-Time Rendering 第二章 图形渲染流水线(The Graphics Rendering Pipeline)
文章
取消

Real-Time Rendering 第二章 图形渲染流水线(The Graphics Rendering Pipeline)

Real-Time Rendering

第二章 图形渲染流水线

这一章讲的是实时渲染的核心部分,即图形渲染流水线。图形渲染流水线的功能是通过给定的摄像机、三维物体信息、光源等信息,生成一个二维图像。因此,渲染流水线是实时渲染的底层工具。

在现实世界中,流水线概念以许多不同的形式表现出来,从工厂装配线到快餐厨房。它也适用于图形渲染。管道由几个阶段组成,每个阶段执行总任务的一部分。

流水线阶段并行执行,每个阶段取决于前一阶段的结果。理想情况下,将一个非流水线系统分为n个流水线,可以提供n倍的加速。提高性能是使用流水线的主要原因。

实时渲染流水线大致分为四个主要阶段:应用阶段(application)、几何处理阶段(geometry processing)、光栅化阶段(rasterization)以及像素处理阶段(pixel processing),如下图所示。

这些阶段中的每个阶段本身通常是一个流水线,这意味着它由几个子阶段组成。

渲染速度可以用FPS表示,即每秒渲染的图像数。它也可以用赫兹(Hz)表示,赫兹只是1/s的符号,即更新频率。通常也只说明渲染图像所需的时间,以毫秒(ms)为单位。通常每个帧期间执行的计算的复杂度不同,生成图像的时间也会有所不同。

顾名思义,应用阶段由应用程序驱动,通常在通用CPU上运行的软件中实现。这些CPU通常包括能够并行处理多个执行线程的多个核心,这使CPU能够高效地运行由应用阶段负责的大量任务。通常在CPU上执行的任务包括碰撞检测、全局加速算法、动画、物理模拟和许多其他任务,具体取决于应用程序的类型。

几何处理阶段处理变换、投影和其他类型的几何体处理。这一阶段计算要绘制的内容、应如何绘制以及应在何处绘制。

光栅化阶段通常接受三个顶点信息,这三个顶点信息组成一个三角形,然后计算这个三角形所包含的像素,然后把像素信息传递到像素处理阶段。

最后,像素处理阶段逐像素确定其颜色,并且可以执行深度测试以确定该像素是否可见。这一阶段还可以将新的像素颜色和之前的像素颜色混合。

几何处理阶段、光栅化阶段、像素处理阶段通常在包含许多可编程内核以及固定操作硬件的图形处理单元(GPU)上执行。

  • 应用阶段

开发者可以完全控制应用阶段中发生的事情,因为它通常在CPU上执行。因此,开发者可以完全确定应用阶段的实现,并在以后修改它以提高性能。此处的更改也会影响后续阶段。例如,在应用阶段使用适当的算法或设置可能会减少要渲染的三角形数量。

然而,使用一种称为计算着色器的单独模式也可以让一些应用程序工作由GPU执行。此模式将GPU视为高度并行的通用处理器,忽略其专门用于渲染图形的特殊功能。

应用阶段需要处理好待渲染的几何信息,包括点、线、三角形,再将几何信息传递给几何处理阶段。

除此之外,应用阶段还要实现碰撞检测、键盘鼠标等外部设备的输入、加速算法(例如特定的剔除算法)等功能。

  • 几何处理阶段

几何处理阶段负责处理每个三角形以及顶点。此阶段进一步分为以下功能阶段:顶点着色(Vertex Shading)、投影(Projection)、裁剪(Clipping)和屏幕映射(Screen Mapping)

    • 顶点着色阶段

顶点着色有两个主要任务,即计算顶点的位置和计算程序员想要的任何顶点输出数据,例如法线和纹理坐标。

通常来说,物体的颜色是通过将灯光应用到每个顶点的位置和法线以及顶点处的颜色来计算的。因此,该可编程顶点处理单元被命名为顶点着色器(vertex shader)。

      • 可选的顶点处理

当顶点处理完成后,GPU上可以按以下顺序执行几个可选阶段:曲面细分(tessellation)、几何体着色(geometry shading)和流输出(stream output)。它们的使用既取决于硬件的能力(并非所有GPU都有),也取决于程序员的需求。

第一个可选阶段是曲面细分阶段。假设你有一个反弹的球对象。如果用一组三角形表示,可能会遇到质量或性能问题。你的球可能在5米之外看起来不错,但近距离观察各个三角形,特别是沿着轮廓,就会变得可见。如果使用更多三角形制作球以提高质量,当球离屏幕较远且仅覆盖屏幕上的几个像素时,可能会浪费大量的处理时间和内存。通过曲面细分,可以使用适当数量的三角形生成曲面。场景中的摄影机可用于确定生成多少个三角形:面片靠近时很多,面片远离时很少。

第二个可选阶段是几何体着色器。此着色器早于曲面细分着色器,因此在GPU上更常见。它类似于曲面细分着色器,因为它接受各种基本体,并可以生成新的顶点。这是一个更为简单的阶段,因为创建的范围有限,输出的图元类型也非常有限。几何体着色器有多种用途,其中最常用的是粒子生成。想象一下模拟烟花爆炸,每个火球都可以用一个点,一个顶点来表示。几何体着色器可以将每个点转化为一个正方形(由两个三角形组成),覆盖多个像素,从而为我们提供一个图元进行着色。

第三个可选阶段是流输出阶段。我们可以选择将处理后的顶点输出到数组以进行进一步处理,而不是将处理后的顶点沿管道的其余部分发送到屏幕。这些数据可以在以后的过程中由CPU或GPU本身使用。

    • 投影阶段

从一个顶点到屏幕上,需要进行几个坐标空间变换。初始时,一个顶点在一个模型上,通过模型变换把顶点在模型空间中的位置和方向变换到世界空间。然后,为了能够观察到这个物体,我们还需要一个摄像机。摄像机在世界中也有位置和方向,我们再通过观察变换把顶点的世界坐标变换到观察空间。此时我们就得到了一个顶点在以摄像机为原点的位置和方向了。

    • 裁剪阶段

只有全部或部分位于视图内的图元需要传递到光栅化阶段(以及随后的像素处理阶段),然后将它们绘制在屏幕上。

完全在视图内的图元可以直接传递到下一阶段,完全在视图外的图元会被裁剪掉,不会被传递到下一阶段,而一部分在视图内、另一部分在视图外的图元需要进行额外处理,即在视图边缘生成新的顶点,如下图所示。

    • 屏幕映射阶段

裁剪后的图元被传递到这一阶段后,图元在裁剪空间下还是三维坐标,这一阶段把图元的xy坐标通过平移、缩放变换为屏幕坐标,z坐标会重映射到一个区间。屏幕坐标和重映射的z值会传递到光栅化阶段。

  • 光栅化阶段

光栅化阶段有两个最重要的目标:计算图元覆盖了哪些像素,计算这些像素的颜色。

该阶段包含以下两个阶段:

    • 三角形设置(Triangle Setup)

该阶段主要用于计算三角形表面相关数据信息,以供下一阶段使用,该过程在专门为其设计的硬件上执行。

    • 三角形遍历(Triangle Traversal)

在这个阶段会检查像素中心是否被三角形覆盖,如果被覆盖就会生成一个片元(fragment)。每个三角形片元的属性均由三个顶点的数据插值而生成,这些数据来自几何处理阶段的着色数据。

  • 像素处理阶段

此时,三角形或其他图元内部的所有像素都已找到,这是前面所有阶段组合的结果。像素处理阶段是对图元内部的像素或样本执行逐像素或逐样本计算和操作的阶段。像素着色阶段包含以下两个阶段:

    • 像素着色阶段(Pixel Shading)

与三角形设置和遍历阶段(通常由专用硬件执行)不同,像素着色阶段由可编程GPU内核执行。为此,程序员需要为像素着色器提供一个程序(pixel shader)(在OpenGL中称为fragment shader)。这里可以使用多种技术,其中最重要的技术之一是纹理。

    • 合并阶段(Merging)

这个阶段有两个主要任务:

  1. 决定每个片元的可见性,需要经过一些测试工作:深度测试,模板测试。

  2. 合成之前储存于缓冲器中的由之前像素着色阶段产生的片段颜色。

每个像素的信息存储在颜色缓冲区中,颜色缓冲区是一个矩形的颜色数组(每种颜色的红色、绿色和蓝色分量)。合并阶段负责将像素着色阶段产生的片段颜色与当前存储在缓冲区中的颜色相结合。这一阶段也被称为ROP(raster operations pipeline 或 render output unit)。与着色阶段不同,执行此阶段的GPU子单元通常不完全可编程。但是,它是高度可配置的,可以实现各种效果。

此阶段还负责解决可见性问题。

深度测试(Depth Test):

对于大多数甚至所有图形硬件,这是通过z-buffer(也称为深度缓冲)算法完成的。当一个图元将要被绘制时,需要将他的z-value和z-buffer中的z-value进行比较,如果新的z-value较小,那这个图元将会被绘制,z-buffer中的z-value会更新为新的z-value;反之,如果新的z-value较大,那这个图元就会被抛弃。

z-buffer算法简单,具有O(n)的时间复杂度(其中n是渲染的图元的数量),适用于可以为每个(相关)像素计算z值的图元。该算法允许大多数图元以任何顺序渲染,这也是它流行的另一个原因。

但是,z-buffer在屏幕上的每个点只存储一个深度,因此不能用于部分透明的图元。必须在所有不透明图元之后,按照从后到前的顺序,或使用单独的顺序独立算法渲染这些图元。

我们已经提到,颜色缓冲区用于存储颜色,z-buffer存储每个像素的z值。

模板测试(Stencil Test):

和深度测试一样,它也可能会丢弃片段。接下来,被保留的片段会进入深度测试。模板测试是根据又一个缓冲来进行的,它叫做模板缓冲(Stencil Buffer),我们可以在渲染的时候更新它来获得一些很有意思的效果。

一个模板缓冲中,通常每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值。我们可以将这些模板值设置为我们想要的值,然后当某一个片段有某一个模板值的时候,我们就可以选择丢弃或是保留这个片段了。

当图元到达并通过以上阶段时,从相机的角度可以看到的图元将显示在屏幕上。屏幕显示颜色缓冲区的内容。当渲染数据量较大时,渲染可能需要很长时间,还可能会出现闪烁现象。为了解决这些问题,需要使用双缓冲。这意味着场景的渲染在屏幕外的back buffer中进行。在back buffer中渲染场景后,back buffer的内容将与先前在屏幕上显示的front buffer的内容交换。交换通常发生在垂直回溯(vertical retrace)期间,此时这样做是安全的。

本文由作者按照 CC BY 4.0 进行授权

Real-Time Rendering 第一章 介绍(Introduction)

Real-Time Rendering 第四章 变换(Transforms)

热门标签