在熟悉 OpenGL 图形专有名词和坐标解析之前,需要知道 GPU 在处理图形的几种框架
几种图形处理框架
1. OpenGL (Open Graphics Library) 是一个跨编程语言、跨平台的编程图形程序接⼝,它将计算机的资源抽象称为一个个 OpenGL 的对象,对这些资源的操作抽象为⼀个个的 OpenGL 指令。
2. OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形API的子集,针对⼿机、 PDA和游戏主机等嵌入式设备而设计,去除了许多不必要和性能较低的 API 接⼝。
3. DirectX 是由很多 API 组成的,DirectX 并不是一个单纯的图形API。 最重要的是 DirectX 是属于 Windows 上一个多媒体处理框架,并不支持 Windows 以外的平台,所以不是跨平台框架。按照性质分类,可以分为四大部分,显示部分、声音部分、输入部分和网络部分。
4. Metal 是苹果公司为游戏开发者推出的新技术。该技术能够为3D 图像提高10 倍的渲染性能。所以说 Metal 是苹果公司为解决3D渲染而推出的框架。
图形API主要处理的问题是游戏场景和人物渲染、视频解码后的数据渲染、动画的绘制、滤镜效果,利用 GPU 芯片高效渲染图形,图形API也是iOS开发者唯一接近 GPU 的方式。
OpenGL 图形专业名词解析
状态机
理论上是一种机器,但是它非常难以构想和理解,我们可以类比成一个对象的生命周期内所经历的各种状态,发生状态的变换的动因或是条件所执行的活动。或是说状态机是一种行为,说明对象在生命周期中响应事件所经历的状态序列及对那些状态事件的响应。可以想象成一个类中声明了某些状态变量和方法,状态变量跟方法有某种关联,状态转变时可以对应执行方法获取结果。 因此状态机有下列特点:
1. 有记忆功能,能记住其当前的状态(对应类的状态变量)
2. 可以接收输入,根据输入内容和自己原先状态,修改当前状态,并且可以有对应的输出(状态变量跟方法有某种关联)
3. 可以进入停止状态,不再接收输入,在程序退出前先停止工作(对象释放)
或许这样类比不是很正确,只是这样能有助于我们理解。
上下文 (Context)
在应用程序调用任何 OpenGL 的指令之前,需要首先创建一个 OpenGL 的上下文,这个上下文是一个庞大的状态机,保存了 OpenGL 中的各种状态,这也是 OpenGL 指令执行的基础。OpenGL 的函数执行是面向过程的,本质上都是对 OpenGL 上下文这个状态机的某种状态进行操作。Context 这个应该很熟悉,CoreGraphics/CoreImage/CoreAnimation 中的 API 中有的带有这个变量,可以理解成一个画布。上下文切换会产生较大的开销,但是不同的绘制模块,可能需要独立的状态管理。因此,可以在应用程序中分别创建多个不同的上下文,在不同线程中使用不同的上下文,共享纹理和缓存区等资源,并发高效执行这也是 GPU 能办到的事情。
渲染(Rendering)
将图形/图像数据转换成2D空间图像的操作。
顶点数组(VertexArray)和顶点缓冲区(VertexBuffer)
我们在画图的时候会先画轮廓,之后再填充颜色,对于 OpenGL 也是一样。顶点数据就是要画的轮廓,但是跟现实不同的是 OpenGL 中的图像都是由图元组成的。在 OpenGL ES 中,有3种类型的图元分别是:点、线、三角形。开发者可以选择设定的函数指针,在调用绘制方法的时候,直接由内存传入顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组。而性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中,这部分的显存被称为顶点缓冲区。
顶点指的是我们在绘制一个图形时,它的顶点位置数据,而这个数据可以直接存储在数组中或者将其缓存到 GPU 内存中。
管线
管线是一个抽象的概念,之所以称为管线是因为显卡在处理数据的时候是严格按照一个固定顺序,这个顺序是不能打破的。就像接若干个固定顺序的水管,每个水管就像一个一个节点。
固定管线/存储着色器:封装了很多种着色器程序内置的一段包含了光照、坐标变换、裁剪等诸多功能的固定程序的着色器。只需要传入相应的参数,就能快速完成图形的渲染。
可编程管线:由于OpenGL 的使用场景非常丰富,固定管线或存储着色器无法完成每一个业务,将相关部分开放,要使用 GLSL 着色语言来驱使 GPU。
GLSL(OpenGL Shading Language)
OpenGL 中用来着色编程的语言,也是开发人员写的短小的自定义程序,在 GPU 上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性,比如:视图转换、投影转换等。GLSL 的着色代码分成2个部分:顶点着色器和片元着色器。
着色器程序(Shader)
全面的将固定渲染管线架构变成可编程渲染管线。因此,OpenGL 在实际调用绘制函数之前,还需要指定一个由 Shader 编译成的着色器程序。常见的着色器主要有:顶点着色器、片元着色器/像素着色器、几何着色器、曲面细分着色器。片元着色器和像素着色器只是在 OpenGL 和 DirectX 中叫法不同。可惜的是,直到 OpenGL 3.0 依然只支持顶点着色器和片元着色器这两个最基础的着色器。
OpenGL 在处理 Shader 时,和其他编译器一样,通过编译、连接等步骤,生成着色器程序,着色器程序同时包含了顶点着色器和片元着色器的运算逻辑。
顶点着色器(VertexShader)
顶点着色器是 OpenGL 中用于计算顶点属性的程序。顶点着色器是逐顶点运算的程序,也就是说每个顶点数据都会执行一次顶点着色器,当然这是并行的,并且顶点着色器运算过程中无法访问其他顶点的数据。
一般来说典型的需要计算的顶点属性主要包括顶点坐标变换,逐顶点光照运算等等。顶点坐标由自身坐标系转换到归一化坐标系的运算,就是发生在这里。简单来说,就是用来处理图形每个顶点变换(旋转/平移/投影)。
片元着色器(FragmentShader)
片元着色器是 OpenGL 中用于计算片元(像素)颜色的程序。片元着色器是逐像素运算的程序,也就是说每个像素都会执行一次片元着色器,当然也是并行的。简单来说,就是用来处理图形每个像素点颜色计算和填充。举个例子:调整图片饱和度就是片元着色器逐像素修改。
光栅化(Rasterization)
把物体的数学描述以及物体相关的颜色信息转化成屏幕上用于对应位置的像素及用于填充像素的颜色,一个将模拟信号转化为离散信号的过程称为光栅化。其实就是一种将几何图元变成二维图像的过程,该过程包含两个部分:第一部分决定窗口坐标中的哪些整形栅格区域被基本图元占用,第二部分分配一个颜色值和一个深度值到各个区域。简单来说就是把顶点数据转换成片元的过程,片元中每一个元素对应帧缓冲区的中的一个像素。
纹理
纹理可以理解成图片,在渲染图形时需要在其编码填充图片,为了使场景更加逼真。而这里使用的图片就是常说的纹理。在 OpenGL 中习惯性的称为纹理,而不是图片。
混合(Blending)
在测试阶段之后,如果像素依然没有被剔除,那么像素的颜色将会和帧缓冲区中附着上的颜色进行混合,混合算法可以通过 OpenGL 的函数进行指定。但是 OpenGL 提供的混合算法是有限的,如果需要更加复杂的混合算法,一般可以通过片元着色器进行实现,当然性能会比原生的混合算法差一些。这也是出现离屏渲染的原因。
变换矩阵
图形想要发生平移、缩放、旋转就需要使用变换矩阵。
投影矩阵
用于将3D转换为二维屏幕坐标,实际线条也将在二维坐标下进行绘制。投影矩阵有两种投影方式:第一种正投影,图形绘制不管远近视角都是1:1进行绘制,显示2D效果。第二种透视投影,图形绘制根据远近视角进行绘制,远小近大的3D效果。
渲染上屏/交换缓冲区
渲染缓存区一般映射的是系统的资源,比如窗口。如果将图像直接渲染到窗口对应的渲染缓冲区,则可以将图像显示到屏幕上。值得注意的是,如果每个窗口只有一个缓冲区,那么在绘制过程中屏幕进行了刷新,窗口可能显示出不完整的图像。常规的 OpenGL 程序至少会有两个缓冲区,显示在屏幕上的缓冲区,没有显示的离屏缓冲区。在一个缓冲区渲染完成后,通过将屏幕缓冲区和离屏缓冲区交换,实现图像在屏幕上的显示。
由于显示器的刷新一般是逐行进行的,因此为了防止交换缓冲区的时候上线区域的图像分属于两个不同的帧,交换一般会等待显示器刷新完成的信号。在显示器两次刷新的间隔中进行交换,这个信号就被称为垂直同步信号,这个技术被称为垂直同步。
使用了双缓冲区和垂直同步技术后,由于总是要等待缓冲区交换之后再进行下一帧的渲染,使得帧率无法完全达到硬件允许的最高水平,为了解决这个问题,引入了三缓冲区技术,在等待垂直同步时,来回交替渲染两个离屏缓冲区,当垂直同步发生时,屏幕缓冲区和最近渲染完成离屏缓冲区交换,充分实现利用硬件性能的目的。
坐标解析
2D笛卡尔坐标系
由 x 和 y 轴组成,x 轴表示水平方向,y 轴表示垂直方向。
3D笛卡尔坐标系
由 x、y、z 轴组成,x 轴表示水平方向,y 轴表示垂直方向,z 轴表示深度方向(远近方向)。
左右手坐标系
OpenGL 坐标系(物体、世界、照相机坐标系)属于右手坐标系,从左到右,x 轴是递增的;从下到上,y 轴是递增的;从远到近,z 轴是递增的。OpenGL 里每个顶点的 x、y、z 都应该在−1到1之间,超出这个范围的顶点将是不可见。
设备坐标系使用的是左手坐标系。
物体坐标系
世界坐标系
眼或相机坐标系
规范化的设备坐标系
窗口或屏幕坐标系
坐标变换过程图
模型变换、视变换、投影变换属于用户自定义变换是在顶点着色器中完成。
透视除法、视口变换属于 OpenGL 变换是在顶点着色器处理后的阶段完成。
OpenGL 只定义了裁剪坐标系、规范化设备坐标系和窗口坐标系。
物体坐标系、世界坐标系和眼坐标系都是为了方便用户设计而自定义的坐标系,可以看出不管坐标怎么变换都会来到规范化设备坐标。
视口
OpenGL 会自动建立世界窗口和视口的变换。当世界窗口中所有对象都被绘制时,对象在世界窗口中的部分会被自动地映射到视口中,换句话说,被映射到屏幕坐标中,即像素在显示器上的坐标。
视口是通过 glViewport() 函数设定的,完整的API:
//它设置窗口的左下角,以及宽度和高度。
GLAPI void GLAPIENTRY glViewport(GLintx,GLinty,GLsizeiwidth,GLsizeiheight);