一、图形API简介

  • OpenGL(Open Graphics Library):是一个跨编程语言、跨平台的编程图形程序接口,它将计算机的资源抽象称为一个个OpenGL的对象,对这些资源的操作抽象为一个个的OpenGL指令。
  • OpenGL ES(OpenGL for Embedded Systems):是OpenGL三维图形API的子集,针对手机、PDA和游戏主机等嵌入式设备而设计的,去除了许多不必要和性能较低的API接口。
  • DirectX:是由很多API组成的,DirectX并不是一个单纯的图形API。最重要的是DirectX是属于Windows上一个多媒体处理API。并不支持Windows以外的平台,所以不是跨平台框架。按照性质分类,可分为四大部分,显示部分、声音部分、输入部分和网络部分。
  • Metal:Apple为游戏开发者推出了新的平台技术,该技术能够为3D图像提高10倍的渲染性能。Metal是Apple为解决3D渲染而推出的框架

二、图形API的目的是解决什么问题

简单的说就是实现图形的底层渲染

  • 比如在游戏开发中,对游戏场景/游戏任务的渲染
  • 比如在音视频开发中,对于视频解码后的数据渲染
  • 比如在地图引擎,对于地图上的数据渲染
  • 比如在动画中,实现动画的绘制
  • 比如在视频处理中,对于视频加上滤镜效果

本质:就是利用GPU芯片来高效渲染图形图像 图形API是iOS开发者唯一接近GPU的方式

三、OpenGL下专业名词解析

1、3D图形渲染API

现今较为知名的 3D 图形 API 有 OpenGL、DirectX 以及 OpenGL ES,它们各自的应用领域如下:

  • DirectX:主要应用于Windows 平台的游戏开发。
  • OpenGL:应用领域比较广泛,适用于 UNIX、Mac OS、Linux 以及 Microsof 等几乎所有操作系统,可以开发游戏、工业建模以及嵌入式设备。
  • OpenGL ES:专门针对于嵌入式设备的,其实是 OpenGL 的剪裁版本,去除了 OpenGL 中许多不是必须存在的特性,如 GL_QUADS(四边形)与 GL_POLGONS(多边形)绘制模式以及 glBegin(开始)/ glEnd(结束)等操作。

注意:基于 OpenGL ES 2.0 的 3D 应用不能在模拟器上运行,必须使用配置了 GPU(GPU 要求支持 OpenGL ES 2.0)的真机(Android 版本要求最低为 2.2)才可以。

2、OpenGL上下文context

2.1 Opengl中的context

  • 在应用程序调用任何OpenGL的指令之前,需要先创建一个OpenGL的上下文context。这个上下文是一个非常庞大的状态机,保存了OpenGL中的各种状态,这也是OpenGL指令执行的基础。
  • OpenGL的函数不管在哪个语言中,都是类似C语言一样的面向过程的函数,本质上都是对OpenGL上下文这个庞大的状态机中的某个状态或者对象进行操作,当然你得首先把这个对象设置为当前对象。因此,通过对OpenGL指令的封装,是可以将OpenGL的相关调用封装成为一个面向对象的图形API。
  • 由于OpenGL上下文是一个巨大的状态机,切换上下文往往会产生较大的开销,但是不同的绘制模块,可能需要使用完全独立的状态管理。因此,可以在应用程序中分别创建多个不同的上下文,在不同线程中使用不同的上下文,上下文之间共享纹理、缓冲区等资源。这样的方案,会比反复切换上下文,或者大量修改渲染状态更加合理高效。

2.2 OpenGL 状态机

概述:状态机理论上是一种机器,它描述了一个对象在其生命周期内所经历的各种状态,状态间的转变、发送转变的动因、条件以及转变中所执行的活动。或者说,状态机是一种行为,即对象在其生命周期中响应事件所经历的状态序列以及对那些状态事件的响应。具有一下特点:

  • 有记忆功能,能够记住当前的状态。
  • 可以接收输入,根据输入的内容和自己的原先状态,修改自己当前状态,并且可以有对应输出。
  • 当进入特殊状态(停机状态)的时候,便不再接收输入,即停止工作。

