文章目录

  • 1:什么是流水线:
  • 2:什么是渲染流水线:
  • 应用阶段:应用阶段有三个主要任务 (在CPU上运行)
  • 几何阶段(在GPU上执行)
  • 光栅化阶段(在GPU上执行)
  • CPU和GPU之间的通讯
  • 1. 将所需数据加载到显存中
  • 2. 设置渲染状态
  • 3. 调用Draw Call(图像编程接口)
  • GPU流水线(千万要把渲染流水线和GPU流水线分开呀)
  • 几何阶段
  • 顶点着色器(完全可以编程)
  • 曲面细分着色器
  • 几何着色器
  • 剪裁
  • 屏幕映射
  • 光栅化阶段
  • 三角形设置
  • 三角形遍历
  • 片元着色器
  • 逐片元操作
  • 问题
  • CPU和GPU如何并行工作的
  • 为什么DrawCall多了会影响效率
  • 如何减少DrawCall


1:什么是流水线:

现实中的流水线:
例如载一个工厂中,生产一个洋娃娃需要以下几步

  1. 制作洋娃娃的躯干 一个小时
  2. 缝上眼睛和嘴巴 一个小时
  3. 添加头发 一个小时
  4. 进行最后的包装 两个小时
    那么 如果不适用流水线的话,那么只有在前一个步骤完成之后,下一个步骤才能开始去制作,因此需要的时间为1+1+1+2 = 5个小时,所以制作一个娃娃,需要五个小时
    那么现在如果采用流水线来制作,场景如下

    可以看出来,使用流水线之后,制作一个 娃娃的时间取决于步骤中消耗时间最长的流程,也就是两个小时,即步骤四是这个流水线的瓶颈
    因此可以得出以下结论:1.流水线所消耗的时间取决于最慢的步骤 2.流水线可以缩短时间,提高效率

2:什么是渲染流水线:

由于有关流水线的概念,在计算机中的图形渲染中也同样适用,所谓的渲染流水线就是,从一系列顶点数据、纹理信息出发,经过一系列对这些数据的整理、计算、整合,最终形成一张人眼可以看到的图像;

应用阶段:应用阶段有三个主要任务 (在CPU上运行)

首先,我们要准备好场景数据,例如场景中摄像机的位置、视锥体、场景中包含的模型、使用了那些光源等
其次,为了提高渲染性能,我们需要做一个粗粒度剔除,也就是在场景中看不见的物体剔除出去,这样就不需要将多余的数据交付给几何阶段进行处理从而降低了渲染效率
最后,我们需要设置好每个模型的渲染状态,这些渲染状态包括但是不限于他适用的材质(漫反射颜色、高光反射颜色)、适用的纹理、使用的shader
通过应用阶段最重要的就是输出渲染所需要的几何信息,即渲染图元(这些渲染图元会传递给下一阶段)

几何阶段(在GPU上执行)

几何阶段负责和每隔渲染图元进行打交道,进行逐顶点、逐多边形的操作
几何阶段最重要的就是把顶点坐标变换到屏幕空间,然后交给光栅器进行处理
通过对输入的渲染图元进行多步处理之后,这一阶段将会输出平步空间的二位顶点坐标、每隔顶点对应的深度值、着色等相关信息,并将这些信息传递给下一阶段

光栅化阶段(在GPU上执行)

这一阶段会使用上个阶段传递的数据来产生屏幕上的像素,渲染出最终的图像

CPU和GPU之间的通讯

上面已经讲了渲染流水线(不要和GPU的渲染流水线弄混),可以知道,渲染流水线的起点是CPU,即应用阶段大致可以分为下面三个阶段

1. 将所需数据加载到显存中

由于所有渲染所需要的数据都要从硬盘加载到内存,然后网格和纹理数据又被加载到显卡的存储空间——显存中;至于为什么要这样的不断加载,直接通过显存访问内存(RAM)不好吗?实际上,显卡对于显存的访问速度很快,并且,大多数显卡并不具有对内存的访问权限
在真正的渲染中,需要加载到显存的数据并不是只有上述的那两项,还有很多,如位置信息、法线信息、顶点颜色、纹理坐标等

2. 设置渲染状态

