Unity Shader 入门(一):渲染管线

Posted by Kurong on 2020-04-28

什么是渲染管线?

Shader(着色器):是渲染管线上的一小段程序,要了解Shader首先要明白渲染管线是什么呢?

渲染管线的主要功能是在给定虚拟相机、三维物体、光源、照明模式,以及纹理等诸多
条件的情况下,生成或绘制一幅二维图像的过程。对于实时渲染来说,渲染管线就是基础,可以说渲染管线是实时渲染的底层工具。

在概念上可以将图形渲染管线分为四个阶段:

  • 应用程序阶段(The Application)
  • 几何阶段(The Geometry)
  • 光栅化阶段(The Rasterizer)
  • 像素处理阶段(pixel processing)

这里有几个要点:

  • 每个阶段本身也可能是一条管线,如图中的几何阶段所示。此外,还可以对有的阶段进行全部或者部分的并行化处理,如图中的像素处理阶段。应用程序阶段虽然是一个单独的过程,但是依然可以对之进行管线化或者并行化处理。
  • 最慢的管线阶段决定绘制速度,即图像的更新速度,这种速度一般用 frames per second(FPS) 来表示,也就是每秒绘制的图像数量,或者用 Hertz (Hz) 来表示。

应用程序阶段 The Application Stage

顾名思义,应用程序阶段是由应用程序驱动的,一般是图形渲染管线概念上的第一个阶段。开发者能够对该阶段发生的情况进行完全控制,可以通过改变实现方法来改变实际性能,可以做的任务包括:

  • 碰撞检测、输入检测、力反馈
  • 纹理动画、变换仿真、几何形变
  • 等等

在其他阶段是全部或者部分建立在硬件基础上,因此要改变实现过程会非常困难。

正因应用程序阶段是软件方式实现,因此不能像几何和光栅化阶段那样继续分为若干个子阶段。但为了提高性能,该阶段还是可以在几个并行处理器上同时执行。在 CPU 设计上,称这种形式为超标量体系(superscalar)结构,因为它可以在同一阶段同一时间做不同的几件事情。

应用程序阶段通常完成一些不在其他阶段执行的计算,如层次视锥裁剪等加速算法就可以在这里实现。

应用程序阶段的主要任务:在应用程序阶段的末端,将需要在屏幕上(具体形式取决于具体输入设备)显示出来绘制的几何体(也就是绘制图元(rendering primitives):如点、线、矩形等)、以及摄像机位置输入到绘制管线的下一个阶段————几何阶段。

几何阶段 The Geometry Stage

这个阶段用于处理几乎所有我们要绘制的几何相关的事情,比如决定画什么、怎么画、画在哪里等等(这一段主要在GPU上进行),因为事情太多,因此可以进一步细分成一个小的流水线:

  • 顶点着色 Vertex Shading
  • 投影 Projection
  • 裁剪 Clipping
  • 屏幕映射 Screen Mapping

顶点着色 Vertex Shading

顶点着色 Vertex Shading 阶段主要的任务是:

  • 计算顶点的位置
  • 计算程序员要的顶点的输出数据例如:法线、纹理贴图坐标等等

一般来说, 模型大部分的着色是通过:计算光源作用于每个顶点的位置、法线信息的结果,存储时仅存储顶点处的颜色,然后在三角形上插值这些颜色来得到的。顶点着色器现在是一个更通用的部分, 专门用于设置与每个顶点关联的数据比如可以用于顶点绑定、图像变换等等。

在绘制到屏幕上之前,模型通常需要变换到若干不同的空间或坐标系中。模型变换的变换对象一般是模型的顶点和法线。物体的坐标称为模型坐标。世界空间是唯一的,所有的模型经过变换后都位于同一个空间中。

就像上文提到,应该仅对相机(或者视点)可以看到的模型进行绘制,而相机在世界空间中有一个位置和一个方向。为了方便投影和裁剪,必须对相机和所有的模型进行视点变换。变换的目的就是要把相机放在原点,然后进行视点校准,使其朝向 Z 轴负方向,y 轴指向上方,x 轴指向右边。在视点变换后,实际位置和方向就依赖于当前的 API。我们称上述空间为相机空间或者观察空间,下图显示了视点变换对相机和模型的影响。

