流水线:可以将一个复杂的流程拆分为简单的几个步骤分别进行,由此可以提高单位时间的生产量;理想情况下,如果把一个非流水线系统分成n个流水线阶段,且每个阶段耗费时间相同的话,那么会使整个系统得到n倍的速度提升。

    性能瓶颈:流水线系统中决定最后生产速度的是最慢的工序所需要的时间,即性能瓶颈。

    渲染流水线(渲染管线)

        目的:由一个三维场景出发,渲染(生成)一张二维图像,即计算机需要从一系列的顶点数据、纹理等信息出发,把这些信息最终转换成一张人眼可以看到的图像。

        输入:虚拟摄像机、光源、Shader、纹理等。

        工作所在模块:CPU、GPU

        流程应用阶段(Application Stage)、几何阶段(Geometry Stage)、光栅化阶段(Rasterizer Stage);应用阶段在CPU中进行,因此开发者拥有绝对控制权,为了提高渲染性能而进行的遮罩剔除工作也是在此阶段进行,最终输出渲染图元(点、线、三角面等)给几何阶段;几何阶段在GPU中进行,在此阶段会把顶点坐标变换到屏幕空间中,最终将屏幕空间的二维顶点坐标、每个顶点对应的深度值、着色等信息传递给光栅化阶段;光栅化阶段在GPU中进行,在此阶段会将从上一阶段获取到的信息进行插值运算再进行逐像素处理,此阶段决定每个渲染图元中最终能绘制在屏幕上的像素。        

    Shader:即着色器,属于GPU流水线上一些可高度编程的阶段,由Shader编译出来的最终代码运行在GPU上(对于固定管线的渲染来说,Shader有时等同于一些特定的渲染设置);

        类型:顶点着色器(Vertex Shader)、片元着色器(Fragment Shader)等

        功能:可以通过着色器控制流水线中的渲染细节,例如用顶点着色器进行顶点变换、传递数据,用片元着色器进行逐像素的渲染。

    应用阶段流水线

        1.把数据加载到显存中:因为显卡对显存访问速度快且不存在访问权限问题,故数据(顶点信息、法线、颜色、纹理、坐标等)都需从硬盘中加载到系统内存,然后加载到显存中,加载到显存后内存中用不到的数据就可以移除了。

        2.设置渲染状态:定义了场景中网格的状态(着色器类型、光源属性、材质等)。

        3.调用Draw Call:DC(Draw Call)是由CPU发起,由GPU执行的指令,这个指令仅仅指向一个需要被渲染的图元列表,而不会再包含任何材质信息。当给定一个DC时,GPU就会根据给定的图元列表按照GPU流水线进行计算,最终输出形成屏幕上的像素。

    GPU流水线(几何阶段、光栅化阶段)

        

unity 积水_渲染

      几何阶段流水线

        顶点着色器(Vertex Shader):完全可编程,通常用于实现顶点间的空间变换(对顶点坐标进行某种变换,可以用于制作顶点动画,比如模拟水面、布料)、顶点着色等功能。数据来自CPU,处理单位是顶点,顶点着色器本身不可创建或销毁任何顶点,也无法得到顶点之间的关系,输入进来的每个顶点都会调用一次顶点着色器。顶点着色器必须完成的一个工作是把顶点坐标从模型空间转换到齐次裁剪空间(常见的处理函数o.pos=mul(UNITY_MVP,v.position);),接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(NDC)。最常见的输出路径是经光栅化后交给片元着色器进行处理,还可以把数据发送给曲面细分着色器或者几何着色器。

        曲面细分着色器(Tessellation Shader):可选着色器,用于细分图元。

        几何着色器(Geometry Shader):可选着色器,可用于执行逐图元的着色操作,或用于产生更多图元。

        裁剪(Clipping):可配置阶段,不可编程,用于将那些不在摄像机视野内的顶点裁减掉,并剔除某些三角图元的面片。在此阶段可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面。

        屏幕映射(Screen Mapping):不可配置和编程,此阶段负责把每个图元坐标从三维坐标系转换到屏幕坐标系中。屏幕坐标系和z坐标一期构成窗口坐标系(Window Coordinates);屏幕映射得到的屏幕坐标决定了这个点点对应屏幕上哪个像素以及距离这个像素多远。OpenGL把屏幕的左下角当做原点,DirectX把屏幕左上角当做原点。

      光栅化阶段流水线(最主要目标—计算每个图元覆盖哪些像素,并为这些像素计算它们的颜色)

        三角形设置(Triangle Setup):固定函数阶段,不可编程和配置。此阶段会计算光栅化一个三角网格所需的信息,即计算三角网格每条边(边界)上的像素坐标。

        三角形遍历(Triangle Traversal,也称为 扫描变换,Scan Conversion):固定函数阶段,不可编程和配置。此阶段将会检查每个像素是否被一个三角网格所覆盖,若被覆盖则会生成一个片元(Fragment,并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色,这些状态包括但不限于它的屏幕坐标、深度信息、顶点信息等)。然后使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值,得到各像素的位置(尤其是可以得到深度)。

        片元着色器(Fragment Shader):完全可编程,用于实现逐片元的着色操作。在DirectX中,片元着色器被称为像素着色器(Pixel Shader),虽然此时的片元并不是真正意义上的像素。片元着色器的输入时三角形遍历对顶点信息插值得到的结果,输出的是一个或多个颜色值。此阶段可以完成很多重要的渲染技术,其中最重要之一是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后就可以得到其覆盖的片元的纹理坐标了。片元着色器的局限性在于它仅能影响单个片元,除此之外它仅能访问到导数信息(Gradient或者说是Derivative)

        逐片元操作(Per-Fragment Operations):不可编程,但是具有很高的可配置性(即我们可以设置每一步的操作细节),负责执行很多重要操作如修改颜色、深度缓冲、进行混合等。在DirectX中此阶段被称为输出合并阶段(Output-Merger)。此阶段的主要任务是

            1.决定每个片元的可见性。此任务涉及很多测试工作,如深度测试、模板测试等。

            2.如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并(混合)。

