三维图形渲染显示的全过程

三维图形渲染管线就是将三维场景转化为一幅二维图像的过程。

图像中物体所处位置及外形由其几何数据和摄像机的位置共同决定,物体外表是受到其材质属性、光源、纹理及着色模型所影响。


管线过程由3个大的阶段组成:


Application(应用程序阶段)

运行在CPU上,能被开发者完全控制,该过程所做操作包括:

① 准备场景数据
 -  加载模型:Mesh、Material、Shader、Texture(硬盘 --> 内存 --> 显存)
 -  摄像机(位置、朝向、视锥体)
 -  光源(位置、类型等参数信息)
② 裁剪和剔除 :视锥裁剪、背面剔除、遮挡剔除 (Occlusion Culling)
③ 计算模型视图矩阵
④ 设置渲染状态(RenderState)

渲染管线内部维护着一些状态值。在我们调用渲染API函数进行绘制之前我们需要设置这些状态值。

这些状态值指导GPU如何渲染我们传递到显存的模型和纹理数据。我们称这些状态值为“渲染状态(Render States) ”。

渲染状态包括Shader、Texture、Material、Light内部定义的各种状态等

最后,发起DrawCall调用

Geometry(几何阶段)

负责与每个渲染图元打交道,进行逐顶点、逐多边形的操作。

其重要任务是把顶点坐标变换到带有深度的屏幕空间中,再交给光栅器进行处理。

可进一步分割成:模型视图变换,顶点着色,[曲面细分],[几何着色],投影,裁剪及屏幕映射


模型变换:将模型从模型空间变换到世界空间

视图变换:将各个模型从世界空间变换到眼空间(摄像机处于原点)

通常会把这两个变换矩阵结合成modelview矩阵,并将这个过程称之为模型视图变换


顶点着色器:主要功能是修改顶点属性。如:通过传入模型视图矩阵(MVP)进行顶点空间变换(位置属性)、逐顶点光照(颜色属性)、纹理坐标变换(uv属性)等

顶点着色器的处理单元是顶点,也就是说,输入进来的每个顶点都会调用一次顶点着色器。

顶点着色器只能对输入顶点的相关属性进行修改、创建和忽略,不可以创建或销毁任何顶点,而且无法得到顶点与顶点间的关系。

输入一般是一个变换矩阵和一个相对坐标;输出为眼空间中的坐标及每个顶点所附带的其他属性,如颜色、纹理坐标

曲面细分着色器:用于细分图元,分为3个阶段。
① Control Shader,负责把控后续阶段的初始化操作,例如细化程度(可编程)
② 处理Control阶段的输出,细化Patch数据(不可编程)
③ Evaluation Shader的输入为Patch数据;输出数据为顶点着色器所应输出的数据,但是是批量的(可编程)

几何着色器:输入是1个图元,输出是N个图元(N>=0)

通过Shader程序可以指定Geometry Shader对顶点信息进行增减。还有,因为实际增减的是图元顶点,所以对各种的线段、多边形、粒子等图元也可以进行增减。

利用Geometry Shader的各种方法被创造出来,因为可以自由的生成多边形,那么就可以在地面上生长出草的多边形,或者让3D角色生长出毛发等是最基本的使用方法。

在游戏中,还可以把不需要做逻辑交互处理的例如火花等特效的表现,使用Geometry Shader来生成。

注:Geometry Shader通常是在display driver中实现的,也就是说其实是由CPU负责计算,当重新返回GPU的VS时,对流水线的影响很大,所以Geometry Shader的实际效能并不高,甚至是非常低

投影:分为透视投影与正交投影;在眼空间将模型从三维空间投影到二维平面(D3D投影平面为z=1.0,OpenGL为z=-1.0;为了便于理解,可将其定义为视景体近裁截面)


注:上图为眼空间,D3D为左手系,OpenGL为右手系


投影完成后,会得到归一化的设备坐标(Normalized Device Coordinates,NDC),方便下一步进行硬件裁剪

a. 归一化x、y分量到[-1.0, 1.0]

b. 归一化z深度值(D3D:[0.0, 1.0] OpenGL:[-1.0, 1.0])注:近裁截面为最小深度、远裁截面为最大深度;

裁剪:将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元面片


屏幕映射:将每个图元的x、y坐标从NDC转换到屏幕空间


注:D3D将屏幕左上角作为原点,x轴向右,y轴向下;OpenGL将屏幕左下角作为原点,x轴向右,y轴向上。

Rasterizer(光栅化)

对上个阶段得到的图元各顶点进行插值(z深度值、法线方向、纹理坐标、颜色等)来产生屏幕上的像素,并渲染出最终的图像。

光栅化的任务主要是决定每个渲染图元中的哪些像素应该被绘制在屏幕上


三角形设置:对三个顶点插值计算三角形边上的像素

三角形遍历:扫描三角形边上的像素来插值计算整个三角形内的像素

片元着色器:逐片元的进行着色计算(即逐像素光照)。该阶段可以完成很多重要的渲染技术 如:纹理采样

