关于 OpenGL 相关知识

前言

我们目前的、以及将来较长的一段时间游戏项目,都将会采用OpenGL或WebGL的渲染方式。

随着主流浏览器的不断升级和老旧移动设备的淘汰,网页的canvas渲染模式已经不适用于游戏方面的应用。 cocosCreator在其最新版本已经基本上抛弃了canvas渲染,只是在微信小游戏的“开发数据域”的绘制上保留了canvas渲染选项。


# 一. 关于 OpenGL

OpenGL发展至今,已经有20余年,作为一个成熟并久负盛名的跨平台计算机图形应用程序接口规范,它被广泛使用在游戏、影视、军事、航空航天、地理、医学、机械设计,以及各类科学数据可视化等领域。

# 1. OpenGL用途

  • 视频、图形、图片处理
  • 2D/3D游戏引擎开发
  • 科学可视化
  • 医学软件开发
  • CAD(计算机辅助技术)
  • 虚拟实境(AR,VR)
  • AI(人工智能)

# 2. 概念

OpenGL 是一种图形应用程序编程接口(Application Programming Interface,API)。它是一种可以对图形硬件设备特性进行访问的软件库,OpenGL 被设计为一个现代化的、硬件无关的接口,因此我们可以在不考虑计算机操作系统或窗口系统的前提下,在多种不同的图形硬件系统上,完全通过软件的方式实现 OpenGL 的接口。

# 3. 程序操作

OpenG L自身并不包含任何执行窗口任务或者处理用户输入的函数,也没有提供任何用于表达三维物理模型,或者读取图像文件(例如PNG、JPEG文件)的操作,一个用来渲染图像的OpenGL 程序需要执行的主要操作如下:

  • 从 OpenGL 的几何图元中设置数据,用于构建形状;
  • 使用不同的着色器(shader)对输入的图元数据执行计算操作,判断它们的位置,颜色,以及其他渲染属性;
  • 将输入图元的数学描述转换为与屏幕位置对应的像素片元(fragment)。这一步也成为光栅化(rasterization),OpenGL 的片元若最终渲染为图像,那它就是像素;
  • 最后,针对光栅化过程产生的片元,执行片元着色器(fragment shader),从而决定这个片元的最终颜色和位置;
  • 如果有必要,还需要对每个片元执行一些额外的操作,例如判断片元对应的对象是否可见,或者将片元的颜色与当前屏幕位置的颜色进行融合;

# 二. 为什么用OpenGL

难道不能直接将数据从CPU跨到GPU处理?为什么要多此一举,出现OpenGL这个框架?

数据饥饿: 从一块内存中将数据复制到另一块内存中,传递速度是非常慢的,内存复制数据时,CPU和GPU都不能操作数据(避免引起错误)


# 三. 着色器的基本认识

# 1. 图元:

组成图像的基本单元(OpenGL中有7种基本几何图元),它只是顶点的集合以预定义的方式结合在一起而已.

# 2. OpenGL渲染管线:

OpenGL渲染管线(rendering pipeline),一系列有序的处理阶段的序列,用于把我们应用程序中的数据转化为生成一个最终的图像的一个过程.

# 3. GLSL:

专门为图形开发设计的编程语言.


# 四. OpenGL渲染管线

OpenGL 实现了我们通常所说的渲染管线(redering pipeline),它是一系列数据处理过程,并将应用程序的数据转换到最终渲染的图像。

OpenGL 首先接收用户提供的几何数据(顶点和几何图元),并且将它输入到一系列着色器阶段中进行处理,这些阶段包括顶点着色、细分着色(它本身包含两个着色器)以及最后的几何着色,然后再经过图元装配和剪切后,将它们送到光栅化单元(rasterizer)。光栅化单元负责对所有剪切区域(clipping region)内的图元生成片元数据,然后对每个生成的片元都执行一个片元着色器.

# 1. 顶点着色器:

对于绘制命令传输的每个顶点,OpenGL 都会调用一个顶点着色器来处理顶点相关的数据。通常来说,一个复杂的应用程序可能包含许多个顶点着色器,但是在同一时刻只能有一个顶点着色器起作用.