unity 积水_渲染_02

   模版测试(Stencil Test),具有很高的可配置性,通常用于限制渲染的区域,还可用于渲染阴影、轮廓渲染等,步骤如下:

        1.若开启模板测试则继续进行。

        2.GPU使用读取掩码读取模版缓冲区中该片元位置的模板值,然后将该值与使用读取掩码读取到的参考值(Reference Value)进行比较,此比较函数可以由开发者指定。

        3.若该片元未通过模板测试,该片元就会被舍弃。

        4.不管测试是否通过,都可以根据测试结果和深度测试结果来修改模板缓冲区,此修改操作也可由开发者指定。

    深度测试(Depth Test),具有很高的可配置性,步骤如下:

        1.若开启深度测试则继续进行。

        2.GPU读取该片元的深度值与已经存在于深度缓冲区中的深度值进行比较。此比较函数也可由开发者设置,通常这个比较函数是小于等于的关系,即若这个片元的深度值大于当前深度缓冲区中的值,那么就会舍弃它,用以实现只显示离摄像机最近的物体的功能。

        3.若该片元未通过深度测试,该片元就会被舍弃。

        4.若未通过测试则改片元就没有权利更改深度缓冲区中的值,否则开发者可以指定是否要用这个片元的深度值覆盖掉原有的深度值,修改深度值需要通过开启/关闭深度写入来进行。

    合并:当片元通过所有测试而没有被舍弃后进入合并阶段,在此阶段可以解决本次渲染是覆盖之前颜色缓冲中的结果还是对之前结果进行其他处理;对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值;对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。

    混合(Blend):具有很高的可配置性,开发者可以选择开启/关闭混合功能。若未开启混合功能,就会直接使用片元的颜色覆盖掉颜色缓冲区中的颜色;若开启了混合功能,GPU会取出颜色缓冲区的颜色和片元的颜色,将两种颜色进行混合。之后会使用一个混合函数来进行混合操作。这个混合函数通常和透明通道息息相关,例如根据透明通道的值进行相加、相减、相乘等。

    Early-Z技术:将深度测试在片元着色器之前执行的技术,可以提高GPU的性能,Unity中即采用此技术。使用此技术可能会与片元着色器中的一些操作冲突,例如在片元着色器进行透明度测试,但是此片元没有通过,我们会在着色器中调用API将其手动舍弃,折旧导致GPU无法提前执行各种测试。因此现代的GPU会判断片元着色器中的操作是否会与提前测试发生冲突,若存在冲突就会禁用提前测试,这将导致性能上的下降,因为有更多片元需要被处理了,这也是透明度测试会导致性能下降的原因。

    我们的屏幕最终显示的就是颜色缓冲区中的颜色值,但是为了避免我们看到那些正在进行光栅化的图元,GPU会使用双重缓冲(Double Buffering)的策略。即对场景的渲染是在幕后发生的,即后置缓冲(Back Buffer)中,一旦场景已经被渲染到后置缓冲中,GPU就会交换后置缓冲和前置缓冲(Front Buffer)中的内容,二前置缓冲去是之前显示在屏幕上的图像。由此保证了我们看到的图像是连续的。