所谓的设置渲染状态:最通俗的接视就是,这些状态定义了场景中的网格是怎样被渲染的,如果我们没有去主动的改变渲染状态,那么所有的网格都将使用同一种渲染状态,如果不去修改渲染状态,那么不同渲染状态下渲染的不同物体,最终给人的感觉是使用了同样的材质

如下:

unity shadergraph 逐渐出现_游戏引擎


看起来就像是使用了相同的材质是嘛?

3. 调用Draw Call(图像编程接口)

实际上Draw Call 就是一个命令,它的发起方是CPU,接收方是GPU,实际上Draw Call命令指向的是一个需要被渲染的图元列表,当给定DrawCall的时候,GPu就会根据渲染状态和所有输入的顶点数据来进行计算,最终输出成屏幕上的像素

GPU流水线(千万要把渲染流水线和GPU流水线分开呀)

几何阶段

上面的标题中已经示意出了,几何阶段和光栅化阶段是GPU执行的,所以,GPU的流水线和这两个阶段是密不可分,请看下图

unity shadergraph 逐渐出现_unity_02


由于在几何阶段输出的是渲染图元的几何信息,这里可以简单地认为是顶点的数据(顶点的位置、顶点的数量等)

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

顶点着色器是GPU渲染流水线的第一个阶段,它的输入来自CPU的渲染图元数据,顶点着色器处理的单位是顶点,也就是说,对于每个输入进来的顶点都会调用一次顶点着色器,需要注意的是,顶点着色器没有创建或者销毁顶点的能力,并且无法得到顶点之间的关系,只需要去处理每一个顶点即可,由于这样的独立性,所以这一阶段的处理是很快的(并行化处理);

unity shadergraph 逐渐出现_unity_03


由图可以看出,顶点着色器所执行的任务就是将输入的顶点进行坐标的转换(屏幕坐标转换到齐次剪裁坐标系),并且计算顶点的颜色

unity shadergraph 逐渐出现_游戏引擎_04


顶点坐标转换到齐次剪裁坐标系下,再由硬件做透视除法,最终得到归一化的坐标

由于顶点着色器由很多输出方式,因此它可以将输出的数据输入到曲面细分着色器或者几何着色器

曲面细分着色器

可选的着色器:用于细分图元

几何着色器

可选的着色器:用于逐图元的着色或者用于产生更多的图元

剪裁

剪裁有三种操作

  1. 当图元在视野内 保存,不剪裁
  2. 当图元在视野外 舍弃,全部剪裁
  3. 当图元的一部分在视野内 在视野外的全部剪裁,并且形成新的顶点(新的顶点一定在单位立方体与图元的交点)

屏幕映射

屏幕映射的作用就是,将图元的坐标映射成屏幕坐标,另外,在屏幕映射中并不对z坐标进行处理,而是将映射之后的x、y、z坐标一起传递到光栅化阶段(图元的数据)

光栅化阶段

三角形设置

由于上一个阶段的输出是图元的数据(最基本的图元是三角形),具体是三角网格的顶点,即我们得到的是三角形每条边的两个顶点,但是要得到三角形网格对于像素的覆盖情况,我们必须要计算出每条表上的像素坐标,为了能够计算边界像素的坐标信息,我们就需要得到三角行边界的表示形式,因此三角形设置就是这样一个计算三角形网格表示数据的过程;

三角形遍历

三角形遍历阶段会根据上一阶段的计算结果来判断一个三角网格覆盖了那些元素,并且使用三角网格的三个顶点的定点信息,对整个覆盖区域的像素进行插值

unity shadergraph 逐渐出现_数据_05


在三角形遍历的过程:根据几何阶段输出的顶点信息,最终的到该三角网格覆盖的像素位置。对应像素会生成一个片元,而片元中的状态是对三个顶点的信息进行插值得到的,例如对图中的三个顶点的深度进行插值预算得到其重心位置对应的片元的深度值为-10.0

片元:用来保存一系列表述一个三角网格是怎样覆盖每个像素信息

这一步输出的就是得到一个片元序列,需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的几何,这些状态用来计算每个像素最终的颜色,这些状态包括但不限于(屏幕坐标、深度信息、以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等)

片元着色器

用来实现逐片元的着色操作,

逐片元操作

