渲染流水线

一、CPU和GPU之间的通信

    渲染流水线的起点是CPU。可大致分为三阶段:

   (1)把数据加载到显存中。

        如标题所言,此阶段就是将所有渲染所需的数据从硬盘加载到系统内存中,然后将显卡用的上的数据(如网格,纹理等)加载到显存中。

   (2)设置渲染状态

        渲染状态定义了场景中的网格是怎样被渲染的。例如使用了哪个顶点/片元着色器、光源属性、材质等。如果没有更改渲染状态,那么会导致所有网格都使用同一种渲染状态。

   (3)调用DrawCall

二、GPU流水线

    GPU从CPU那得到渲染命令后,就会进行一系列流水线操作。

 

    GPU渲染流水线

 

    上图就是GPU的渲染流水线实现。其中实线表示该Shader必须由开发者编程实现,虚线表示该Shader是可选的。

   (1)顶点着色器

    顶点着色器是流水线的第一个阶段,它的输入来自CPU。输入进来的每个顶点都会调用一次顶点着色器顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点之间的关系。由于顶点相互独立的关系,GPU可以并行处理每个顶点。

    顶点着色器主要完成的工作有坐标变换和逐顶点光照。所谓坐标变换就是对顶点坐标进行某种变换。但不管怎样,顶点着色器必须完成的一个基本工作是,把顶点坐标从模型空间转换到齐次裁剪空间。接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(Normalized Device Coordinates,NDC)。需要注意的是,OpenGL中NDC在z分量上的范围在[-1,1]之间,DirectX中在[0,1]之间。

   (2)裁剪

    所谓裁剪就是对那些部分在视野内,部分在视野外的图元进行一个处理。裁剪是不可编程的,这是硬件上的固定操作,但我们可以自定义一个裁剪操作来对这一步进行配置。

   (3)屏幕映射

    这一步输入的坐标仍然是三维坐标系下的坐标(范围在单位立方体内,就是NDC下的坐标)。屏幕映射的任务是把每个图元的x和y坐标转换到屏幕坐标系下。屏幕坐标系是一个二维坐标系,与用于显示的画面的分辨率有很大关系。

    屏幕映射过程实际是一个x,y缩放过程,但不会对z坐标做任何处理。屏幕坐标系和z坐标构成窗口坐标系,这些值会一起被传递到光栅化阶段。

    屏幕映射得到的屏幕坐标决定了这个顶点对应屏幕上哪个像素以及距离这个像素多远。

    需要注意的是,OpenGL将屏幕左下角当做最小窗口坐标值,DirectX将屏幕左上角当做最小窗口坐标值。

   (4)三角形设置

    从上一个阶段输出的信息是屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z坐标)、法线方向、视角方向等。光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。

    三角形设置是光栅化的第一个流水线阶段。上一阶段输出的都是三角网格的顶点,即三角形网格每条边上的两个端点。但如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,就需要得到三角形边界的表示方式。这样一个计算三角网格表示数据的过程就叫三角形设置。它的输出是为了给下个阶段做准备。

   (5)三角形遍历

    三角形遍历阶段会检查每个像素是否被一个三角网格覆盖。如果被覆盖,就会生成一个片元。而片元中的状态是对3个顶点的信息进行插值得到的。这一步的输出就是得到一个片元序列。

    注意的是,片元并不是真正意义上的像素,而是包含了很多状态的集合。这些状态包括了它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,如法线、纹理坐标等。

   (6)片元着色器

    片元着色器的输出是一个或者多个颜色值。这个阶段可以完成很多重要的渲染技术,其中最重要的技术之一是纹理采样。为了在片元着色器中进行纹理采样,通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三个三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标。

    片元着色器的局限在于,它仅可以影响单个片元。

   (7)逐片元操作

    这个阶段的主要任务:

    1、决定每个片元的可见性。这涉及了很多测试工作,如深度测试、模板测试等。

    2、如果一个片元通过了所有测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说混合。

          

    模板测试。与之相关的是模板缓冲。如果开启了模板测试,GPU会首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考值进行比较,这个比较函数是由开发者指定的,例如小于时舍弃该片元,或大于时舍弃。不管一个片元有没有通过模板测试,我们都可以根据模板测试和深度测试结果来修改模板缓冲区,这个修改操作也是开发者指定的。例如,在失败时模板缓冲区保持不变,通过时将模板缓冲区中对应位置的值加1等。模板测试通常用于限制渲染的区域。另外,模板测试还有更高级的用法,如渲染阴影、轮廓渲染等。

    深度测试。这个测试也是高度可配置的。如果开启了深度测试,GPU会把该片元的深度值和已经存在于深度缓冲区中的深度值进行比较。这个比较函数也是开发者指定的。如果该片元没有通过测试,就会被舍弃。和模板测试不同的是,如果一个片元没有通过深度测试,它就没有权利更改深度缓冲区中的值。而如果通过了测试,开发者可以指定是否用该片元的深度值覆盖掉原有深度值,这是通过开启/关闭深度写入来做到的。

    合并。一个片元通过所有测试,就能进行合并了。我们知道,每个像素的颜色信息被存储在一个名为颜色缓冲的地方。每当执行这次渲染时,颜色缓冲区往往已经有了上次渲染之后的结果。对于不透明物体,开发者可以关闭混合(Blend)操作,这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。对于半透明物体,就需要混合操作来让这个物体看起来是透明的。

    但上面的测试顺序并不是唯一的。为了提高性能,GPU会提前进行深度测试,舍弃那些不用渲染的片元,减少片元着色器的计算量。这种技术成为Early-Z技术。不过提前测试可能会与片元着色器中的一些操作冲突,比如进行透明度测试。因此,现代GPU会判断片元着色器中的操作是否与提前测试发生冲突,如果有冲突,就会禁用提前测试。

 

    当模型的图元经过上面层层计算和测试后,就会显示到屏幕上。屏幕中显示的就是颜色缓冲区中的颜色值。但是,为了避免用户看到那些正在进行光栅化的图元,GPU会使用双重缓冲的策略。对场景的渲染是在后置缓冲中进行的。一旦场景已经被渲染到了后置缓冲中,GPU就会交换后置缓冲和前置缓冲中的内容,而前置缓冲区中是之前显示在屏幕上的图像,由此,保证用户看到的图像总是连续的。

posted @ 2019-06-11 13:16  Chia-li  阅读(949)  评论(0编辑  收藏  举报