gl079 发表于 2023-3-22 10:57

【图形学基础】渲染管线

渲染管线的的主要功能是决定给定虚拟相机、三维物体、光源、照明模式、以及纹理等诸多条件下,生成或绘制一张二维图像的过程;渲染管线并不是一成不变的,由于软硬件以及平台的差异,也会有一些细节上的差别;广义上从流程上包含4个阶段:应用阶段,几何处理阶段、光栅化阶段、像素处理阶段;每个阶段都是并行处理的;后面的三个阶段统称为GPU渲染管线,即GPU渲染;



应用程序阶段是在CPU端完成,其他阶段都是在GPU端完成

应用阶段(Application)

    不同于其他阶段,该阶段可以完全通过软件实现,开发者拥有绝对控制权;但是为了提升性能,可以在几个并行处理器上同时执行,在CPU设计上,称这种形式为超标量体系(superscalar)结构,因为它可以在同一阶段同一时间做不同的几件事情;
    该阶段的工作主要有:(a)准备好场景数据:照相机的位置、视椎体、场景中的模型、使用的光源等;(b)为了提高渲染性能,需要进行粗粒度剔除(cullling),把不可见的物体剔除;(c)设置每个模型的渲染状态,如:使用的材质(漫反射颜色、高光反射颜色)、使用的纹理、使用的Shader等;主要任务是将需要在屏幕上显示的渲染图元(rendering primitives)、摄像机位置、光照信息等输入到几何阶段。渲染图元可以是点、线、三角形等;
几何处理阶段(Geometry Processing)

    该阶段对每一个渲染图元进行逐顶点、逐多边形的操作;把定点坐标变换到屏幕空间中,再交给光栅器处理;通过对输入的渲染图元进行顶点着色、投影变化、裁剪、屏幕映射后会输出屏幕空间的二维顶点坐标、每个顶点对应的深度值、着色等相关信息,并传递给下一个阶段;


顶点着色(vertex shadering)

    顶点着色的主要工作是:1)通过坐标变换计算出顶点的位置;2)计算顶点相关数据,如顶点法线、纹理坐标等;传统上讲,一个物体的大部分着色工作是对每个顶点的位置和法线应用光照,并且计算出每个顶点的颜色,接着对内部进行颜色插值。因此,这个顶点处理单元被称为顶点着色器(vertex shader);随着现代GPU的到来,一些或者全部的着色都会逐像素进行;顶点着色阶段越发通用并且可能完全不会进行任何着色计算,变成了一个专门设置每个顶点数据的单元;
    顶点的位置涉及几个坐标空间:模型空间(model space)、世界坐标(world space)、相机空间(camera space)、裁剪空间(clip space);对应的变化分别对应:模型变换(model transform)和视图变换(view transform)和投影变换(project transform);


   顶点着色阶段除了计算顶点、法线的位置信息外,还可以输出物体的外观(涉及着色模型和材质);这个决定光线对材质影响的效果被称为着色(shading)。通常,其中一些计算在模型顶点的几何处理过程执行,一些在逐像素处理阶段指向;顶点着色的结果(如颜色,法线、纹理坐标等)被发送到光栅化和逐像素处理阶段,进行插值和表面着色计算;
可选的顶点处理

    每个管线都有上述的步骤,除此之外,GPU还会执行一些可选操作;按顺序依次为:曲面细分(tessellation)、
几何着色(geometry shadering)、流输出(stream output),这些流程并非所有GPU都支持;
裁剪(clipping)

    将不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片;例如,可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面;部分在视野内的图元需要进行的处理;视野外部的顶点应该使用一个新的顶点来替代,这个新的顶点位于这条线段和视野边界的交点处;
    裁剪阶段使用了投影产生的4值齐次坐标进行裁剪,透视空间的三角形无法进行线性插值,所以需要第4个坐标,以便在透视投影时正确插值和裁剪;最后,通过透视除法算出三角形在三维规范化设备坐标下的位置;该视图体积范围为(-1, -1, -1)~(1, 1,1);
屏幕映射(screen mapping)

    负责把每个图元的坐标转换到屏幕坐标系中;这一步输入的坐标仍然是三维坐标系下的坐标(范围在单位立方体内),输出是一个二维坐标系;屏幕映射不会对输入的z坐标进行任何处理、实际上,屏幕坐标系和Z坐标一起构成了窗口坐标系,这些值会被一起传递到光栅化阶段;屏幕映射得到的屏幕坐标决定了这个顶点对应屏幕上哪个像素以及距离这个像素有多远;假设一个场景会被渲染到一个(x1,y1)到(x2,y2)的窗口中,其中x1<x2,y1<y2,屏幕映射转换其实就是一个缩放操作。新生成的x和y坐标被称为屏幕空间坐标,z坐标(OpenGL中为[-1,1],DirectX中为[0,1])会被映射到[z1,z2],其中默认z1=0,z2=1。这些与具体的API实现有关。


