OpenGL应用开发---渲染管线

来源:互联网 发布:淘宝店铺怎样刷信誉 编辑:程序博客网 时间:2024/06/07 11:10

一、图形渲染与渲染流水线

在计算机中的图形渲染,给定一个视点,和需要绘制的图形的几何顶点的几何,然后通过一系列的数学运算,然后转换为计算机可以识别的数据最后显示在二维的屏幕上。

在我们生产手机的工厂中就有许多的流水线,流水线的每个阶段都执行不同的功能,而在计算机图形中我们也是将图形的渲染分为几个大的阶段,而每个大的阶段中又有几个小的阶段,在大的阶段中我们通过把渲染管线分为三个阶段
1、应用程序阶段
2、几何阶段
3、光栅化阶段

、

二、应用程序阶段

应用程序阶段主要是在CPU执行,使用C/C++等编程语言,对内存中的数据进行操作。,在应用程序阶段主要执行了几个操作:

1、图形绘制数据的加载,将硬盘中的图形数据加载到系统内存中,当数据加载到内存中后我们可以操作顶点进行相关的操作,列入碰撞检测,场景图形的建立,剔除完全处于摄像机视野的几何体(裁剪)等。在进行这些操作之后把我们需要进行图形渲染的数据再拷贝到显存中。提供给我们的GPU进行数据的渲染。

2、设置渲染状态,渲染状态就是指定那些网格使用那些材质和着色器,以及怎样的方式去绘制这些网格,不同的物体有不同的渲染状态,当我们没有对物体改变渲染状态时则所有的物体都会使用同一种渲染状态。

3、在所有的工序都准备好之后我们就可以开始进行图形渲染了,CPU向GPU发送图形绘制的指令,GPU接收到绘制指令之后就会根据渲染状态对对应的的物体进行渲染。而这些指令在DirectX或者OpenGL中则表现为图形绘制命令。

在了解到应用程序阶段执行的操作之后我们还需要知道CPU和GPU之间数据又是如何拷贝到显存中的,以及绘制图形缓慢的原因。

1、CPU与内存之间通过内存宽带进行连接,CPU通过内存宽带获取内存中的数据进行处理,在处理完成之后又通过内存宽带传递到内存中储存,而内存中的数据则是通过Pcle宽带进行传输到显存进行储存,GPU则和CPU一样通过显存宽带获取显存中的数据进行处理,在处理完成之后也通过显存宽带存放到显存中,等待以后的利用。

2、当我们每次绘制一次图形,都需要从CPU向GPU传递绘制图形的数据和渲染指令,每次数据的传递都需需要在显存中分配一片接触空间来储存这些数据,而且PCle内存宽带的数据传输速率又低,这样导致CPU运算过载,而GPU本身处理速度很快,在处理完成上一次的绘制数据后没有新的绘制指令就会不在进行运算。在现代的图形绘制系统中为了解决这个问题,CPU在向GPU发送图形绘制指令的时候创建一个命令缓冲,将需要绘制的模型数据和绘制指令、渲染状态指令等一次性通过PCle内存宽带传递到显存,此时GPU只需要从命令缓冲中读取绘制指令进行图形的绘制就行,而不必每绘制一次图形,就传递一次数据和指令。

三、GPU与显存

1、为什么使用GPU出来数据而不直接使用CPU
CPU由大部分的控制器和寄存器组成很少的部分逻辑运算单元(ALU),控制器的主要主要作用是从运行的程序中取出执行,然后对指令进行分析确定该执行怎样的操作,然后通过协调各个硬件部分进行协调工作,而逻辑运算单元则用于对程序中的数据进行运算处理,由于CPU中的逻辑运算单元很少,再进行数据运算的时候非常的慢.所以在CPU中适合用于指令的获取与分析,控制和协调计算机各个部分的共同工作。
GPU则由大部分的逻辑运算单元和少部分的控制器组成,并且GPU采用的是并行设计结构,即在GPU中可以在同一时刻同时处理多个数据,而且运算速度非常的快,适合用于大量的数据运算,但在指令解析等方面则非常的弱,同时在处理数据的时也无法知道数据与数据之间的关联。

从图中我们可以看出CPU在进行数据处理的时候在同一时刻只能同时处理一个元素,所以需要使用循环语句来循环处理,而GPU采用并行结构,无需循环,在同一时间则则可以全部处理完成所有的数据。
游戏中有大量的模型顶点需要进行数学运算,所以我们在对游戏中的逻辑编程的时候使用C++等编程语言在CPU中执行运算,而涉及到顶点变化等运算我们则使用shader语言在GPU中运算

