整理自《Unity Shader入门精要》,因为是OneNote里面复制过来的,排版有点问题
渲染流水线:
概念上分为3个阶段:应用程序阶段、几何阶段、光栅化阶段。
-
应用程序阶段:应用开发者控制,通常在CPU上实现
调用过程:
主要任务:
- 准备好场景数据:如场景物件、光源和相机等。
- 设置渲染状态:材质系数、使用的纹理等。
- 调用DrawCall:当渲染相关的一切数据和状态都准备好之后,将这些信息输出到下一阶段开始进行绘制。
- 几何阶段:在GPU中进行,负责对渲染图元进行逐顶点、图元处理。
这一过程的主要任务是将顶点坐标变换到裁剪空间中,然后交给下一个阶段进行光栅化处理。
主要任务:
这个阶段对几何顶点数据处理后会输出其屏幕空间坐标、对应的深度值,还可能有其他额外信息如UV纹理坐标、法线方向、观察方向等。
- 光栅化阶段:在GPU中进行。
输入为几何阶段输出的逐顶点数据,根据这些数据输出并渲染最终图像。
主要任务:
决定渲染图元覆盖了屏幕上的哪些区域并需要绘制到屏幕上,然后根据上一阶段输出的逐顶点数据对需要绘制的区域进行逐顶点处理。
1.1 应用程序阶段
CPU与GPU之间的通信:
渲染流水线的起点是CPU,即应用程序阶段,这一阶段可分为以下3个阶段:
- 提交数据到显存
- 设置渲染状态
- 调用DrawCall
加载数据到显存:
需要渲染的数据需要先从硬盘加载到系统内存,然后网格和顶点数据加载到显存中。
这是因为显卡对显存的访问更快,并且往往显卡没有访问系统内存的权力。
数据加载到显存后即可从系统内存中移除,后续需要CPU访问的数据(如网格用于碰撞检测)可能会继续保留,因为从硬盘加载这些数据非常耗时。
设置渲染状态:
渲染状态用于指示GPU如何渲染一个物体:如是否开启半透明、是否开启光照、使用何种材质或纹理等等。
当这些都准备好后,CPU可以发送命令通知CPU使用指定状态渲染给定数据,而这个命令就是DrawCall。
调用DrawCall
Draw实际上是一个命令,它的发起方是CPU,接收方是GPU,CPU通过此命令通知GPU进行渲染并输出图像到屏幕。
GPU流水线
应用程序阶段后的2个阶段:几何阶段和光栅化阶段 都是GPU中进行。所以他们共同构成GPU流水线。
开发者无法完全控制这两个阶段的细节。但是GPU向开发者开放了许多控制权、
这两个阶段都可以分成若干更小的流水线阶段,GPU为每个流水线阶段提供了不同的可配置性和可编程性。
换句话说:GPU流水线中几何阶段和光栅化阶段包含许多子流水线阶段,这些流水线的实现有些完全由GPU固定,称为不可控;有些开发参数供开发者修改,称为可配置;有些完全通过开发者通过Shader实现,称为可编程。
1.2 几何阶段
具体包括以下几个子阶段:
- 顶点着色器(Vertex Shader):可编程,必要
顶点着色器的主要工作是进行坐标变换和顶点光照。坐标变换即对顶点的坐标(即位置)进行某种变换,使其转换到在指定空间下的位置,在过程中可以按照某种规律实现顶点动画。
不管顶点着色器会这样改变顶点的坐标,它必须完成的一个任务是把顶点的坐标从模型空间转换到其次裁剪空间f。
- 曲面细分着色器(Tessellation Shader):可编程,可选
用于细分图元
- 几何着色器(Geometry Shader):可编程,可选
用于执行逐图元的着色操作或者用于产生更多的图元。
- 裁剪(Clipping):可配置
这一阶段主要是把不在视野范围的图元给剔除掉,对部分可见的图元进行裁剪,同时会根据图片朝向或者背离相机来决定是否需要剔除(一般会剔除背面)。
这一过程是不可编程的,但是我们可以通过定义裁剪面或者设置正反面剔除模式来进行配置。
- 屏幕映射(Screen Mapping):不可控
这一步的输入还是三维空间下的坐标,它的任务是把图元的x和y转换到屏幕坐标系下,不会对输入的z坐标做任何处理。
因为输入时NDC下的单位化的坐标,所以这一步实际上是一个缩放过程,输出结果是在屏幕上的坐标x,y和z值:x,y表示这个顶点对应屏幕上的哪个像素,z表示距离这个像素有多远。
1.3 光栅化阶段
具体包括以下几个子阶段:
- 三角形设置(Triangle Setup):不可控
这是光栅化的第一个阶段,光栅化阶段的主要目标是:计算每个图元覆盖了哪些像素,并计算这些像素的颜色。
三角形设置阶段的输入是上一阶段输出的顶点屏幕坐标、深度值(z坐标)和其他相关的额外信息如法线方向、视角方向等。
具体来讲,这一阶段的输入是三角网格的三个顶点,这些顶点的信息是由上一阶段输出的。要得到整个三角形对屏幕上像素的覆盖情况,就需要计算每条边和边界像素的信息,也就是说这一阶段我们需要以一种方式来表示这个三角形。暴力点理解就是:需要构造一个三角形类的实例。
- 三角形遍历(Triangle Traversal):不可控
这一阶段的主要任务是扫描每个像素,根据上一阶段得到的三角形边界信息,决定其是否被当前图元(这里就是三角形)所覆盖。
对覆盖的每个像素生成一个片元,这些片元的顶点信息(屏幕坐标、深度z,法线方向等所有信息)将通过对三角网格的三个顶点进行插值得到。
这一阶段的输出是片元序列。注意,片元对应像素,但片元不是像素,它包含了很多顶点信息和状态用以计算对应像素的颜色。
- 片元着色器(Fragment Shader):可编程,必要
在Direct3D中片元着色器被称为像素着色器。在Unity Shader中必须自定义片元着色器。
这一阶段的输入是上一阶段的片元序列。输出是对应这些片元的一个或者多个颜色值。
再次暴力地理解:这一阶段会遍历上一阶段的片元序列并对每个片元执行片元着色器。
在这个阶段最重要的技术就是进行纹理采样。片元着色器仅影响单个片元。
- 逐片元操作(Per-Fragment Operations):可配置
这一阶段在Direct3D中称为合并/混合阶段。已经很明确了这一阶段的的操作单位是片元,目的是将上一阶段输出的片元颜色与颜色缓冲区中的颜色进行混合。
这一阶段的处理过程如下:
- 检测片元的可见性:检测包括对片元进行模板测试和深度测试
- 将通过检测的片元与颜色缓冲区中的颜色按照指定的方式进行混合;将未通过检测的片元舍弃掉,这也意味着之前的所有计算和检测都白费了。