举例:电脑可以说就是典型的状态机

  • 电脑的存储器(内存、硬盘等),可以记住电脑自己当前的状态(当前暗转在电脑中的软件、保存在电脑中的数据、电脑当前的设置等,其实都是二进制的值,都属于当前的状态)
  • 电脑的输入设备接收输入(键盘、鼠标、文件输入),根据输入的内容和自己的状态(主要指可以运行的程序代码),修改自己的状态(修改内存中的值),并且可以得到输出(将结果显示到屏幕)
  • 当它进入到特殊状态(关机状态)的时候,便不再接收输入,停止工作。

类推OpenGL:可以这么理解

  • OpenGL可以记录自己的状态(如当前所使用的颜色、是否开启了混合功能等)。
  • OpenGL可以接收输入(调用OpenGL函数时,可以理解成OpenGL在接收输入),如调用glColor3f,即OpenGL接收到这个输入后,会修改自己的“当前颜色”这个状态。
  • OpenGL可以进入停止状态,不再接收输入。在程序退出前,OpenGL总会先停止工作。

OpenGL是一个状态机,它保持自身的状态,除非用户输入一条命令让它改变状态。

3、着色器shader

片元:一张图片可以看是由无数个点组成,那么其中的每个点就是一个片元。

顶点着色器:是一个可编程的处理单元,功能是执行顶点的变换、光照、材质的应用与计算等顶点的相关操作。其工作过程为首先将原始的顶点几何信息以及其他属性传送到顶点着色器中,经过自己开发的着色器处理之后产生纹理坐标、颜色、点位置等后继流程需要的各项顶点属性信息。(顶点着色器的源码:保存顶点信息;)

片元着色器:处理片元值及其相关数据的可编程单元,其可以执行纹理的采样、颜色的汇总、计算雾颜色等操作,每个片元执行一次。(片段着色器的源码:将纹理坐标信息传入到片段着色器)

4、着色器程序ShaderProgram

GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。

将固定渲染管线架构变成为可编程渲染管线。因此,OpenGL在实际调用绘制函数之前,还需要指定一个由shader编译成的着色器程序。常见的着色器主要有顶点着色器(VertexShader)、片段/片元着色器(FragmentShader)/像素着色器(PixelShader)、几何着色器(GeometryShader)、曲面细分着色器(TessellationShader)。直到OpenGL ES3.0,依然只支持了顶点着色器和片段着色器这两个最基础的着色器。

OpenGL在处理shader时,和其他编译器一样。通过编译、链接等步骤,生成了着色器程序(glProgram),着色器程序同时包含了顶点着色器和片元着色器的运算逻辑。在OpenGL进行绘制的时候,首先由顶点着色器对传入的顶点数据进行运算。在通过图元装配,将顶点转换为图元。然后进行光栅化,将图元这种矢量图形,转换为栅格化数据。最后,将栅格化数据传入片元着色器中进行运算。片元着色器会对栅格化数据中的每一个像素进行运算,并决定像素的颜色。

OpenGL在iOS开发中的应用 opengl api_着色器

OpenGL在iOS开发中的应用 opengl api_OpenGL在iOS开发中的应用_02

4.1 顶点数据

首先,我们以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data);顶点数据是一系列顶点的集合。一个顶点(Vertex)是一个3D坐标的数据的集合。而顶点数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据,但是简单起见,我们还是假定每个顶点只由一个3D位置和一些颜色值组成的吧。

4.2 顶点着色器FragmentShader

图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理。

变换每个顶点的顶点位置。顶点位置从本地坐标系统到裁剪坐标的变换通过加载执行对应矩阵来完成。

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

4.3 图元装配

图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状。将执行裁剪、透视分割和视口变换过程。

绘制图元的API:可以用 glDrawArraysglDrawElementsglDrawRangeElementsglDrawArraysInstancedglDrawElementsInstanced 命令绘制的几何形状对象(三角形、直线、点精灵)。

4.4 几何着色器

图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。

4.5 光栅化

几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。

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

4.6 片元着色器FragmentShader

OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

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

4.7 测试混合

在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

5、顶点输入

开始绘制图形之前,我们必须先给OpenGL输入一些顶点数据。OpenGL是一个3D图形库,所以我们在OpenGL中指定的所有坐标都是3D坐标(x、y和z)。OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上(在这个范围以外的坐标都不会显示)。

5.1 数据输入

为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去指定这些数据所表示的渲染类型。我们是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?做出的这些提示叫做图元(Primitive),任何一个绘制指令的调用都将把图元传递给OpenGL。这是其中的几个:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP。

顶点: 指的是在绘制一个图形时,它的顶点位置数据,而这个数据可以直接存储在数组中或者将其缓存到GPU内存中。

顶点数组:OpenGL中的图像都是由图元组成,在OpenGL ES中,有3种类型的图元:点、线、三角形。开发者可以选择设定函数指针,在调用绘制方法的时候,直接由内存传入顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组。

顶点缓存区:而性能更高的做法是提前分配一块显存,将顶点数据预先传入到显存当中,这部分的显存,就被称为顶点缓冲区

由于我们希望渲染一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。我们会将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float数组:

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

由于OpenGL是在3D空间中工作的,而我们渲染的是一个2D三角形,我们将它顶点的z坐标设置为0.0。

5.2 标准化设备坐标

一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是标准化设备坐标了,标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴):

OpenGL在iOS开发中的应用 opengl api_着色器_03

与通常的屏幕坐标不同,y轴正方向为向上,(0, 0)坐标是这个图像的中心,而不是左上角。最终你希望所有(变换过的)坐标都在这个坐标空间中,否则它们就不可见了。

你的标准化设备坐标接着会变换为屏幕空间坐标(Screen-space Coordinates),这是使用你通过glViewport函数提供的数据,进行视口变换(Viewport Transform)完成的。所得的屏幕空间坐标又会被变换为片段输入到片段着色器中。

定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。

5.3 顶点缓冲对象

通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

顶点缓冲对象是OpenGL对象。就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:

unsigned int VBO;
glGenBuffers(1, &VBO);

OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲(VBO)绑定到GL_ARRAY_BUFFER目标上:

glBindBuffer(GL_ARRAY_BUFFER, VBO);

从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。

第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。

第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。

第三个参数是我们希望发送的实际数据。

第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:

  • GL_STATIC_DRAW :数据不会或几乎不会改变。
  • GL_DYNAMIC_DRAW:数据会被改变很多。
  • GL_STREAM_DRAW :数据每次绘制时都会改变。


6、着色器语言 GLSL(OpenGL Shading Language)

OpenGL ES 着色语言是一种高级的图形编程语言。其源自于应用广泛的 C 语言,同时具有 RendeMan 以及其他着色语言的一些优良特性,易于被开发人员掌握。 OpenGL ES 的着色语言主要包括以下特性:

  • OpenGL ES 2.0 着色语言是一种高级的过程语言(不是面向对象的)。
  • 对顶点着色器、片元着色器使用的是同样的语言。
  • 基于 C/C++ 的语法及流程控制。
  • 完美支持向量与矩阵的各种操作。
  • 通过类型限定符来管理输入与输出。
  • 拥有大量的内置函数来提供丰富的功能。

(详细介绍)

