目录
Step1.应用程序阶段(The Application Stage)
Step2.几何阶段(The Geometry Stage)
Step3.光栅化阶段(The Rasterizer Stage)
前言
最近两个月学完了乐乐女神的shader精要和闫佬的101,也对学的东西有了一点点点点的初步理解,故此整理一下,也方便记忆,
很多地方理解不深,如有错处,恳请各位大佬不吝赐教。
一些疑问
一、什么是渲染管线?
渲染管线,也作渲染流水线。
初次听起来一副晦涩难懂的样子,实际上就是将3D信息的一些数据递给GPU,最后得出一张二维图像的过程。
简单来说,就是将你游戏里的场景渲染到你的屏幕上的过程。
在TRT一书中将渲染流程分为了:应用阶段(Application Stage )、几何阶段(Geometry Stage)、光栅化阶段(Rasterizer Stage)。
二、为啥这玩意是个流水线?
上面说过的三个阶段,其实是给渲染流程进行基本功能划分为提出来的,真正的流水线是实现在硬件GPU上。
提到GPU,就不得不提到CPU。
1.CPU与GPU
*黄色为控制器,绿色为算术逻辑单元,橙色用于存储信息。
*CPU的流水线比较长,计算趋于
线性
执行(可部分并行),基本上只能处理一件事,但是处理的手法可以是相当精细,像是名艺术家,今天雕塑明天画画,每件事都有自己独立的逻辑,很难并行。
*GPU的流水线较短,内部集成了大量ALU,又可以
并行
执行,所以可以进行大量可并执行的数学计算,像是饭店里的切菜工,管你是什么品质什么形状的土豆,先给你一刀再说。于是这种对一批数据进行一样的计算,就形成了并行结构的基础。
如图所示,GPU的计算模块要比CPU大,而控制模块要小很多,正是这种比较确定的计算流程导致的。
看完GPU和CPU结构区别之后也就初步简单了解为什么渲染可以是流水线,以及渲染选择专门的硬件而不是用CPU来算。
2.CPU和GPU数据交互
在程序开始运作的时候,所有的数据都存储在磁盘上。程序开始,为了CPU能够快速访问,需要先将数据载入内存(RAM)。所有的渲染工作都集中在GPU执行,所以需要计算的资源也都必须载入显存中(VRAM)。
当然,GPU在进行计算获取数据的时候,并不是直接从显存中获取数据,而是经过多级缓存后,经过对应的寄存器(Register),最后进入ALU中进行计算。
同理当GPU完成对数据的计算之后,会经过寄存器—L1级缓存—L2级缓存然后保存在显存中,最终显示器获取显存中的数据进行显示输出。
3.CPU和GPU并行工作
在Unity Shader精要(后面以精要代称)里面介绍CPU和GPU实现并行工作是采用命令缓冲区(Command Buffer)的方法。
命令缓冲区包含了一个命令队列,由CPU添加命令,GPU读取命令,添加和读取是相互独立的,命令缓冲区的存在使得CPU和GPU可以互相独立工作。
如果没有流水线化,那么CPU需要等到GPU上一个渲染任务完成才能再次发送渲染命令。
Step1.应用程序阶段(The Application Stage)
应用程序阶段一般是图形渲染管线概念上的第一个阶段,这一阶段完全由开发者主导,通常由CPU负责实现。
这个阶段比较好理解,比如做了一款游戏,其中的碰撞检测,动画,剔除,几何形变,加速算法等等,把这些该计算的都计算完了之后,在这个阶段的末端,将这些计算好的数据(法向量,纹理,顶点)传入下一个阶段。
其中剔除有:视锥剔除(Frustum Culling),遮挡剔除(Occlussion Culling),层级剔除(Layer Culling Mask)。
这个过程在精要中分为三步:
一、把数据加载到显存中。
这个过程就是将上面所述的数据加载到显存中。
*注意:
一般情况下,数据加载到显存上就可以将RAM的数据移除了,但实际情况中,CPU仍然需要访问一下数据(例如访问网格数据进行碰撞检测),这是我们就不希望RAM中的数据被移除,因为从硬盘加载到RAM的过程是很耗时的
二、设置渲染状态。
- 定义场景中的物体网格被怎样渲染,使用了哪个顶点着色器(VertexShader)/片元着色器(Fragment Shader)、光源、材质。
- 渲染的顺序(Queue,Distance,UICanvas)
- 渲染模式(Forward/Deferred)
三、调用Draw Call
- DrawCall的含义十分简单,就是CPU调用图像编程接口。
- DrawCall就是一种命令,由CPU发起,GPU接收。
- 调用DrawCall就通知GPU开始根据传输的数据和渲染状态进行渲染。
Step2.几何阶段(The Geometry Stage)
几何阶段主要负责大部分多边形操作和顶点操作,如:顶点坐标变换、顶点着色、裁剪、投影以及屏幕映射,这个阶段主要在GPU上进行运算。
一、顶点着色器
顶点着色器是完全可编程的阶段,专门处理传入顶点信息的着色器,通常,顶点着色器将顶点从模型空间(Model Space)变换齐次裁剪空间(Homogeneous Clip Space),并且一个顶点着色器至少且必须输出此变换位置。
顶点着色器主要工作为:坐标变换和逐顶点光照。
其中这个变换也就是我们常说的MVP矩阵变换。
*注意:
那么我们的问题也来了,为啥要这么麻烦去变换顶点坐标?
在精要书中解释的很好,直接贴书中内容了。
根据顶点坐标变换的先后顺序,主要有下图几个坐标空间:模型空间(Model Space)
有时也被称为(Object Space)
、世界空间(World Space)、观察空间(View Space)、裁剪空间(Clip Space)、屏幕空间(Screen Space)
以下过程均以缩写代替(例如Object to World = O2W)
O2W
:首先,模型空间中的坐标值是模型在建立之初就存在的,比如一个MAX文件,里面包含的数据就是Object Space的自身坐标,和World Space不同,没有和其他物体任何的参照关系。
World Space坐标的实际意义就是有了一个坐标原点,物体和原点进行比较之后知道自己的位置,在Unity中导入一个模型,它的Transform就是世界坐标。
而这一过程,就是将模型空间的坐标值转换为世界空间的坐标值。
W2V
:观察空间其实就是摄像机空间,以摄像机为原点,由视线方向、视角和远近平面组成。
那么从世界空间到观察空间这个变换过程被称为观察变换。
回到顶点着色器,如果需要的话,在顶点着色器中还可以计算和输出顶点的颜色。
二、曲面细分着色器(可选)
对三角面进行细分操作,增加面数,由外壳着色器(Hull Shader),镶嵌器(Tessellator)和域着色器(Domian Shader)构成(了解不是很深入,止步于认识这些名词)。
三、几何着色器(可选)
- 输入是一个单独的类型:点、线段、三角形。
- 几何着色器可以改变新传递进来的图元拓扑结构,且能接受任何拓扑类型的图元,但只能输出点、折线(Line Strip)和三角形条(Triangle Strips)。
- 几何着色器需要图元作为输入,在处理过程中他可以将这个图元整个丢弃或者输出一个或更多的图元。(也就是说它可以产生比它得到的更多或更少的顶点。)
- 当我们没有添加几何着色器的时候,默认直接输出三角形,添加之后可以修改输出我们想要的图形。
四、裁剪
1.视锥体裁剪
这一步就是剔除那些不在视锥体范围的物体。
这也正是View Space到Clip Space的过程中的一步。
V2C
:观察空间转换到裁剪空间的过程其实就是一个裁剪、投影的过程。因为在不规则的视锥体内裁剪比较困难,所以一般到这一步需要将视锥体转换成一个立方体,而这个立方体被称为规范立方体(CVV),如果是物体的一部分不在视锥体内,那么产生新的顶点去代替。
*注意:
经过透视投影和透视除法之后,这个立方体叫做CVV(Canonical View Volume),而这个立方体所处的坐标系叫做归一化的设备坐标(Normalized Device Coordinates,NDC)。
2.面剔除(Face culling)
接下来会将所有背面朝向的面剔除掉,只要面被挡住了,就会被剔除(可设置)。
当进行裁剪和面剔除之后,可以减少进入光栅化阶段的图元数量,这个过程我们也可以称作为图元装配(Primitives Assembly)。
五、屏幕映射
这一步也就是将前面产生的图片坐标信息,映射到屏幕坐标系中。
屏幕映射得到的坐标确定了顶点所在的像素位置。
那么之前的CVV是三维坐标系,而屏幕是二维坐标系,那么映射后的Z坐标到哪去了呢?
实际上,屏幕坐标系和Z坐标构成了一个叫做
窗口坐标系
(Window Coordinates),这些值会被一起传递给光栅化阶段。
需要注意的是,屏幕坐标系在OpenGL和DirectX之前存在差异,OpenGL原点在左下角,DX在左上角。
在Unity里面,和上面应用阶段准备的碰撞检测、视锥剪裁数据一样,裁剪空间到屏幕空间的转换是底层帮我们自动完成的。
到了这一步,我们几何阶段也就差不多完成了,输出屏幕空间下的顶点信息(法线、深度等)。
Step3.光栅化阶段(The Rasterizer Stage)
一、三角形设置
- 把前面接受到的顶点信息连线组成三角形,得到三角网格的信息(计算边的像素坐标)。
二、三角形遍历
- 检查每个像素是否被三角形网格覆盖,被哪一个三角形网格覆盖。如果被覆盖的话,就会产生一个片元(Fragment)。
- 至此,图元/片元装配过程结束。
- 这不过这一步得到的片元虽然是以像素点为单位,但它并不是真正最终的像素,而是一个很多信息的集合,包括了屏幕坐标,深度信息,顶点信息,纹理坐标等,这些信息用于后面计算每个像素的最终颜色。
三、片元着色器
- 片元着色器和像素着色器属于同一个东西,只是在Open GL和DirectX中的叫法不同。
- 片元着色器是可编程的。
- 片元着色器并不会影响像素的颜色,而是产生一系列数据信息,用来描述三角形网格是怎么覆盖每个像素的,每个片元负责储存这样一系列数据。
- 片元着色器的输入是上一个阶段对顶点信息插值得到的结果,而他的输出是一个或多个颜色值。
- 这一阶段可以应用很多渲染技术,比如纹理采样。
四、逐片元操作
- 逐片元操作(Per-Fragment Operations)和输出合并(The Merging Stage )是一个东西,还是OpenGL与DirectX的叫法区别。
-
逐片元操作是可
高度配置
但
不可编程
的
。
- 在这个阶段中,是真正会对像素产生影响。
- 这个阶段也就是进行模板缓冲 (Stencil-Buffer)和 Z 缓冲(Z-buffer)操作的地方,什么透明处理(Transparency)和颜色混合(Color Blending)也都是在这个阶段进行的。
首先,这个阶段的任务是决定片元的可见性。只不过这个过程比较复杂,不同图形接口也不一样,精要书中给了两个流程图。
之后对于通过的片元,将他们的颜色和缓冲区已有的颜色进行混合,输出到最终的屏幕上。
而这一阶段的流程为:透明度测试,模板测试,深度测试,混合。
最后,理解了渲染管线对学习shader有很大帮助,因为shader所在的阶段就是渲染流水线的一部分。
参考书籍:《Unity Shader 入门精要》