Shader入门(2)渲染管线的概念

最近因为工作的需要而学习Unity Shader,为了方便记忆也方便和各位大佬们一起交流,所以把笔记发了出来,有问题的话希望大佬指正。文中多有引用,如有侵权请指出。

一、应用阶段

渲染流水线的第一步是在CPU中进行的,这一步叫做应用阶段(Application)。

应用阶段又分为三个步骤。1.数据加载2.设置渲染状态3.调用DrawCall

1.数据加载

(1)显卡可以更快的读取显存来运算,所以首先要把渲染数据从硬盘加载到显存中。

(2)加载的过程是:硬盘=>内存=>显存

(3)需要准备的场景数据:a.模型Mesh、material、shader、texture b.摄像机信息 c.灯光 (灯光位置,类型,参数)

(4)网格数据传送给显存之后就会从系统内存中删除,但是并非所有网格数据都会删除,部分数据如碰撞体等数据会保留在系统内存中进行其他运算。

(5)在这一步并没有把数据传入显存,只有经过渲染状态的命令之后才会成为图元进入显存中。

2.设置渲染状态

(1)渲染状态定义了场景中的网格使用什么着色器、光源属性、材质等来进行渲染。

(2)渲染状态包括了shader、texture、material、light等等属性。

(3)Unity内部都有默认的渲染状态,如果渲染前不做任何修改,那么所有网格都会使用同一种渲染状态。

(4)这些步骤完成之后会准备好一个需要渲染的图元列表,用来提供给GPU进行渲染。

3.调用Draw Call

(1)Draw Call 是指在应用层阶段,收集完图元的Assemble Data之后,CPU调用图形绘制API通知GPU的接口调用过程,每一次调用为一次Draw Call。(想知道自己当前在Unity窗口中渲染的物体会调用多少Draw Call,可以在 窗口=>Analisys=>帧调试 下面打开帧调试器查看。)

Shader入门(2)渲染管线的概念

(2)给定Draw Call之前的阶段都是CPU在工作,之后就由GPU工作了。而图形绘制API调用会比较耗时,所以当Draw Call过多之后,CPU满头大汗忙不过来,GPU就会闲的一批。

二、几何处理阶段

当GPU得到CPU传来的数据之后,就会在GPU中进行后续的操作,几何阶段(Geometry Processing)和光栅化阶段(Rasterization)都是在GPU中执行的。

Shader入门(2)渲染管线的概念

  1. 顶点着色器(Vertex Shader)

(1)顶点着色器通常用于实现坐标变换,顶点空间变换,逐顶点光照计算,输出后去阶段需要是数据,为像素着色器提供数据来源。

(2)不管其他操作有没有,顶点着色器必然会做的一件事就是:把顶点坐标从模型空间转换到齐次裁剪空间(这些空间什么的又是让人头疼的数学)。

(3)顶点着色器是不会得到顶点与顶点之间的关系的,所以在这里我们不会知道两个顶点是否属于同一个三角形网格。这样做的原因是这么算会提高运算效率。

(4)每处理一个顶点都会调用一次顶点着色器,所当我读到这里的时候惊叹于现代计算机的强大,游戏中的每一帧画面都包含有几万到几百万不等的顶点,计算一次这些顶点所需要的时间只需要一瞬间。

  1. 曲面细分着色器(Tessellation Shader)

(1)它可以对三角面进行细分,增加物体的三角面数。由外壳着色器(Hull Shader),镶嵌器(Tessellator)和域着色器(Domian Shader)构成。

(2)现在UE4当中已经有应用曲面细分材质,可以根据高度图或法线图进行细分,增加细节。

  1. 几何着色器(Geometry Shader)

(1)几何着色器可以输入一个完整图元(如三角形)输出一个或多个其他图元。

(2)曲面细分着色器增加的细分像是在原有的三角形内在切两刀,而几何着色器就是直接把纸折成花。

(3)通常来说,曲面细分着色器和几何着色器非常非常消耗运算,所以不是内力深厚的机器不要轻易尝试。

  1. 裁剪(Clipping)

(1)这一阶段,会去掉所有你屏幕上看不到的部分(就是不在你的裁剪空间中的面)。

Shader入门(2)渲染管线的概念

(2)背面剔除(Face culling):接下来还会把所有背面朝向你的面剔除掉,只要这个面被别的面挡住了,就要剔除。

Shader入门(2)渲染管线的概念

(4)其实裁剪和背面剔除这两个步骤,又可以叫图元组装(Primitives Assembly),做完这个流程之后就可以减少进入光栅化阶段的图元数量(也就是面数),加速渲染过程。

  1. 屏幕映射(Screen Mapping)

