注:资料整理自《Unity Shader入门精要》一书
一、渲染流程概念阶段:
应用阶段:(1)准备好场景数据:(如摄像机位置,物体以及光源等)
(2)粗粒度剔除(Culling):(把不可见物体剔除,不导入下一阶段)
(3)设置每个模型的渲染状态:(如材质、纹理、shader等),输出渲染图元(rendering primitives)(如点、线、三角面等几何信息)并传递至下一阶段
几何阶段:(1)在GPU上处理绘制几何所需要的相关操作(具体操作细分在随后的GPU流水线中介绍)
(2)重要操作:把顶点坐标变换到屏幕空间中,再交给光栅器处理
(3)随后输出屏幕空间的二维顶点坐标,每个顶点的深度值、着色信息等至下一阶段
光栅化阶段:(1)利用上一阶段的数据在屏幕上产生像素,并渲染出最终图像(由逐顶点数据 -> 到逐像素数据)
注:这只是概念化的渲染阶段,具体硬件上的流程请参考下面的GPU流水线
二、GPU流水线:
几何阶段
顶点着色器(Vertex Shader):完成的主要工作有:坐标变换和逐顶点光照
裁剪(Clipping):将不在摄像机范围内的物体剔除掉
屏幕映射(Screen Mapping):
输入:上一阶段的单位立方体内的三维坐标
输出:二维屏幕坐标系(Screen Coordinates),与分辨率有关
保留Z轴坐标(深度值),与屏幕坐标系构成窗口坐标系(Window Coordinates)
光栅化阶段
光栅化阶段有两个重要目标:
计算每个图元覆盖哪些像素
替这些像素计算颜色
百科:光栅化(Rasterization)是把顶点数据转换为片元的过程,具有将图转化为一个个栅格组成的图象的作用
三角形设置(Triangle Setup):
计算光栅化一个三角网格所需的信息(如坐标信息等)
三角形遍历(Triangle Traversal):
检查每个像素是否被三角形网格所覆盖,如果是,则生成一个片元(Fragment)
通过三角网格来判断覆盖了哪些像素,并用3个顶点的顶点信息对这些像素插值
注:片元并不相当于像素,相比于像素还多了很多信息(如坐标,深度值、法线、纹理坐标等)
片元着色器(Fragment Shader):
输入:上一阶段的插值数据
输出:颜色值(一个或多个)
逐片元操作(Per-Fragment Operations(OpenGL)):
(Output-Merger(DirectX))
主要任务:(1)决定每个片元的可见性
(2)如果一个片元通过所有测试,则把这个片元的颜色值和已储存在颜色缓冲区中的颜色合并
模范测试和深度测试是可配置的,通常将片元的模板值/深度值与开发者给定的值进行比较,然后决定是否舍弃该片元
混合也是可配置的,决定是覆盖上一次颜色缓冲区中值还是进行合并(如透明效果)
三、其余补充
关于Draw Call
首先DrawCall通俗讲就是CPU对GPU发出的一个命令(CPU调用图像编程的API(如OpenGL和DirectX)以命令GPU开始渲染)
大致上的渲染阶段:CPU阶段:CPU从硬盘中加载数据到显存中 -> 设置好渲染状态 -> DrawCall
GPU阶段:参考上文第二部分GPU流水线
众所周知,DrawCall会影响游戏的帧数,而帧数则是由CPU和GPU两者中较差的那个决定了瓶颈上限,因此当DrawCall多了以后,如上图2.19所示,CPU需要一条条发布命令,而GPU由于对图像的渲染能力很强,可以一次渲染多个三角网格,此时往往就会导致CPU处理速度跟不上无法及时给出命令,而GPU则会处于空闲状态。
那如何减少DrawCall?
(1)避免使用大量很小的网格,如果必须使用时可以考虑把他们合并成一张大网格,减少节点数
(2)避免使用过多的材质