2、 Shader原理
shader就如同Lua脚本一样是运行于GPU上的脚本语言,它也有一个虚拟机,而Shader语言的虚拟机则是构建与寄存其中,在Shader运行的时候通过这个虚拟机将Shader翻译成为当前硬件支持的硬件指令。

3、显存与寄存器
在GPU中显存和内存一样是一个独立的模块用于处理各种需要渲染的数据,而寄存器和CPU中寄存器一样则嵌入在GPU处理器中用于用于存储Shader运行时将要处理或者处理过后的的临时数据、Shader指令集等。当Shader运行完成后会将这些处理过后的数据在保存到显存中。

4、GPU中的几个缓冲区对
一块显卡上具有多个区域用于储存不同的数据,以方便Shader在运行的时候快速的读取数据。
1、帧缓冲区
帧缓冲区是一个抽象的概念,它被描述为储存了显示一帧图像的所有信息,它并不是一个实质的储存区域,我们可以指定通过自由组合颜色缓冲区、模板缓冲区、深度缓冲组建我们自己的帧缓冲。

2、颜色缓冲区
颜色缓冲区是一个实质的储存区域用于储存每一个像素的颜色信息。我们读取的纹理像素信息也被储存在这个颜色缓冲中。

3、深度缓冲区
每一个像素离摄像机的远近不同,而我们通过一个值来表示像素深度值,通过这个深度值我们可以剔除一些我们不想显示的像素。
深度缓冲与和颜色缓冲区大小一样,在深度缓冲区中储存的每一个像素的深度信息。

4、顶点缓冲区
用于储存顶点属性信息数据列如坐标、纹理坐标、顶点颜色等信息。

5、索引缓冲区
索引缓冲区用于储存每个顶点的索引值,根据每个顶点的顶点的索引值我们可以重复的使用同一个顶点,以减少储存空间。

6、模板缓冲区
模板缓冲区是一个额外的缓冲区,它和深度缓冲区共享一片内存,在内存中储存着每个像素的一个标记,用于表示当前像素一些特征,列如用一个标记来表示当前像素处于阴影中。

四、几何阶段

在几何阶段主要是对顶点进行运算然后显示在屏幕上的过程。在固定管线时代的流程为:

1、从模型坐标转换为世界坐标系

模型坐标系下的顶点(Object Coordinates):
模型的所有顶点都是以模型自身建立的一个坐标系为参考坐标系,然后在这个坐标系下定义模型顶点的位置,所有的顶点数据都是从此处进行拷贝并进行变化。

世界坐标系为整个游戏场景中的参考坐标系,是一个固定不变的坐标系,所有的模型的坐标都可以在世界坐标系中表示。

物体从世界坐标转为世界坐标使用的是模型矩阵实现的,模型矩阵是一种转换矩阵,它能在世界坐标系中通过对对象进行平移、缩放、旋转来将它置于它本应该在的位置或方向,并且能够改变其大小。

2、从世界坐标转换为摄像机坐标

摄像机作为一个观察者,模拟游戏者的眼睛观看游戏世界中的物体,游戏场景中的物体会根据摄像机的移动而改变,这是因为我们将模型坐标从世界坐标系变换到了摄像机坐标系中表示,随着摄像机的移动,游戏物体也在不断的改变。

从世界坐标系转换为摄像机坐标系使用的是观察矩阵(View Matrix),而这通常是由一系列的平移和旋转的组合来平移和旋转场景从而使得特定的对象被转换到摄像机前面。

3、从摄像机坐标转换为裁剪空间坐标

为了方便裁剪,在图形管线中将裁剪安排在一个单位立方体裁剪空间中进行,这个裁剪空间以摄像机的近裁剪面作为近裁剪面,而裁剪空间空间的近裁剪面的X-Y轴坐标则作为屏幕的像素坐标(左下角为0,0,0),而Z则作为像素的深度值。为了将锥形摄像机可是空间中的物体坐标转换为裁剪空间的坐标,此时我们需要使用透视变换矩阵进行转换。常用的投影方式有两种,平行投影和透视投影。

4、图元装配与裁剪

