初探OpenGL 及相关名词解析

517 阅读16分钟

一、什么是OpenGL

我们先了解一下OpenGL到底是什么?一般它被认为是一个API(Application Programming Interface, 应用程序编程接口),包含了一系列可以操作图形、图像的函数。然而,OpenGL本身并不是一个API,它仅仅是一个由Khronos组织制定并维护的规范(Specification)。 OpenGL Logo

OpenGL规范严格规定了每个函数该如何执行,以及它们的输出值。至于内部具体每个函数是如何实现(Implement)的,将由OpenGL库的开发者自行决定(译注:这里开发者是指编写OpenGL库的人)。因为OpenGL规范并没有规定实现的细节,具体的OpenGL库允许使用不同的实现,只要其功能和结果与规范相匹配(亦即,作为用户不会感受到功能上的差异)。

实际的OpenGL库的开发者通常是显卡的生产商。你购买的显卡所支持的OpenGL版本都为这个系列的显卡专门开发的。当你使用Apple系统的时候,OpenGL库是由Apple自身维护的。在Linux下,有显卡生产商提供的OpenGL库,也有一些爱好者改编的版本。这也意味着任何时候OpenGL库表现的行为与规范规定的不一致时,基本都是库的开发者留下的bug。

由于OpenGL的大多数实现都是由显卡厂商编写的,当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL,这也是为什么总是建议你偶尔更新一下显卡驱动。

二、OpenGL相关专用名词解析

1.OpenGL 上下文(context)

  • OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。
  • 在应⽤程序调用任何OpenGL的指令之前,需要安排⾸先创建⼀一个OpenGL的 上下⽂文。这个上下文是⼀一个⾮常庞大的状态机(State Machine),保存了了OpenGL中的各种状态,这也是OpenGL指令执行的基础。
  • OpenGL库是用C语言写的,同时也支持多种语言的派生,但其内核仍是一个C库。由于C的一些语言结构不易被翻译到其它的高级语言,因此OpenGL开发的时候引入了一些抽象层。“对象(Object)”就是其中一个。
  • 在OpenGL中一个对象是指一些选项的集合,它代表OpenGL状态的一个子集。比如,我们可以用一个对象来代表绘图窗口的设置,之后我们就可以设置它的大小、支持的颜色位数等等。可以把对象看做一个C风格的结构体(Struct)
struct object_name {
    float  option1;
    int    option2;
    char[] name;
};
  • 切换上下文往会产生较大的开销,但是不同的绘制模块,可能需要使用完全独⽴的状态管理.因此,可以在应用程序中分别创建多个不同的上下文,在不同线程中使用不同的上下文,上下文之间共享纹理、缓冲区等资源.这样的方案,会比反复切换上下文,或者大量修改渲染状态,更加合理⾼效的

2.OpenGL 状态机

  • 状态机 顾名思义就是一种机器,一种包含各种状态的机器。状态机描述了了一个对象在其生命周期内所经历的各种状态,状态间的转变,发⽣转变的动因,条件及转变中所执行的活动。或者说,状态机是一种⾏为,说明对象在其生命周期中响应事件所经历的状态序列以及对那些状态事件的响应。因此具有以下特点:

    • 有记忆功能,能记住其当前的状态。
    • 可以接收输入,根据输入的内容和自己的原先状态,修改⾃⼰当前状态,并且可以有对应输出。
    • 当进入特殊状态(停机状态)的时候,变不再接收输⼊入,停⽌⼯作。
  • OpenGL 状态机 在OpenGL中可以这么理解:

    • OpenGL可以记录⾃己的状态(如当前所使用的颜⾊色、是否开启了混合功能等)。
    • OpenGL可以接收输入(当调用OpenGL函数的时候,实际上可以看成OpenGL在接收我们的输入),如我们调⽤用glColor3f,则OpenGL接收到这个输⼊后会修改自己的“当前颜色”这个状态。
    • OpenGL可以进入停止状态,不再接收输入。在程序退出前,OpenGL总会先停止⼯作的。

3.渲染

  • 将图形/图像数据转换成3D空间图像操作叫做渲染。简单理解就是,将需要显示的图形图像数据,经过转换变成显示到屏幕上内容。

4.顶点数组(VertexArray)和顶点缓冲区(VertexBuffer)

