【Unity Shader入门精要】— 渲染流水线

内容会持续更新,有错误的地方欢迎指正,谢谢!

引言

Shader只是整个渲染流程的一个子部分,想要学习它,我们就需要了解整个渲染流程是如何进行的。

何为渲染流水线

渲染流水线的工作任务:由一个三维场景出发,渲染出一张二维图像。
这个工作通常由CPU和GPU共同来完成。

《Real-Time Rendering》中将渲染流程分成三个阶段(这是一条概念流水线):

  1. 应用阶段:CPU的锅,开发者拥有绝对的控制权;输出渲染图元(即:渲染所需的几何信息(点、线、三角面等))。
  2. 几何阶段:GPU的锅,开发者有部分的控制权。逐顶点处理。把顶点坐标变换到屏幕空间中,再把这些顶点的数据交给光栅器进行处理。
  3. 光栅化阶段:GPU的锅,开发者有部分的控制权。逐像素处理。使用上一阶段传来的数据产生屏幕上的像素,并渲染出最终的图像。

应用阶段

分为下面三个步骤:

  1. 把渲染所需数据从硬盘加载到系统内存(RAM),再加载到显存中。显卡=显存+GPU。如此,GPU可快速访问这些数据。
  2. 设置渲染状态:定义如何渲染,也就是使用什么材质、纹理、着色器等。
  3. 调用Draw Call。Draw Call的发起方是CPU,接收方是GPU,它告诉GPU按照设置的渲染状态来渲染。就是一个命令而已嘛,一个Draw Call指向本次需要渲染的图元列表。(图元:组成图像的基本单元,比如三维模型中的点、线、面等)

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

GPU流水线:

【Unity Shader入门精要】— 渲染流水线

绿色:可编程控制;
黄色:可配置不可编程控制;
蓝色:由GPU固定实现,开发者无任何权限。
实线框(也就只有顶点着色器)表示必须由开发者编程实现,虚线框表示该Shader是可选的。

光栅化阶段有两个目标:

  1. 计算每个图元覆盖了哪些像素;
  2. 为这些像素计算它们的颜色。

三角形设置:计算光栅化一个三角网格所需的信息,它的输出是为下一步骤做准备。
三角形遍历:找到哪些像素被三角网格覆盖的过程。对应像素会生成一个片元,而片元中的状态是对三个顶点的信息进行插值得到的。如下图:
【Unity Shader入门精要】— 渲染流水线

片元着色器:输入是上一步对顶点信息插值得到的结果,输出是颜色值(着色)。如下图:
【Unity Shader入门精要】— 渲染流水线

补充:片元着色器这一步骤可以完成很多重要的渲染技术,比如 纹理采样:在顶点着色器阶段输出每个顶点对应的UV坐标(也叫纹理坐标),然后经过光栅化阶段对三角网格的3个顶点对应的UV坐标进行插值后,就可以得到覆盖的片元的UV坐标了。
可以这样理解贴图:不妨把纹理图片想象成一片弹性很好的橡皮薄膜,贴图就相当于用钉子把橡皮薄膜固定在与其纹理坐标相对应的顶点上。

逐片元操作:OpenGL中的说法,在DX中,叫输出合并阶段。
该阶段的任务:

  1. 决定每个片元的可见性。涉及了很多测试工作,例如模板(Stencil)测试(小于或者大于等于模板缓冲区中该片元的模板值就舍弃该片元,通常用于限制渲染区域。)、深度(Depth)测试(大于等于深度缓冲区中该片元的深度值就舍弃该片元)等;
  2. 如果一个片元通过了所有测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行混合,最后再写入颜色缓冲区中。也就是将片元的颜色值一个个地堆叠起来。

总结:只需要在一个Unity Shader中设置一些输入、编写顶点着色器和片元着色器、设置一些状态就可以达到大部分常见的屏幕效果。

CPU、OpenGL/DirectX、显卡驱动和GPU之间的关系图:
【Unity Shader入门精要】— 渲染流水线

OpenGL/DirectX是图像应用编程接口,架起了应用程序和GPU沟通的桥梁。应用程序向这些接口发送渲染指令,而这些接口会依次向显卡驱动发送渲染命令。显卡驱动就是显卡的操作系统,它知道如何和显卡中的GPU打交道,并将渲染命令翻译成GPU能理解的机器语言。

批处理可以有效减少Draw Call。很简单的一个道理:10000个1kb的小文件和1个10M的大文件同时传输,肯定是1个10M的文件传得快许多。

为避免我们看到正在光栅化的图元,GPU采取双重缓冲的机制,渲染发生在后置缓冲中,渲染结束后,GPU就会交换后置缓冲区和前置缓冲区中的内容。