本文大部分知识来源于冯姐的《shader入门精要》。

具体源代码可从这里获取——>shader入门精要源代码

渲染流水线的目的在于将一个三维场景,从某个视角生成(或者说渲染)出一张二维图像。换句话说,计算机需要从一系列的顶点数据、纹理等信息触发,把这些信息最终转换成一张人眼可以看到的图像,这个工作由CPU和GPU共同完成。

一般讲渲染流程分为三个阶段:

  • 应用阶段
  • 几何阶段
  • 光栅化阶段

渲染流水线总结(一)之美_渲染

下面分别总结下这三个阶段分别要做的工作是什么。

应用阶段


该阶段主要由开发者主导,主要是将场景中的信息(摄像机位置、视锥体、场景中的模型、光源、雾效等等)进行一个粗粒度剔除工作,目的在于把一不可见的物体剔除出去,减少几何阶段的工作量,最后输出一份渲染所需的几何信息(即渲染图元,里面包含了模型所使用的纹理,使用的shader、材质等),这些渲染图元将会被传递到下一个阶段-几何阶段中进行处理。


几何阶段


几何阶段决定我们需要绘制的图元是什么,怎样进行绘制,在哪里绘制。这一阶段通常在GPU上进行,它负责和每个渲染图元打交道,进行逐顶点操作,其中一个重要的任务就是把顶点坐标变换到屏幕空间中,再交给光栅器进行处理。通过对渲染图元进行多步处理后,这个阶段将会输出屏幕空间的二维顶点坐标、每个顶点对应的深度值、着色等相关信息,并传递给下一个阶段。(在unity的顶点着色器中你可以对顶点进行你想要的操作,更改显示的效果)


光栅化阶段


这一阶段将会使用几何阶段中传递过来的数据来产生屏幕上的像素,并渲染出最终的图像。这一阶段也是在GPU上运行的。光栅化的主要任务是决定每个渲染图元中的哪些像素应该被绘制在屏幕上(具体可以在片元着色器中进行自定义),它需要对上一个阶段得到的逐顶点数据(纹理坐标、顶点颜色)进行插值,然后在进行逐像素处理。


设置渲染状态


什么是渲染状态?一个通俗易懂的解释就是,定义场景中的物体以怎样的方式渲染出来。例如:使用哪个顶点着色器(Vertex Shader)/片元着色器(Fragment Shader)、光源属性、材质等。这一步骤也是用户自己实现shader过程中必不可缺的,你可以自己组合形成各种各样的渲染状态。下图显示了当使用同一种渲染状态时,渲染3个不同物体的结果。

渲染流水线总结(一)之美_unity_02


什么是DrawCall?


相信接触过性能优化的同学应该都听说过Draw Call,实际上,Draw Call就是一个命令,从CPU发起一个命令给GPU,这个命令仅仅会只想一个需要被渲染的图元列表,而不会再包含任何信息(这是因为我们在上一个阶段中完成了)。
当给定了一个Draw Call时,GPU就会根据渲染状态和所有输入的顶点数据进行计算,最终成像。
所以Draw Call本身的含义很简单,就是CPU调用图像编程接口,以命令GPU进行渲染的操作。

渲染流水线总结(一)之美_unity_03


如何减少DrawCall?


提早大量很小的DrawCall会造成CPU的性能瓶颈,即CPU吧时间都花费在准备DrawCall的工作上了,那么一个很显然的优化方法就是把很多小的DrawCall合并成一个大的DrawCall,这就是批处理的思想。下图显示了批处理所做的工作。

渲染流水线总结(一)之美_unity_04

在游戏开发中,为了减少DrawCall的开销,有两点需要注意

  • 避免使用大量很小的ui、Sprite,当不可避免的需要使用很小的图片时,应该将它们打包成一个图集。
  • 避免使用过多的材质,尽量在相同的物体上共用同一个材质。