Shader和渲染管线
什么是Shader
Shader,中文翻译即着色器,是一种较为短小的程序片段,用于告诉图形硬件如何计算和输出图像,过去由汇编语言来编写,现在也可以使用高级语言来编写。一句话概括:Shader是可编程图形管线的算法片段。
它主要分为两类:Vertex Shader和Fragment Shader。
什么是渲染管线
渲染管线也称为渲染流水线,是显示芯片内部处理图形信号相互独立的并行处理单元。一个流水线是一序列可以并行和按照固定顺序进行的阶段。就像一个在同一时间内,不同阶段不同的汽车一起制造的装配线,传统的图形硬件流水线以流水的方式处理大量的顶点、几何图元和片段。
注意理解的是这里有一个前后关系,前一个阶段的输入会到后一个阶段去输出。比如在顶点程序当中,顶点程序计算的数据就会作为片段程序再进一步加工的材料。
为了更形象地理解渲染管线,来看一下下面的插图。
最上面的是【3D应用或者游戏】,【3D应用或者游戏】会直接调用【3D应用接口】,也就是OpenGL或者DirectX等。OpenGL和DirectX是方便应用程序去进行硬件访问和调用的中间层。如果没有它们,我们需要为硬件去编写一个非常复杂的专门针对硬件的驱动程序。
再接下来就是【CPU和GPU的分界线】,在此之上都是CPU的操作,以下进行GPU运算。
GPU的运算是从左到右依次进行,最开始【GPU前端模块】到【图元装配】这中间如果在过去T&L流水线的过程当中是由硬件设计好的运算过程,也就是集成的,它不能够进行编程控制。当图形硬件具有了可编程能力之后,我们就可以在这个阶段使用【顶点着色器】进行编写运算逻辑,用于取代过去直接集成在硬件当中的运算。在这个阶段完成之后,会到【光栅化以及插值】阶段。
光栅化就是把计算机显卡当中运算的数据进行一个细分,用于去适配屏幕上具体的每一个像素的显示,但是光栅化并不等同于像素显示,像素显示最终反映的是颜色,而光栅化过后得到的结果是【帧缓存】,在这个过程当中,我们可以插入【片段着色器】,这个部分就可以使用可编程化运算。所以【片段着色器】的目标是为屏幕上最终要显示的每一个像素去计算它最后需要什么样的颜色。
对于Unity来说,以上过程可以通过下图来解释。
最上面【Geometry】是几何模型,几何模型进入【Unity】,可以理解为把几何模型Mesh、网格等数据交给Unity,Unity导入后就通过Unity引擎去调用【Grphics APU】图形API,调用图形API的过程就是在驱动GPU进行处理运算。
进入GPU运算首先进行的是【Vertex Processor】顶点处理器,这个部分就需要我们使用【Vertex Shader】顶点着色器,顶点着色器运算的结果会交给【Pixel Processor】像素处理器,也就是片段处理器,在这个部分我需要为像素处理编写【Pixel Shader】像素着色器程序,这部分计算完后就输出了最终我们可以用于在屏幕上的颜色信息,我们把它叫做【Frame Buffer】帧缓冲。帧缓冲存储的是计算机依次显示所要的数据,但也不仅仅是这些数据,它还有其他的附加信息,比如深度值等。
下面是Unity官方手册中的一张渲染管线图示。
渲染管道线中最左边的这个部分中Transform指的是模型的空间变换,主要针对的是顶点的空间几何变换;TexGen即Texture Generator,表示的是纹理坐标的生成,主要用于在顶点当中去取得纹理坐标,再转换为UV取值的范围;Lighting指的是光照。因此这个部分就是过去就是T&L几何变换光照流水线,当图形硬件具有了可编程能力后,这个固定的模块就被【Vertex Shader】顶点着色器代替了。
在顶点着色器处理过后,Unity就进入【Culling & Depth Test】裁剪和深度测试过程。裁剪和深度测试描述的是如果一个物体在摄像机前展示,它向着摄像机的面会被观察到,它背对着摄像机的面不会被观察到,在这样的情况下,为了减少GPU处理数据量就进行了一个裁剪(Culling),把看不见的面直接剔除,不需要去处理的这些面所涉及的顶点数据,从而加速图形处理。第二个方面深度测试(Depth Test),指的是摄像机有一个特性,在计算机当中没有无限这个概念,计算机处理的数据都是离散化的,它有一个范围,当超过最近和最远这个范围的这部分会被剔除。
接下来就进入到纹理采样(Texturing)和雾化处理(Fog)阶段。在这阶段实际上就是在进行光栅化处理,描述的就是如何在屏幕上显示每一个像素的颜色。这里需要去纹理采样,一张贴图有很多数据,我们去采集纹理上某一点的颜色值,这个就叫做纹理采样。雾化就是根据最后计算的数据后需不需要进行一个雾化处理,近处的很清晰,远处的有种朦胧感,这个部分就是片段着色器可编程的能力范围。
之后还需要【Alpha Test】,指的是去绘制那些半透明的或全透明的物体。经过【Alpha Test】之后还需要进行【Blending】处理,这阶段会混合最终的图像。
以上介绍的是渲染管线的流程,要把握的主要的过程就是我们可编程能力是两个部分,一个就是几何变换和光照使用的顶点着色器部分,另一个就是关于如何去采样,去计算颜色以及雾化处理等使用的片段着色器部分。
这里需要注意的是Unity优化当中有一个主要部分就是减少Draw Call的调用,Draw Call就指的是应用程序去调用图形硬件GPU去进行渲染的调度过程。应用程序需要准备很多的数据,包括顶点数据、矩阵、向量等数据,都需要通过应用程序传递给GPU,这样个调度过程CPU必须要去收集数据以后才产生一个API的调用,这个过程的消耗是高昂的,如果反复地启动这个调用,那么就仅仅收集和传递参数这样的过程相当耗时耗力,就会造成了应用程序或游戏运行的瓶颈。因此要尽量减少Draw Call,尽量减少CPU对GPU这样的调用。
Shader和材质、贴图的关系
Shader(着色器)实际上就是一小段程序,它负责将输入的顶点数据以指定的方式和输入的贴图或者颜色等组合起来,然后输出。绘图单元可以依据这个输出来将图像绘制到屏幕上。输入的贴图或者颜色等,加上对应的Shader,以及对Shader的特定的参数设置,将这些内容(Shader及输入参数)打包存储在一起,得到的就是一个Material(材质),之后,我们便可以将材质赋予三维物体来进行渲染(输出)了。
材质好比引擎最终使用的商品,Shader好比是生产这种商品的加工方法,而贴图就是原材料。
总结
Shader是图形可编程方案的程序片段。主要分为顶点着色器和片段着色器。
渲染管线是一种计算机从数据到最终图形成像的形象描述。
材质可以理解为商品,Shader是加工这种商品的方法,而贴图是加工过程中需要的材料。