现代建筑基本都是框架式结构,修建的时候先搭建房子的主体框架,有了框架,然后再根据设计图在对应的位置填充不同的材料(窗户玻璃、砖墙体等)就可以了。在OPenGL中类似,显示图像也是先搭建框架,再进行填充。与现实中不同的是,OpenGL中的图像都是由图元组成。在OpenGLES中,有3种类型的图元:点、线、三⻆形。所以我们只要能够知道这三种图元的定点的位置,即可确定图像的具体位置。那这些顶点数据最终是存储在哪里的呢?

  • 在绘制一个图形是,构成这个图形所需要图元的位置的点(线的两个点,三角形的三个点),即为顶点
  • 开发者可以选择设定函数指针,在调用绘制方法的时候,直接由内存传⼊顶点数据,也就是说这部分数据之前是存储在内存当中的,这些数据就被称为顶点数组
  • 性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中。这部分的显存,就被称为顶点缓冲区

5.管线

汽车生产线,是将汽车从合金框架一步步组装成为可以让我们的驾驶的汽车的必经流程。管线在OpenGL中类似,就是我们渲染图像的必经流程。管线是一个抽象的概念,之所以称之为管线是因为显卡在处理数据的时候是按照一个固定的顺序来的,而且严格按照这个顺序。就像水从一根管⼦的⼀端流到另一端,这个顺序是不能打破的。 管线也分为固定管线(存储着色器)和可编程管线:

  • 固定管线也叫存储着色器:使用固定管线时,OpenGL不需要也不允许你自己去定义顶点渲染和像素渲染的具体逻辑,它内部已经固化了一套完整的渲染流程,只需要开发者在CPU代码端输入渲染所需要的参数并指定特定的开关,就能完成不同的渲染.

  • 可编程管线:OpenGL的可编程管线必须由开发者自行实现渲染流程,否则无法绘制出最终的画面.开发者可以根据自己的具体需要来编写顶点渲染和像素渲染中的具体逻辑,可以最大程度的简化渲染管线的逻辑以提高渲染效率,也可以自己实现特定的算法和逻辑来渲染出固定管线无法渲染的效果。

固定管线可编程管线的区别就是,固定管线是OpenGL封装好的可以直接传入参数调用来实现渲染的.而可编程管线需要开发者完全通过自定义来实现渲染的.但可编程管线不是说管线中的每一步流程都可以自定义实现,直到OpenGLES 3.0,依然只支持顶点着色器片源着色器这两个最基础的着色器这两步的自定义。

6.着色器程序(Shader)

**着色器(Shader)**是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出 有些着色器允许开发者自己配置,这就允许我们用自己写的着色器来替换默认的。这样我们就可以更细致地控制图形渲染管线中的特定部分了,而且因为它们运行在GPU上,所以它们可以给我们节约宝贵的CPU时间。

  • GLSL:即为着色器语言(OpenGL Shading Language),GLSL是一种类C语言。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。着色器的开头总是要声明版本,接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。 一个典型的着色器有下面的结构:
#version version_number
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;

uniform type uniform_name;

int main()
{
  // 处理输入并进行一些图形操作
  ...
  // 输出处理过的结果到输出变量
  out_variable_name = weird_stuff_we_processed;
}

OpenGL着⾊语言GLSL是用来在OpenGL中着⾊编程的语言,也即开发人员写的短小的自定义程序。它们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性.⽐如:视图转换,投影转换等.GLSL的着⾊器代码分成2个部分:VertexShader(顶点着⾊色器)FragmentShader(⽚元着⾊器)

  • 常见的着色器主要有顶点着⾊器(VertexShader)⽚段着⾊器 (FragmentShader)/像素着色器(PixelShader)几何着色器 (GeometryShader)曲⾯细分着色器(TessellationShader)。片段着色器(片元着色器)和像素着色器只是在OpenGL和DX中的不同叫法⽽而已。
  • OpenGL在处理shader时,和其他编译器一样.通过编译链接等步骤,生成了着色器程序(glProgram),着色器程序同事包含了顶点着色器和片元着色器的运算逻辑.在OpenGL进行绘制的时候,首先顶点着色器对传入的顶点数据进行运算.再通过图元装配,将顶点转换为图元.然后进行光栅化,将图元这种矢量图形,转换为栅格化数据.最后,将栅格化数据传入片元着色器中进行运算.片元着色器会对栅格化数据中的每一个像素进行运算,并决定像素的颜色。

1).顶点着色器(VertexShader)

  • 一般用来处理图形每个顶点的变换旋转/平移/投影等
  • 顶点着色器是OpenGL中用于计算顶点属性的程序,顶点着色器是逐顶点运算的程序,也就是说,每个顶点数据都会并行执行一次顶点着色器,并且顶点着色器运算过程中无法访问其他顶点的数据.
  • 一般来说典型的需要计算的顶点属性主要包括顶点坐标变换,逐顶点光照运算等等.顶点坐标由自身坐标系转换到归一化坐标系的运算,就是在这里发生的.