为了产生逼真的场景,仅仅渲染形状和位置是远远不够的,我们需要对物体的外观进行建模。而物体经过建模,会得到对包括每个对象的材质,以及照射在对象上的任何光源的效果在内的一些刻画。且光照和材质可以用任意数量的方式,从简单的颜色描述到复杂的物理描述来模拟。

确定材质上的光照效果的这种操作被称为着色(shading),着色过程涉及在对象上的各个点处计算着色方程(shading equation)。通常,这些计算中的一些在几何阶段期间在模型的顶点上执行(vertex shading),而其他计算可以在每像素光栅化(per-pixel rasterization)期间执行。可以在每个顶点处存储各种材料数据,诸如点的位置,法线,颜色或计算着色方程所需的任何其它数字信息。顶点着色的结果(其可以是颜色,向量,纹理坐标或任何其他种类的着色数据)计算完成后,会被发送到光栅化阶段以进行插值操作。

着色计算通常认为是在世界空间中进行的。在实践中,有时需要将相关实体(诸如相机和光源)转换到一些其它空间(诸如模型或观察空间)并在那里执行计算,也可以得到正确的结果。这是因为如果着色过程中所有的实体变换到了相同的空间,着色计算中需要的诸如光源,相机和模型之间的相对关系是不会变的。

投影 Projection

在光照处理之后,渲染系统就开始进行投影操作,即将视体变换到一个对角顶点分别是 (-1,-1,-1) 和 (1,1,1) 单位立方体(unit cube)内,这个单位立方体通常也被称为规范视域体(Canonical View Volume,CVV)。目前,主要有两种投影方法,即:

  • 正交投影(orthographic projection,或称 parallel projection):正交投影的可视体通常是一个矩形,正交投影可以把这个视体变换为单位立方体。正交投影的主要特性是平行线在变换之后彼此之间仍然保持平行,这种变换是平移与缩放的组合。
  • 透视投影(perspective projection):相比之下,透视投影比正交投影复杂一些。在这种投影中,越远离摄像机的物体,它在投影后看起来越小。更进一步来说,平行线将在地平线处会聚。透视投影的变换其实就是模拟人类感知物体的方式。

正交投影和透视投影都可以通过 4 x 4 的矩阵来实现,在任何一种变换之后,都可以认为模型位于归一化处理之后的设备坐标系中。虽然这些矩阵变换是从一个可视体变换到另一个,但它们仍被称为投影,因为在完成显示后,Z 坐标将不会再保存于的得到的投影图片中。通过这样的投影方法,就将模型从三维空间投影到了二维的空间中

可选择的顶点处理

这一部分是这个流水线上的可选项,不一定必须经历这个环节,是否启用取决于硬件的条件,这些环节彼此之间是相互独立的,按照顺序是:

  • 曲面细分 tessellation:例如我们用三角形来描绘物体,理论上将三角形划分的越细物体也就越精细,如果这个物体离摄像头很远或者只在摄像头内出现一角,花费大量的三角形细分就比较浪费资源了。但在应用曲面细分,曲面就可以用适当的三角形数来生成。
  • 几何元着色 geometry shading:这个阶段的作用和曲面细分类似,都可以生成新的顶点,但这是一个输出与输入的图元都受限的阶段,相应的也较为简单快速。最广泛应用这个阶段的地方可能是 生成粒子 例如烟火效果,每一点火星就好像一个点,几何元着色可以将这个点由一个面对观众的正方形(两个三角形组成)来表示,这更方便我们渲染图元。
  • 流式输出 stream output:这个阶段让我将GPU当作几何引擎,这个阶段我们不是把数据传给剩余的流水线去处理,而是可以选择将它们输出到数组以进行进一步处理,这些数据可以给CPU,或者GPU自己,这个阶段在经常用于生成粒子,例如上面烟火的例子。