1、在之前的阶段都是对单个顶点进行处理,并不需要知道点与点之间的关系,我们都知道网格是由顶点、索引值组成,在这个阶段GPU会根据最开始定义顶点数据时,顶点与顶点之间的关系以及顶点的索引值连接组成基本的图元,列如 直线、三角形等、四边形等。

2、在进行图元装配之后就进行裁剪,在裁剪过程中会形成新的顶点,而这些顶点的颜色会更加插值来计算他的颜色。

注释 :我们在CPU中也进行了裁剪,在GPU中也进行了裁剪,这是物体与摄像机视野的关系有三种,完全不在摄像机视野内,部分在摄像机视野内,部分在摄像机视野内,完全不在摄像机视野内是不会被渲染出来的,所以在CPU中将这些不用数据裁剪掉,减少数据传输的大小。而在裁剪空间中则是将部分在视野内的物体超出视野的部分几何数据裁剪掉。

5、屏幕映射

通过裁剪剩下的数据就是我们需要渲染的数据了,但此时的模型坐标数据依然是3D坐标,而屏幕是一个二维坐标系,在这个过程中我们就需要将3D坐标映射到屏幕上。

六、光栅化阶段

光栅化阶段主要是对为像素正确的配色,使渲染出来的图形更加的真实。

1、三角形设置

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

2、三角形遍历

三角形遍历阶段会根据上一个阶段的计算结果来判断一个三角网格覆盖了哪些像素,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值,而这样一个找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也被称为扫描变换(Scan Conversion)。
在这个阶段就需要执行绘制线段算法,填充算法。

前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了(但不限于)它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。

3、片元操作

从上一步骤的到片元之后就会对片元进行操作,列如纹理映射、雾化操作、对片断进行更多计算,如逐片断光照等,这一阶段过后会将片元输出到下一个阶段进行处理。

4、逐片元操作

光栅化的输出是一些列片断(Fragments,这些片断可能经过片断着色器处理),片断被称为“准像素”,要能想象出屏幕坐标系的一个整数坐标上只有一个像素,但可以前后“堆叠”多个片断。这些片断进入逐片断处理(Per-Fragment Operations),首先进行各种测试(下图中共5个),每步测试,不通过的片断将被丢弃从而不能进入后续操作,然后进行一些操作(如混合),最终通过所有处理的片断将被写入FrameBuffer用于最终屏幕显示。

五、可编程管线

在可编程时代,GPU中的多个阶段都可以由Shader进行代替:

根据图可知,顶点变换、光照的计算由顶点着色器完成是完全可编程的,而图元装配是由OpenGL内部完成,开发者是不可控制的,几何着色器是插入在图元装配和裁剪之间的一个完全可编程着色器,裁剪是可配置但不可编程的,屏幕映射、三角形设置、三角形遍历等也是完全不可编程也不可控制的,片元着色器代替了片元的操作阶段,逐片元操作是可配置不可编程的。

五、双缓冲技术

当模型的图元经过了上面层层计算和测试后,就会将所有的书保存到帧缓冲区中,然后显示到我们的屏幕上。我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到那些正在进行光栅化的图元,GPU会使用双重缓冲(Double Buffering)的策略。这意味着,对场景的渲染是在幕后发生的,即在后置缓冲(Back Buffer)中。一旦场景已经被渲染到了后置缓冲中,GPU就会交换后置缓冲区和前置缓冲(Front Buffer)中的内容,而前置缓冲区是之前显示在屏幕上的图像。由此,保证了我们看到的图像总是连续的。

六、OpenGL库的作用

OpenGL图像应用编程接口,这些接口用于渲染二维或三维图形。可以说,这些接口架起了上层应用程序和底层GPU的沟通桥梁。一个应用程序向这些接口发送渲染命令,而这些接口会依次向显卡驱动(Graphics Driver)发送渲染命令,这些显卡驱动是真正知道如何和GPU通信的角色,正是它们把OpenGL或者DirectX的函数调用翻译成了GPU能够听懂的语言,同时它们也负责把纹理等数据转换成GPU所支持的格式。

概括来说,我们的应用程序运行在CPU上。应用程序可以通过调用OpenGL图形接口将渲染所需的数据,如顶点数据、纹理数据、材质参数等数据存储在显存中的特定区域。随后,开发者可以通过图像编程接口发出渲染命令,这些渲染命令也被称为Draw Call,它们将会被显卡驱动翻译成GPU能够理解的代码,进行真正的绘制。

0 0
原创粉丝点击