在这一阶段有几个重要任务

  1. 决定每个偏远的可见性
  2. 如果一个片元通过了所有测试,就需要把这个片元的颜色值和已经存储的颜色缓冲区中的颜色进行合并,或者说是混合

测试的过程实际上是一个比较复杂的过程,这里只给出两种测试深度测试和模板测试,

unity shadergraph 逐渐出现_3d_06


上面两种都是测试的简化流程图

先来看一下,模板测试:如果开启了模板测试,GPU首先会读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将这个值和读取到的参考值(由开发人员进行设定)进行比较,这个比较函数可以是由开发者进行设定,比如小于参考值或者大于参考值进行舍弃,如果当前的片元没有通过测试,则被抛弃,反之进行下一个测试;

通过模板测试的片元进行下一阶段的测试深度测试,进行深度测试的流程和模板测试类似,只不过是在深度缓冲区读取该片元位置的模板值,与参考值进行比较,比较函数同样由开发人员设定;

如果某个片元通过了所有的测试,那么就会进行下一步——合并,为什么要进行合并?

在渲染过程中,是一个物体接着一个物体进行渲染的,后渲染的物体会挡在先渲染的物体前面,而每个颜色信息都被存储在一个名为颜色缓冲区中,在进行下一次的渲染之前,颜色缓冲区中往往已经存在了上次渲染的颜色信息,那么在下一次渲染的时候,是选择覆盖之前的渲染颜色,还是将两次渲染的颜色进行混合呢?

由两种情况:

  1. 如果后绘制的物体是不透明的,那么直接覆盖颜色缓冲区中原本的颜色即可;
  2. 如果后绘制的物体是透明的,那么就需要进行混合

注:
上面给出的测试顺序并不是唯一的,从逻辑上来将,这些测试是在着色器之后进行的,但是对于GPU来说,他们会尽可能的在执行片元着色器之前进行这些测试,这样做就可以提高性能,可以避免着色器绘制完某些片元,但是片元没有通过测试,浪费性能;
但是并不是提前就可以提高性能,如在片元着色器中会进行透明度的测试,如果没有通过片元着色器的透明度测试的话,会直接抛弃,这就导致GPU无法提前进行测试,因此现代的GPu会先判断以下片元着色器中的操作是否和提前测试发生冲突,如果由冲突,就会禁止提前测试;

最后一步:
当模型的图元通过了上层的计算和测试之后,就会显示到我们的屏幕上,我们的屏幕显示的就是颜色缓冲区中的颜色质,但是为了避免我们看到那些正在进行光栅化的图元,GPU会使用双重缓冲的策略,这意味着,对场景的渲染是幕后发生的,即在后置缓冲中,一旦场景已经被渲染到了后置缓冲中,GPU就会交换后置缓冲区和前置缓冲区中的内容,而前置缓冲区是之前显示在屏幕上的图像,因此 ,我们看到的图像总是连续的

问题

CPU和GPU如何并行工作的

使用一个命令缓冲区,命令缓冲区中包含了一个命令队列,由CPu向其添加命令,GPU则在其中读取命令,命令缓冲区使得两者可以相互独立的工作

为什么DrawCall多了会影响效率

由于每次调用图像编程接口的时候,CPu都会想GPU发送很多内容,包括数据、状态、命令等,在这一阶段CPU就需要完成很多工作,当CPu完成了这些准备工作之后,GPU才可以开始本次的渲染,GPU的渲染能力是很强的,因此,渲染速度往往快于CPU命令提交的速度,如果DrawCall的数量太多,那么CPU就会把大量的时间花费到DrawCall上,造成CPU的过载;

如何减少DrawCall

使用批处理,在unity中的Static Batching、Dynamic Batching就是通过这种方法

  1. Static Batching 是将标明为 Static 的静态物件,如果在使用相同材质球的条件下,Unity 会自动帮你把这两个物件合并成一个 Batch,送往 GPU 来处理。这功能对效能上非常的有帮助,所以是需要付费才有的。
  2. Dynamic Batching 是在物件小于300面的条件下(不论物件是否为静态或动态),在使用相同材质球下,Unity就会自动帮你合合并成一个 Batch 送往 GPU 来处理。
    批处理更适合静态的物体,例如不会移动的大地、石头等;

个人整理:

unity shadergraph 逐渐出现_着色器_07


unity shadergraph 逐渐出现_着色器_08