裁剪 Clipping

只有当图元完全或部分存在于视体(也就是上文的规范视域体CVV)内部的时候,才需要将其发送到光栅化阶段,这个阶段可以把这些图元在屏幕上绘制出来。显然一个图元相对视体内部的位置,分为三种情况:完全位于内部、完全位于外部、部分位于内部。所以就要分情况进行处理:

  • 当图元完全位于视体内部:那么它可以直接进行下一个阶段。
  • 当图元完全位于视体外部:不会进入下一个阶段,可直接丢弃,因为它们无需进行渲染。
  • 当图元部分位于视体内部:则需要对那些部分位于视体内的图元进行裁剪处理。对部分位于视体内部的图元进行裁剪操作,这就是裁剪过程存在的意义。裁剪过程见下图。

屏幕映射 Screen Mapping

只有在视体内部经过裁剪的图元,以及之前完全位于视体内部的图元,才可以进入到屏幕映射阶段。进入到这个阶段时,坐标仍然是三维的(但显示状态在经过投影阶段后已经成了二维),每个图元的 x 和 y 坐标变换到了屏幕坐标系中,屏幕坐标系连同 z 坐标一起称为窗口坐标系。假定在一个窗口里对场景进行绘制,窗口的最小坐标为(x1,y1),最大坐标为(x2,y2),其中 x1 < x2,y1 < y2。屏幕映射首先进行平移,随后进行缩放,在映射过程中 z 坐标不受影响。新的 x 和 y 坐标称为屏幕坐标系,与 z 坐标一起(OpenGL下是[-1,+1],DirectX下是[0,1])进入光栅化阶段。如下图:

屏幕映射阶段的一个常见困惑是整型和浮点型的点值如何与像素坐标(或纹理坐标)进行关联。因为像素的中点可以定义在0.5处,因此索引在[0,9]之内的像素可以表示的浮点范围在[0.0,10.0)之内,这个转换可以简单的通过以下来转变:
$$d=floor©$$
$$c=d+0.5$$
其中 d 是像素的整数索引, c 是像素内的浮点值。

光栅化阶段 The Rasterizer Stage

给定经过变换和投影之后的顶点,颜色以及纹理坐标(均来自于几何阶段),给每个像素(Pixel)正确配色,以便正确绘制整幅图像。这个过个过程叫光珊化(rasterization)这个阶段主要分成两部分:三角形设定(Triangle Setup)也被称为图元装配和三角形遍历(Triangle Traversal),即从二维顶点所处的屏幕空间(所有顶点都包含 Z 值即深度值,及各种与相关的着色信息)到屏幕上的像素的转换。

三角形设定 Triangle Setup

三角形设定阶段主要用来计算三角形表面差异和边缘方程等其他相关数据。该数据主要用于扫描转换(scan conversion),以及由几何阶段处理的各种着色数据的插值操作所用,该过程在专门为其设计的硬件上执行。

三角形遍历 Triangle Traversal

在三角形遍历阶段将进行逐像素检查操作,检查该像素处的像素中心是否由三角形覆盖,而对于有三角形部分重合的像素,将在其重合部分生成片段(fragment)。找到哪些采样点或像素在三角形中的过程通常叫三角形遍历(TriangleTraversal)或扫描转换(scan conversion)。每个三角形片段的属性均由三个三角形顶点的数据插值而生成。这些属性包括片段的深度,以及来自几何阶段的着色数据。

像素处理 Pixel Processing

在这个阶段,所有的像素已经都在一个三角形中或者是前面一系列处理之后的图元下,像素处理阶段被分为像素着色和融合两个子阶段

像素着色 Pixel Shading

所有逐像素的着色计算都在像素着色阶段进行,使用插值得来的着色数据作为输入,输出结果为一种或多种将被传送到下一阶段的颜色信息。纹理贴图操作就是在这阶段进行的。像素着色阶段是在可编程 GPU 内执行的,在这一阶段有大量的技术可以使用,其中最常见,最重要的技术之一就是纹理贴图(Texturing)。纹理贴图在书的第六章会详细讲到。简单来说,纹理贴图就是将指定图片“贴”到指定物体上的过程。而指定的图片可以是一维,二维,或者三维的,其中,自然是二维图片最为常见。如下图所示:

