Unity渲染流水线详解

渲染管线全流程

三个阶段:

应用阶段 ——> 几何阶段 ——> 光栅化阶段

详细介绍:
  • 应用阶段(开发者拥有绝对控制权)

    由CPU处理,为接下来GPU的渲染操作提供所需要的几何信息,即输出渲染图元(Rendering Primitives)

    1,准备数据,将数据加载到显存中

    **第一步:**剔除不需要的数据(如以包围盒为单位的视锥体剔除,遮挡剔除,层级剔除等)

    **第二步:**根据UI的深度值的顺序(DFS深度优先搜索)设置渲染的顺序。

    根据RenderQueue(渲染队列)进行排序:

    数值不相等时:数值越小越先被渲染

    数值相等时:不透明队列:RenderQueue<2500,按摄像机距离从前到后排序

    半透明队列:RenderQueue>2500,按摄像机距离从后到前排序

    **第三步:**将所有需要渲染的数据从硬盘(Hard Disk Drive:HDD)中加载到系统内存(Random Access Memort:RAM)中,再把GPU渲染需要的数据(纹理坐标,顶点的位置,颜色,法线方向等)打包发送给显存(Video Random Access Memory:VRAM)(*注意:*GPU一般没有对主存的访问权限,且与显存交互速度快)

    2,设置渲染状态(SetPassCall)
    告诉GPU该使用哪个顶点着色器(Vertex Shader)/片元着色器(Fragment Shader),纹理,材质等去渲染模型网格,这个过程也称SetPassCall。当使用不同的材质或者相同的材质下不同的Pass时需要设置切换多个渲染状态,就会增加SetPassCall,所以SetPassCall的次数也能反映性能。

    3,发送DrawCall

    当收到一个DrawCall时,GPU会按照指令,根据渲染状态和输入的顶点信息对指定的网格进行渲染。

    CPU通过调用图形API接口命令GPU对指定物体进行渲染一次的操作称为一次DrawCall。

  • 几何阶段(部分可配置和可编程)

    由GPU进行处理所有和几何相关的绘制,主要任务是把顶点坐标变换到屏幕空间,输出变换后的屏幕坐标及深度值,着色,法线等信息。

    1,顶点着色器(完全可编程)

    第一个阶段,可以通过编程进行控制。输入来自CPU发送的顶点信息,每个顶点都会调用一次顶点着色器。主要工作为:坐标转换和逐顶点光照(可选,计算输出顶点的颜色值)。其把顶点坐标从模型空间转换到齐次裁剪空间。(齐次裁剪空间不是屏幕空间,是xyz均放缩到-1到1的空间)。

    注意:此时GPU处理的顶点并不清楚顶点之间的关系,只是无差别的对待每个顶点

    2,裁剪

    将不需要的数据对象剔除出去的过程。由于场景一般很大,摄像机的视野范围可能不会覆盖所有的场景物体,裁剪就是为了将那些在摄像机视野范围外的物体剔除出去而被提出来的。

    一个图元和摄像机的关系有三种:完全在视野内、部分在视野内、完全在视野外。完全在视野内的就传递给下一个流水线阶段,完全在视野外的就不会向下传递,而部分在视野内的就需要进行一次处理,就是裁剪。

    注意:裁剪的范围不是摄像机的四锥体,而是转化为齐次空间的立方体重去裁剪

    BackFaceCulling(背面剔除):

    如果索引列表中的形成三角形的三个点如果是顺时针排序的表示在背面,如果是逆时针排序的表示在正面。

    3,屏幕映射

    通过计算将实际场景的对象映射到屏幕上,实质上就是对坐标的放缩,只映射对象的XY轴的坐标,Z轴坐标用于表示深度关系

  • 光栅化阶段**(部分可配置和可编程)**

    由GPU进行处理。这一阶段将会使用上个阶段传递的数据(屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z坐标)、法线方向、视角方向等。)来产生屏幕上的像素,并渲染出最终的图像。光栅化的主要任务是决定渲染图元中的哪些像素应该被绘制在屏幕上,然后对其颜色进行合并混合。

    1,三角形设置

    主要任务是为后续光栅化提供所需要计算的信息。例如,后续阶段需要判断像素点是否被三角形网格覆盖,只靠上个阶段得到的顶点信息无法确定边界的覆盖情况,还需要三角形网格边的信息,所以在这个阶段需要计算出边的表达式以供后续判断的使用。

    2,三角形遍历

    此阶段遍历所有的像素点,判断其是否被三角网格所覆盖 (用上面计算的结果) ,如果被覆盖,则在此像素点上生成一个片元。

    片元不是单纯的像素点,其还包含很多状态的集合,这些状态用来最终计算检测筛选每个像素点最终的颜色。(部分状态包括:屏幕坐标,深度值Z,以及从几何阶段继承来的法线,切线,纹理和自定义数据等等)

    3,片元着色器

    非常重要的可编程着色器阶段。片元着色器的输入是上一个阶段对顶点信息插值得到的结果,输出为每个片元的颜色值。这一阶段可以按需完成很多重要的渲染技术,最重要的技术之一就是纹理采样。

    纹理采样*:*

    为了在片元着色器中进行纹理采样,先在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角形网格的三个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标了。其局限在于仅可以影响单个片元。即执行片元着色器时,不能将结果直接发给旁边的邻居(有一个情况例外,片元着色器可以访问到导数信息(Gradient))。片段着色器输出颜色的具体过程如下

    4,逐片元操作(Per-Fragment Operations)(也称为输出合并阶段):高度可配置

    该阶段是对每一片片元进行操作,主要任务有:

    ①决定每个片元的可见性,如深度测试、模板测试,处理遮挡关系等

    ②如果一个片元通过了所有测试,就把这个片元的颜色值和已经存储在颜色缓冲区的颜色进行合并(或者说混合);没通过任何一个测试,片元都会被丢弃。

    *注意:*该阶段是高度可配置的,我们可以设置每一步的操作细节

    • Alpha测试

      对于Alpha低于某个值的片元直接舍弃

    • 模板测试(Stencil Test)

      模板测试通常用于限制渲染区域,也可以用于渲染阴影和轮廓渲染等;

      如果开启了模板测试,GPU会首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,与读取到的参考值进行比较(比较函数可以由开发者指定),不管这个片元有没有通过测试,我们都可以根据模板测试和深度测试结果来修改模板缓冲区(修改操作可以由开发者指定)

    • 深度测试(Depth Test)

      **ZTest(深度测试):**如果开启了深度测试,GPU会把该片元的深度值与深度缓冲区中的值进行比较(比较函数可由开发者指定)

      **ZWrite(深度写入):**深度写入可以由开发者指定开启或关闭;当深度写入关闭时,不会写入深度缓冲区,但是如果通过深度测试,则对应的颜色会写入颜色缓冲区;(注意:透明效果和深度测试以及深度写入关系密切)

    • 混合(Blending)(也称合并)(高度可配置的)

      如果一个片元通过了所有测试,就会达到混合阶段;

      为什么需要进行混合?

      由于渲染过程是一个物体接着一个物体绘制到屏幕上的,而每个像素的颜色被存储在颜色缓存区上,在我们执行某一次渲染时,颜色缓冲区中已经存储了上次渲染的颜色结果,那么我们是要使用这次渲染结果完全覆盖之前的结果还是进行其它处理,这就需要混合来解决问题!

      • **不透明物体:**可以关闭混合,让片元着色器得到的颜色值覆盖掉颜色缓冲区中的像素值;
      • **透明物体:**需要使用混合操作让这个物体看起来是透明的;

      混合操作也是可以高度配置的。开启了混合,GPU会取出源颜色和目标颜色将两者混合。

      源颜色是片元着色器得到的颜色,目标颜色是已经存在于颜色缓冲区中的颜色值。

    • Early-Z技术(将深度测试提前在片元着色器之前)

      上面的测试顺序并不是唯一的,假如按照上面的流程正常进行,当GPU在片元函数阶段花了很多性能计算出片元的颜色后,却没通过测试,这无疑浪费了很多GPU性能;但是对于大多数GPU来说,它们会尽可能执行片元着色器之前完成这些测试;

      *注意:*如果将这些测试提前,其检测结果可能与片元着色器中一些操作冲突,现代GPU会检测是否存在冲突,若存在则会禁用提前测试(这是透明度测试会导致性能下降的原因)

    双重缓冲(Double Buffering)

    屏幕上显示的就是颜色缓冲区中的值,但是为了避免看到那些正在进行光栅化的图元,GPU会采用双重缓冲的策略;对场景的渲染是在幕后进行的,即后置缓冲(Back Buffer);一旦场景被渲染到了后置缓冲区中,GPU就会交换后置缓冲区和前置缓冲区(Front Buffer),而前置缓冲区是之前显示在屏幕上的图像;由此可以保证屏幕上看到的图像是连续的;

    帧缓冲区(FrameBuffer)

    **颜色缓冲区(ColorBuffer)**
    
    • 深度缓冲区(DepthBuffer)

    • 模板缓冲区(StencilBuffer)

命令缓冲区

CPU与GPU之间是并行工作的,需要用到命令缓冲区(Command Buffer),命令缓冲区中有一个命令队列,由CPU添加命令,由GPU读取命令,添加命令和读取命令是相互独立的;命令缓冲区中命令有很多种类,DrawCall是其中一种,还有改变渲染状态等(例如改变使用的着色器,使用不同的纹理等);

为什么DrawCall多会影响帧率?

每次调用DrawCall之前,CPU需要向GPU发送很多内容,包括数据,状态和命令等;CPU需要完成很多工作,例如检查渲染状态等,GPU的绘制能力是很强的,对于200个还是2000个网格基本没区别,因此渲染能力一般会快于CPU提交命令的速度。如果DrawCall太多,CPU会把大量的时间花费在提交DrawCall上,照成CPU过载;

一些专业术语概念:

图元:由若干个顶点构成的几何形状,(点,线,三角形,多边形都可以是一个图元)

渲染状态:渲染状态包括着色器(Shader),纹理,材质,灯光等等。

DrawCall:CPU每次调用图像API接口命令GPU进行渲染的操作称为一次DrawCall

猜你喜欢

转载自blog.csdn.net/lel18570471704/article/details/134708949
今日推荐