2).片源着色器(FragmentShader)

  • 一般用来处理图形中的每个像素点颜色计算和填充
  • 片元着色器是OpenGL中用于计算片元(像素)颜色的程序。片元着色器是逐像素运算的程序,也就是说每个像素都会并行执行一次片元着色器。

7.光栅化Rasterization

  • 光栅化是把顶点数据转换为片元数据的过程,具有将图转化为一个个栅格组成的图象的作用,⽚元中的每⼀个元素对应于帧缓冲区中的一个像素。
  • 光栅化其实是一种将⼏何图元变为⼆维图像的过程。该过程包含了两部分的工作。第⼀部分⼯作:决定窗口坐标中的哪些整型栅格区域被基本图元占用;第二部分⼯作:分配一个颜⾊值和⼀个深度值到各个区域。光栅化过程产生的是片元。
  • 把物体的数学描述以及与物体相关的颜色信息转换为屏幕上用于对应位置的像素及用于填充像素的颜色,这个过程称为光栅化,这是⼀个将模拟信号转化为离散信号的过程。

8.纹理

从前面我们已经了解到,我们可以为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销,因为每个模型都会需求更多的顶点,每个顶点又需求一个颜色属性。

艺术家和程序员更喜欢使用纹理(Texture)。纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节;你可以想象纹理是一张绘有砖块的纸,无缝折叠贴合到你的3D的房子上,这样你的房子看起来就像有砖墙外表了。因为我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点。 就像下图,在三角形上贴了一张绘有砖块的纸。 image

9.混合(Blending)

在测试阶段之后,如果像素依然没有被剔除,那么像素的颜⾊将会和帧缓冲区中附着上的颜色进⾏混合,混合的算法可以通过OpenGL的函数进行指定。但是OpenGL提供的混合算法是有限的,如果需要更加复杂的混合算法,⼀般可以通过像素着⾊器进⾏实现,当然性能会比原生的混合算法差⼀些。

物体透明分成两种透明,一种是完全透明另一种是部分透明,完全透明的话会使颜色完全穿透,而部分透明使颜色穿透的同时也显示自身颜色。为了渲染出不同的透明度级别,我们需要开启混合(glEnable(GL_BLEND))。简单理解就是两张不同颜色图片放在一起,它们重合的地方就需要运用混合来展示新的颜色。

10.变换矩阵(Transformation)

我们现在已经知道了如何创建一个物体、着色、加入纹理,给它们一些细节的表现,但因为它们都还是静态的物体,仍是不够有趣。我们可以尝试着在每一帧改变物体的顶点并且重配置缓冲区从而使它们移动,但这太繁琐了,而且会消耗很多的处理时间。我们现在有一个更好的解决方案,使用(多个)矩阵(Matrix)对象可以更好的变换(Transform)一个物体。这些矩阵即为换矩阵(Transformation)。 ##11.投影矩阵(Projection)

  • 投影在OpenGL中起着至关重要的作用,我们常见的显示设备电视、手机、电脑都是二维的,但是我们看到显示的内容很多时候都是立体的也就是三维的。投影的作用就是⽤于将3D坐标转换为⼆维屏幕坐标,实际线条也将在二维坐标下进行绘制。同样的,我们也是运用矩阵来进行实现的,这些矩阵也即为投影举证。
  • 投影分为正投影透视投影,不同的投影方式,我们看到的结果有很大不同。如下图:

12.渲染上屏/交换缓冲区(SwapBuffer)

  • 渲染缓冲区一般映射的是系统的资源比如窗口.如果将图像直接渲染到窗口对应的渲染缓冲区,则可以将图像显示到屏幕上.在最简单的情况下,帧缓冲区只有一个,这时帧缓冲区的读取和刷新都都会有比较大的效率问题。
  • 为了解决这个问题,常规的OpenGL程序至少都会有两个缓冲区,即双缓冲机制。显示在屏幕上的成为屏幕缓冲区,没有显示的成为离屏缓冲区.在这种情况下,GPU 会预先渲染好一帧放入一个缓冲区内,让视频控制器读取,当下一帧渲染好后,GPU 会直接把视频控制器的指针指向第二个缓冲器。如此一来效率会有很大的提升。
  • 双缓冲虽然能解决效率问题,但会引入一个新的问题。当视频控制器还未读取完成时,即屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象,如下图:

为了解决这个问题,GPU 通常有一个机制叫做垂直同步(简写也是 V-Sync),当开启垂直同步后,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新。这样能解决画面撕裂现象,也增加了画面流畅度,但需要消费更多的计算资源,也会带来部分延迟。

参考资料: learnopengl-cn.github.io/intro/ www.w3cschool.cn/stiahw/fb7j…

觉得不错记得点赞哦!听说看完点赞的人逢考必过,逢奖必中。ღ( ´・ᴗ・` )比心