逐像素、逐顶点光照差异性主要体现在对于非精细模型,在执行逐顶点光照时,由于点距较大,在进行颜色线性插值的过程中,无法精细平滑过渡,导致效果变差。

另外逐像素光照可以在渲染时添加并不存在的表面细节。如通过bump贴图或normal贴图,在原本平坦的表面表现出近似的凹凸效果。

当然,逐像素的计算量要比逐顶点要大


逐片元操作:有时也被称为光栅操作(raster operations ,ROP)或混合操作(blend operations),通过设置来淘汰一些不合格的片元以及如何合并问题


如果一个片元通过了所有的测试,新生成的片元才能和颜色缓冲区中已存在的像素颜色进行Alpha混合,并写入颜色缓冲区

• Alpha测试:


注1:并非所有显卡都支持Alpha测试特性,使用前需要检查显卡是否有该能力
注2:由于大量片元会在该阶段舍弃,Alpha测试可提高含大量透明物件场景的性能

• 模板测试:


注1:若建立模板缓冲区为8bits,则模板值的范围为:[0, 255]的整数;其初始值为清理模板缓冲区的所设置的值
注2:若在模板测试时,关闭了深度测试,则深度测试始终通过

• 深度测试:


注1:深度值范围(D3D:[0.0, 1.0] OpenGL:[-1.0, 1.0]),建立深度缓冲区位数越多,则深度值的精度就会越高;其初始值为清理深度缓冲区的所设置的值
注2:关闭了深度测试,意味着该片元始终通过深度测试

• Alpha混合:


FrameBuffer(帧缓冲)

帧缓冲器(frame buffer):在显卡中硬件实现,用于存放渲染的最终结果。分为:单缓冲、双缓冲(double buffering)、三重缓冲(Triple Buffering)

单缓冲:各个物体的渲染会直接画在屏幕上,效率比较低,由于能看到中间绘制过程,会导致屏幕不断闪烁。一般只用于显示非动态的图像

双缓冲(double buffering):绘制是在一个后备缓冲器(backbuffer)中以离屏的方式进行的。一旦在后备缓冲器中完成绘制,

通过交换指令(D3为Present、OpenGL为SwapBuffer)就可将后备缓冲器中的内容与已经在屏幕上显示过的前台缓冲器(frontbuffer)中的内容进行交换,使得一个完整的帧显示在屏幕上。

完成交换后,后备缓冲器变为前台缓冲区,而前台缓冲区变为后备缓冲区,为下一帧的绘制工作提前做好准备。

我们将前后缓冲区功能互换的行为成为提交(Presenting)。

由于只是将前台缓冲区的指针和后备缓冲区的指针做一个简单的交换,提交是一个运行速度很快的操作。

// OpenGL单缓冲
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glFlush(); //单缓冲的刷新模式;
// OpenGL双缓冲
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutSwapBuffers(); //双缓冲的刷新模式;

三重缓冲(triple buffering):一个前台缓冲区,两个后备缓冲区。

在开启了VSync垂直同步时,若游戏的FPS低于显示器刷新频率,三重缓冲可缓解卡顿现象,然而由于存在2个后备缓冲区,三重缓冲会导致画面有一帧的延迟。(见下文说明)

显示器

以CRT显示器为例(液晶显示器原理类似),CRT的电子枪从左到右,从上到下进行逐行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。

为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。

当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;

而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。

显示器通常以固定频率(如60HZ)进行刷新,这个刷新率就是 VSync 信号产生的频率。


将显卡与显示器的刷新频率通过一个称为VSync的信号同步起来,保证显示器上显示的是一帧完整的画面,来解决Tearing(撕裂)现象(多帧画面同时绘制在显示器上)。


假设游戏的FPS是100,显示器的刷新频率是75Hz,显卡将比显示器快1/3;这意味着,在1个显示器刷新周期内,显卡将写入4/3的帧数据,也就是说,下一帧的1/3覆盖在前一帧之上;

当然,随着系统运行,1/3这个比例会发生变化,1/3,2/3,1,1/3,循环;这种帧与帧之间的不完全覆盖重合现象就是撕裂现象。


D3DPRESENT_PARAMETERS md3dPP;
md3dPP.PresentationInterval = D3DPRESENT_INTERVAL_ONE; // 开启垂直同步

当开启了垂直同步,若游戏FPS高于显示器刷新频率时,显卡会将一部分时间浪费在等待上,显卡必须等待VSync信号的到来才能将绘制好的画面推送给显示器,这也使得游戏的最大FPS下降为显示器的刷新频率


这避免了显卡做一些无用的工作,降低显卡的功耗;然而,VSync技术也有缺点,会导致玩家输入的响应出现延迟;另外,若游戏的FPS低于显示器刷新频率,那么系统的FPS将迅速下降为显示器刷新频率的分数倍上,加剧画面卡顿(Shutter)


Triple Buffering(三重缓冲)可以缓解这一问题,示意图如下:


然而,从上图可以看出由于存在2个后备缓冲区,三重缓冲会导致画面有一帧的延迟。

本文转自:人人都是极客,转载此文目的在于传递更多信息,版权归原作者所有。

最新文章