光栅化阶段

    光栅化,又称为扫描转换(scan transform),是将屏幕空间的二维顶点(每个顶点的Z值、着色信息、纹理坐标、顶点颜色、法线方向、视角方向等)转换到屏幕上的像素;光栅化也被认为是几何处理和像素处理的连接点;光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色;
视口变换后已知三角形变换到屏幕空间的位置,那么像素该如何显示呢?如何判断像素和三角形呢?这取决于你对GPU管线的设置;比如,如果通过点采样得到方式判断像素是否在三角形内,最简单的方法是对该像素的中心点进行点采样,判断是否在三角形内,如果在,则认为像素在三角形内;还可以使用多个采样点,使用SSAA(超采样,supersampling)或者MSAA(多重采样反走样技术,multisampling sntialiasing techniques)等。还有一种使用保守的光栅化,也就是如果该像素存在和三角形重叠的部分,即认为像素在三角形内部;


三角形设置(triangle setup)

    由硬件的固定管线完成;计算三角形的微分、边缘方程和误差,用于三角形遍历,以及对几何阶段产生的着色数据进行插值;
三角形遍历(triangle traversal)

    检查每一个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元(fragment)。而这样一个找到哪些像素被三角网格覆盖的过程就是三角形遍历;三角形遍历阶段会根据上一个阶段计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。这一步输出得到一个片元序列,这里一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括但不限于:屏幕坐标、深度信息、从几何阶段输出的顶点信息,例如法线、纹理坐标等;
像素处理阶段

    这个阶段之前的处理,确定了三角形内部或者其他图元的像素,像素处理主要分为像素着色(pixel shading)与合并(merging)两个阶段; 像素处理阶段是基于逐像素或者逐采样点处理的;
像素着色(pixel shading)

    完全可编程的,用于实现逐片元的着色操作;前面的阶段并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用于表述一个三角网格是怎么覆盖每个像素的;每个片元就负责存储这样一系列数据。像素着色的输入是插值着色数据,而它的输出是一个或者多个颜色值;这一阶段由可编程GPU内核决定,开发者需要写一套像素着色程序(OpenGL中为fragment shader)进行逐像素计算;这阶段可以完成很多渲染技术,比如,纹理采样;像素着色器的局限是:仅可以影响单个片元。也就是说,当执行像素着色器时,它不可以将自己的任何结果直接发送给它的邻居;其中一个情况例外,片元着色器可以访问到导数信息;
合并(Merging)

    颜色缓冲中存储着每个像素的信息,合并阶段是将像素着色器产生的片元颜色以当前存储在缓冲中的颜色合并;这个阶段也称为ROP,即raster operation(pipeline)或者渲染输出单元。和着色阶段不同,执行此阶段的GPU子单元通常不完全可编程;但是,它可以高度可配置;负责执行操作,如:修改颜色、深度缓冲、进行混合等;这一阶段的主要任务:1)决定片元的可见性;涉及的测试工作:深度测试、模板测试等;未通过测试,则舍弃该片元;不管有没有通过模板测试,都可以根据模板测试修改模板缓冲区中的值;模板测试和深度测试的函数都是由开发者设计的;
【补充】渲染管线深度测试是zbuffer吗?不是;深度测试的结果深度值更新缓存在zbuffer;由于Z-Buffer仅仅储存屏幕上每一个点的单独深度,无法应用在部分透明的图元上,半透明图元必须在渲染了所有不透明图元之后渲染,且按照从后到前的顺序,或者使用其他不依赖顺序算法。全透明渲染是z缓冲区的主要缺点之一。
2)如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合;每个像素的颜色信息被存储在颜色缓冲区,当执行这次渲染,颜色缓冲中已经存在上次渲染结果,应该怎么进行合并呢?对于不透明物体,开发者可以关闭Blend操作,这样片元着色器计算得到的颜色值可以直接覆盖掉颜色缓冲区的像素值;但是对于半透明物体,需要使用混合操作让物体看起来透明;为了充分提高性能,GPU希望尽可能早的知道哪些片元被舍弃的,然后就不需要使用fs计算它们的颜色;在Unity给出的渲染流水线上,它的深度测试是在片元着色器之前,这种将深度测试提前执行的技术通常称为Early-Z技术;屏幕显示的就是颜色缓冲区中的颜色值,但是为了避免我们看到那些进行光栅化的图元,GPU会使用双重缓冲的策略;这就意味着,对场景的渲染是在幕后进行的,即后置缓冲;一旦场景已经被渲染到后置缓冲中,GPU就会交换后置缓冲区和前置缓冲区中的内容,而前置缓冲区是之前显示在屏幕上的图像,由此,保证了我们看到的画面是连续的;
参考资料:


[*]毛星云,《《Real-Time Rendering 3rd》提炼总结》
[*]Real-Time Rendering 4th 译文《二 图形管线》
[*]Real-Time Rendering 4th Edition 实时渲染第四版 第二章 图形渲染管线(The Graphics Rendering Pipeline)
页: [1]
查看完整版本: 【图形学基础】渲染管线