融合 Merging

每个像素的信息都储存在颜色缓冲器中,而颜色缓冲器是一个颜色的矩阵列(每种颜色包含红、绿、蓝三个分量)。融合阶段的主要任务是合成当前储存于缓冲器中的由之前的像素着
色阶段产生的片段颜色。不像其它着色阶段,通常运行该阶段的 GPU 子单元并非完全可编程的,但其高度可配置,可支持多种特效。

这个阶段还负责可见性问题的处理。这意味着当绘制完整场景的时候,颜色缓冲器中应该还包含从相机视点处可以观察到的场景图元。对于大多数图形硬件来说,这个过程是通过 Z 缓冲(也称深度缓冲器)算法来实现的。Z 缓冲算法非常简单,具有 $O(n)$ 复杂度(n 是需要绘制的像素数量),只要对每个图元计算出相应的像素 z 值,就可以使用这种方法,大概内容是:

  • Z 缓冲器和颜色缓冲器形状大小一样,每个像素都存储着一个 z 值,这个 z 值是从相机到最近图元之间的距离。
  • 每次将一个图元绘制为相应像素时,需要计算像素位置处图元的 z 值,并与同一像素处的 z 缓冲器内容进行比较。
  • 如果新计算出的 z 值,远远小于 Z 缓冲器中的 z 值,那么说明即将绘制的图元与相机的距离比原来距离相机最近的图元还要近。这样像素的 z 值和颜色就由当前图元对应的值和颜色进行更新。反之,若计算出的 z 值远远大于 z 缓冲器中的 z 值,那么 z 缓冲器和颜色缓冲器中的值就无需改变。
  • 值得注意的是这个算法不能用于渲染半透明物体,原因我们会在往后的文章解释

颜色缓冲器用来存储颜色,z 缓冲器用来存储每个像素的 z 值,还有其他缓冲器可以用来过滤和捕获片段信息。

  • 比如 alpha 通道(alpha channel)和颜色缓冲器联系在一起可以存储一个与每个像素相关的不透明值。可选的 alpha 测试可在深度测试执行前在传入片段上运行。片段的 alpha 值与参考值作某些特定的测试(如等于,大于等),如果片断未能通过测试,它将不再进行进一步的处理。alpha 测试经常用于不影响深度缓存的全透明片段的处理。
  • 模板缓冲器(stencil buffer)是用于记录所呈现图元位置的离屏缓存。每个像素通常与占用 8 个位。图元可使用各种方法渲染到模板缓冲器中,而缓冲器中的内容可以控制颜色缓存和 Z 缓存的渲染。举个例子,假设在模版缓冲器中绘制出了一个实心圆形,那么可以使用一系列操作符来将后续的图元仅在圆形所出现的像素处绘制,类似一个 mask 的操作。模板缓冲器是制作特效的强大工具。而在管线末端的所有这些功能都叫做光栅操作(raster operations ,ROP)或混合操作(blend operations)可以将颜色缓冲区中当前的颜色与在三角形内处理的像素的颜色混合,这样可以实现半透明或者颜色累积的效果。
  • 帧缓冲器(frame buffer)通常包含一个系统所具有的所有缓冲器,但有时也可以认为是颜色缓冲器和 Z 缓冲器的组合。

而当图元通过光栅化阶段之后,从相机视点处看到的东西就可以在荧幕上显示出来。为了避免观察者体验到对图元进行处理并发送到屏幕的过程,图形系统一般使用了双缓冲(double buffering)机制,这意味着屏幕绘制是在一个后置缓冲器(back buffer)中以离屏的方式进行的。一旦屏幕已在后置缓冲器中绘制,后置缓冲器中的内容就不断与已经在屏幕上显示过的前置缓冲器中的内容进行交换,当然,只有当不影响显示的时候才进行交换。