1 渲染流程
- 应用程序阶段(CPU):识别出潜在可视的网格实例,并把他们及其材质提交给 GPU 以供渲染。
- 几何阶段(GPU):进行顶点变换等计算,并将三角形转换到齐次空间并进行裁剪。
- 光栅化阶段(GPU):把三角形转换为片元,并对片元执行着色。片元经过多种测试(深度测试,Alpha 测试等)之后,最终与帧缓冲混合。
2 CPU 的工作流程
- 准备好需要被渲染的对象。也就是哪些物体需要被渲染,哪些物体需要被剔除(culled),剔除的常用方式包括视锥体剔除和遮挡剔除,并对需要渲染的对象进行排序。
- 设置每个对象的渲染状态。渲染状态包括所使用的着色器、光源、材质等。
- 发送 DrawCall。当给定一个 DrawCall 时,GPU 会根据渲染状态和输入的顶点数据进行计算。
3 GPU 的工作流程
顶点数据主要来自 CPU 端,然后将数据送到渲染管线中,输入的数据可以包括顶点坐标、顶点颜色、顶点法线、纹理坐标等数据。这些数据以图元的方式进行处理,常见的图元有点、线和三角面。
3.1 几何阶段
3.1.1 顶点着色器
是完全可以编程的,它的是流水线的第一个阶段,用于顶点坐标变换以及顶点颜色计算等,在这里改变顶点的位置可以模拟水面和布料等的运动。其中,顶点着色器将顶点坐标从模型空间转换到齐次裁剪空间,接着进行裁剪剔除不渲染的点后,最终得到归一化的设备坐标(NDC),Unity设备坐标范围同 OpenGL 一样在[-1,1]之间。 顶点着色器的输入来自于CPU,CPU输入的每个顶点都会执行一次顶点着色器,顶点着色器本身无法创建和销毁顶点,并且无法得到顶点与顶点之间的关系。正因为这样的独立关系,GPU可以利用自身的特性进行并行运算,所以顶点着色器的运算速度非常快。在此阶段也会进行透视投影、顶点光照、纹理计算、蒙皮。也可以通过修改顶点位置生成程序式动画(procedural animation),例如模拟风吹草动,碧波荡漾。
3.1.2 几何着色器
几何着色器因为在手机端不支持,所以Unity开发程序员也许并不熟悉。几何着色器也是完全可编程的。几何着色器处理以齐次裁剪空间表示的整个图元(三角形,线段,点)。它能够剔除和修改输入的图元,也能生成新的图元。典型应用包括阴影的体积拉伸(shadow volume extrusion)、渲染立方体贴图(cube map)的六个面、在网格的轮廓边拉伸毛发(fur fin)、从点数据生成粒子四边形、动态镶嵌、把线段以分形细分(fractal subdivision)模拟闪电效果、布料模拟等。
3.1.3 裁剪
由于我们的场景会很大,摄像机视野外的画面不需要进行渲染计算,所以裁剪的作用便是剔除视野外的图元。如下图一条线段A点在视野内,B点在视野外,将会剔除B点,取线段与视野交点处C点。注意裁剪这一步我们不可以编辑,这是硬件上的固定操作。
3.1.4 屏幕映射
将图元的x和y坐标转换到屏幕坐标系,这与屏幕的分辨率有关,而图元的z坐标表示各物体与摄像机的远近,用于判断各图元的遮挡关系,这一阶段输出屏幕坐标系下的顶点坐标,Z深度信息,法线方向等。虽然屏幕映射是玩家不可配置和编程的,但是屏幕分辨率确实玩家可以设置的,较小的屏幕分辨率对光栅化阶段是有非常重要的优化效果的。
3.2 光栅化阶段
该阶段是将屏幕坐标系坐标转换为图像像素的过程。
3.2.1 三角形设置
这个阶段会计算光栅化一个三角网格所需的信息。具体来说,上一个阶段输出的都是三角网格的顶点,即我们得到的是三角网格每条边的两个端点。但如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。这样一个计算三角网格表示数据的过程就叫做三角形设置。它的输出是为了给下一个阶段做准备。
3.2.2 三角形遍历
该阶段将会检查每个像素是否被三角网格覆盖,对应覆盖像素会生成一个片元,而片元的状态由三角网格三个顶点插值得到,注意片元不是像素。这些片元包含屏幕坐标、深度信息、法线、纹理坐标等。
3.2.3 片元着色器
此阶段的输入是一组片元属性,这些属性是在三角形遍历阶段通过对顶点属性插值所得。输出则是该片元的颜色信息。这一阶段可以完成很多重要的渲染技术,最重要的技术之一就是纹理采样。
3.2.4 逐片元操作
该阶段也称为合并阶段(merge stage)或混合阶段(blending stage)。此阶段不可编程,但是可以高度配置化。最常用的逐片元操作测试包括深度测试 ZTest、Alpha 测试、模板测试 Stencil test,当片元通过了所有测试以后,其颜色就会与帧缓冲原来的颜色进行混合(Blend),混合的方式是可配置的,如 Blend One One。为了避免看到正在栅格化的图元,GPU 使用双重缓冲,场景渲染是在幕后的后置缓冲区进行的,一旦场景渲染完成后会将后置缓冲区内容与前置缓冲区交换,保证图像的连续性。
3.2.4.1 裁切测试
裁切测试可以避免当视口比屏幕窗口小时造成的渲染浪费问题。
3.2.4.2 Alpha 测试
Alpha 测试可以根据片段颜色的 Alpha 值来裁剪片段。 由于 Alpha 测试本身消耗较大,性能较低,所以只有在必要的情况下才会使用 Alpha 测试。
注:片段着色器执行后再进行深度/模板测试,来确定到底哪些片段(Fragments)对屏幕显示有贡献,哪些片段需要被丢弃(Discard),只有哪些通过了深度/模板测试的片段才成为屏幕上显示的像素(Pixels),这也就是片段和像素本质的区别,也就是说片段是最终显示在屏幕上像素的候选者。 由于深度/模板测试是在片段着色器之后进行的,所以导致着色器计算资源的浪费,因为这些被遮挡的片段对我们最终的画面是没有任何贡献的,而我们还花费了大量的资源对它们进行了复杂的光照等一系列计算。 以上是 Alpha 测试效率较低的原因。
3.2.4.3 模板测试
我们可以把它理解为一个模子 mask,通过 mask 的值来控制那些片段的可见性,无法通过模板测试的片段将被丢弃。
3.2.4.4 深度测试
比较当前片段的深度值是否比深度缓冲中预设的值小(默认比较方式),如果是更新深度缓冲和颜色缓冲;否则丢弃片段不更新缓冲区的值。
3.2.4.5 Alpha 混合
当场景中既有不透明物体,又有半透明物体时,我们需要先渲染不透明物体,渲染顺序为从前往后;然后再渲染半透明物体,渲染顺序为从后往前。我们需要对不透明和半透明物体分开渲染是因为:我们可以透过半透明物体看到半透明物体背后的东西,所以对半透明物体进行渲染时需要后面图层的信息,才能够正确进行混合。
|