# 2.细分着色器:

顶点着色器处理每个顶点的关联数据之后,如果同时激活了细分着色器(tessellation shader),那么它讲进一步处理这些数据。细分着色器会使用面片(patch)来描述一个物体的形状,并且使用相对简单的面片几何体连接来完成细分的工作,其结果是几何图元的数量增加,并且模型的外观变得更加平顺。细分着色阶段会用到两个着色器来分别管理面片数据并生产最终的形状.

# 3. 几何着色器:

允许在光栅化之前对每个几何图元做更进一步的处理.

# 4. 图元装配:

图元装配将顶点与几何图元之间组织起来,准备下一步的剪切和光栅化工作.

# 5. 剪切:

顶点可能落在视口(viewport)之外,此时与顶点相关的图元会做出改动,以保障相关的像素不会再视口外绘制.

# 6.光栅化:

剪切之后马上要执行的工作,就是讲更新后的图元传递到光栅化(rasterizer)单元,生成对应的片元。光栅化的工作是判断某一部分几何体(点、线、三角形)所覆盖的屏幕空间。得到了屏幕空间信息以及输入的顶点数据之后,光栅化单元就可以直接对片元着色器中的每个可变变量进行线性插值,然后将结果传递给用户的片元着色器。光栅化意味着一个片元的声明伊始,而片元着色器中的计算过程本质上意味着计算这个片元的最终颜色,它决不等价于OpenGL对这个片元所执行的全部操作

# 7.片元着色器:

这个最后一个可以通过编程控制显示颜色的阶段。片元着色器计算片元的最终颜色(尽管在逐片元操作中可能还会最终改变一次颜色)和它的深度值。片元着色器会使用纹理映射的方式,对顶点处理阶段所计算的颜色纸进行补充。如果我们觉得不应该继续绘制某个片元,在片元着色器中还可以终止这个片元的处理,这一步叫做片元的丢弃(discard)。总之,顶点着色(包括细分着色和几何着色)决定了一个图元应该位于屏幕的什么位置,而片元着色使用这些信息来决定某个片元的颜色应该是什么

# 8.逐片元的操作:

除了在片元着色器中做的工作之外,片元操作的下一步就是最后的独立片元处理过程。这个阶段里会使用深度测试(depth test)和模板测试(stencil test)的方式来决定一个片元是否是可见的。如果一个片元通过了所有的测试,那么它就可以被直接绘制到帧缓存中了,它对应的像素的颜色值(也可能包括深度z值)会被更新,如果开启了混合(blending)模式,那么片元的颜色会与该像素当前的颜色相叠加,形成一个新的颜色值并写入帧缓存中。

# 五. OpenGL语法简介

OpenGL 中所有函数都以字符gl作为前缀,还有些以glfwgl3wglew为前缀的函数,来自于第三方库GLFW、GL3W、GLEW.

OpenGL 中的常量也采用GL_为前缀,并且使用下划线来分割单词,如GL_COLOR。这些常量的定义是通过#defines来定义的,它们基本上都可以在 OpenGL 的头文件glcorearb.hglext.h 中找到.

OpenGL 中为函数定义了不同的数据类型,如 GLfloat 表示浮点型。另外,由于 OpenGL 是 C 语言的库,没有重载,所以 OpenGL 中区分同名函数使用后缀的变化来标记,例如glUniform2f()glUniform3fv(),2f 中的2 表示两个参数,f表示参数类型为 GLfloat 类型,即glUniform2f()需要传入两个 GLfloat 类型的参数,而glUniform3fv() 中v表示vector(向量)类型,即这个函数我们需要使用一个3维向量作为参数传入,这个三维向量的每个分量都是 GLfloat 类型,注意,在 OpenGL 中,向量 vector 类型是使用一维数组来表示,所以 glUniform3fv() 的参数为一个含有3个 GLfloat 值的一维数组.

下面是命令后缀与参数数据类型的对应关系:

注意

尽量使用 OpenGL 的类型,因为有可能因为OpenGL自身的实现不同,可能会造成类型不匹配,同时,在不同的 OpenGL 实现之间移植代码时,使用 OpenGL 类型就不会出现不匹配的问题.