(1)此阶段的任务是把前几步操作产生的图元的坐标,转换到屏幕坐标系(在整个三维渲染流水线上,有不下十个坐标系,以后都会学习到,足够让我晕头转向)。

(2)屏幕映射得到的屏幕坐标决定了每个顶点对应的像素位置。

(3)需要注意的是在OPenGL和DirectX这两款开放式图形库(图形API)上渲染出来的图片是上下颠倒的,因为两个坐标的Y轴是相反的。

三、光栅化阶段

光栅化是讲3D连续的物体转化为离散屏幕像素点的过程。光栅化会确定图元所覆盖的片段,利用顶点属性插值得到片段的属性信息。

这个阶段是硬件行为,不可编程。

  1. 三角形设置

(1)这个步骤是计算光栅化一个三角网格所需的信息,也就是计算每条边上的像素坐标。

(2)这个阶段有时也称作三角形建立阶段,个人觉得这种说法更为准确。

  1. 三角形遍历(Triangle Traversal)

(1) 这个阶段是检查每个像素是被哪一个三角形网格所覆盖。当像素被三角形覆盖时,就会生成一个片元。(一般在检查像素是否被覆盖时,是检测每个像素的中心是否被三角形网格所覆盖,但是现在也有一些更加复杂和优化的检测方法,使得像素生成更加合理。)

(2) 寻找哪些样本或者像素在一个三角形中的过程,就被称为三角形遍历(也称为扫描变换)。

(3) 在此阶段中,会根据上一个阶段的计算结果来判断一个三角形网格覆盖了哪些像素,并使用三角形网格三个顶点信息对整个覆盖区域的像素进行插值。

Shader入门(2)渲染管线的概念

(4)这一步输出得到的是一个片元序列。片元是以像素点为单位的,但是并不是最终的像素,而是一个很多信息的集合,包括了屏幕坐标,深度信息,法线坐标顶点信息,纹理坐标等等。一个最终输出到屏幕上的像素点,大多数时候只是RGB颜色而已。

  1. 片元着色器(Fragment Shader)

(1)首先需要说明的是片元着色器和像素着色器其实是同一种东西,只是在Open GL和DirectX中的叫法不同。

(2)片元着色器输入上一个阶段对顶点信息插值得到的结果,输出的是一个或者多个颜色值。

(3)在这个阶段可以使用各种各样的渲染技术,其中最为重要的就是纹理贴图,也就是美术们制作的各种颜色,法线,金属度,粗糙度等等贴图。

  1. 逐片元操作(Per-Fragment Operations)

(1)再次首先说明,逐片元操作和输出合并阶段或者说融合阶段是同一种东西!!只是在Open GL和DirectX中的叫法不同。

Shader入门(2)渲染管线的概念

(2)该阶段是高度可配置性的。

(3)该阶段的第一个主要任务:决定每个片元的可见性。这通常需要对片元进行一系列的测试。

(4)测试的过程比较复杂,而且不同的图形接口中也不太一样,书中给出了两个流程图。

Shader入门(2)渲染管线的概念

(5)第二个主要任务是对通过了测试的片元,把这个片元的颜色值和已经储存在颜色缓冲区的颜色进行混合。

(6)颜色缓存区可以有很多中缓存,如深度缓存,模板缓存,帧缓存等信息,这些缓存可以用来实现很多非常实用的图像效果。

(7)混合:对于半透明物体,我们需要使用混合操作来让这个物体看起来是透明的。开启了混合之后GPU会取出源颜色和目标颜色,将两者混合。源颜色指的是片元着色器的颜色值,而目标颜色是存在与颜色缓冲区的颜色值。之后就会使用一个混合函数来进行混合操作。这个操作很像是PS中的图层混合模式。

(8)当模型经过层层计算和测试之后,就会显示在屏幕上。屏幕显示了颜色缓存中的内容。为了避免人们看到图元正在被光栅化以及被送到屏幕的过程,使用了双缓存技术(Double Buffering)。这意味着场景的渲染在后置缓存(Back Buffer)中离屏进行。一旦场景在后置缓存中被渲染完毕,后置缓存中的内容就会与之前显示在屏幕上的前置缓存(Front Buffer)中的内容交换。

四、总结

  1. 这只是一个简单的流程介绍,其实际的实现过程远比这个流程复杂,各种图形接口也有不同的实现方式。而且随着技术的不断发展会对其中的一些过程和细节进行不断的调整和优化,知道这些知识远不能算是懂什么叫做渲染管线。

  2. 理解渲染管线是学习Shader的基础,和Unity图像的实现的所有步骤都息息相关。

Shader入门(2)渲染管线的概念

参考图书《Unity Shader入门精要》