OpenGL着色语言是用来在OpenGL中着色编码的语言,也就是开发人员写的短小的自定义程序,他们是在GPU(Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。
GLSL的着色器代码分成2个部分: VertexShader(顶点着色器) 和 Fragment Shader(片元着色器)。

6.1 纹理

纹理可以理解为图片。在渲染图形时需要在其编码填充图片,为了使得场景更加逼真,而这里使用的图片,就是常说的纹理。在OpenGL中,更加习惯叫纹理,而不是图片。

6.2 混合 Blending

  • 在测试阶段之后,如果像素依然没有被剔除,那么像素的颜色将会和帧缓冲区中颜色附着上的颜色进行混合。混合的算法可以通过OpenGL的函数进行指定。但是OpenGL提供的混合算法有限的,如果需要更加复杂的混合算法,一般可以通过像素着色器进行实现,当然性能会比原声的混合算法差一些
  • 混合就是把两种颜色混在一起。具体一点,就是把某一像素位置原来的颜色和将要画上去的颜色,通过某种方式混在一起,从而达到特殊的效果

6.3 变换矩阵 Transformation

图形想发生平移、缩放、旋转变换,就需要使用变换矩阵.

6.4 投影矩阵Projection

用于将3D坐标转换为二维屏幕坐标,实际线条也将在二维坐标下进行绘制.

6.5 渲染上屏/交换缓冲区 SwapBuffer

渲染缓冲区一般映射的是系统的资源,比如窗口。如果将图像直接渲染到窗口对应的缓冲区,则可以将图像显示到屏幕上。但是,值得注意的是,如果每个窗口只有一个缓冲区,那么在绘制过程中,屏幕进行了刷新,窗口可能显示出不完整的图像。.

屏幕缓冲区和离屏缓冲区:常规的OpenGL程序至少都会有两个缓冲区。显示在屏幕上的称为屏幕缓冲区,没有显示的称为离屏缓冲区。在一个缓冲区渲染完成之后,通过将屏幕缓冲区和离屏缓冲区交换,实现图像在屏幕上显示。

垂直同步信号:因为显示器的刷新一般是逐行进行的,因此为了防止交换缓冲区的时候,屏幕上下区域的图像分属于两个不同的帧,因此交换一般会等待显示器刷新完成的信号,在显示器两次刷新的间隔中进行交换,这个信号就被称为垂直同步信号,这个技术被称为垂直同步。

三缓冲区:使用了双缓冲区和垂直同步技术之后,由于总是要等待缓冲区交换之后再进行下一帧的渲染(交换后再渲染),使得帧率无法完全达到硬件允许的最高水平,为了解决这个问题,引入了三缓冲区技术,在等待垂直同步时,来回交替渲染两个离屏的缓冲区,而垂直同步发生时,屏幕缓冲区和最近渲染完成的离屏缓冲区交换(等待过程中,先渲染,交换时,直接交换渲染完成的),实现充分利用硬件性能的目的。

7、渲染管线

7.1 渲染管线概念

渲染管线:一般是由显示芯片 (GPU) 内部处理图形信号的并行处理单元组成。这些并行处理单元两两之间是相互独立的,在不同型号的硬件上独立处理硬件单元的数量也有很大的差异,一般越高端的硬件,其中独立处理单元的数量也就越多。

Open GL ES 中的渲染管线实质上指的是一些列绘制的过程。这些过程输入待渲染的 3D 物体的相关描述信息和数据,经过渲染的管线,输出一帧想要的图像。

7.2 OpenGL ES 2.0渲染管线

Open GL ES 1.X和Open GL ES 2.0的区别:Open GL ES 1.X 只是对开发人员开放了其中的一部分 API 接口,但在整个渲染管线的运行过程中开发人员是不能直接干预的。因此,虽然 Open GL ES 1.x 的渲染管线功能已经很强大,但是其留给开发人员的发挥空间并不大,很多特效难以开发,而 Open GL ES 2.0 为开发人员提供了更多的发挥空间。

管线:可以理解为渲染流水线。实际上指一堆原始图形数据途径一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。在OpenGL下渲染图形,就会经历一个一个节点。而这样的操作可以理解为管线。可以想象成流水线,每个任务类似流水线一样执行,任务之间有先后顺序。之所以称之为管线是因为显卡在处理数据的时候是按照一个固定的顺序来的,而且严格按照这个顺序。就像水从一根管子的一端留到另一端,这个顺序不能打破。

固定管线:简单理解为渲染图像这个过程,我们只能通过调用GLShaderManager类的固定管线效果实现一系列的着色器处理。

可编程管线:简单理解为在处理图形的过程,我们能够使用自定义顶点着色器和片元着色器的过程。由于OpenGL的使用场景非常丰富,固定管线或者存储着色器无法完成每一个任务,这时将相关部分开放成可编程。

8、渲染

将图形/图像数据转换成3D空间图像操作叫做渲染。