这篇文章主要学习,如何用OpenGL ES来渲染一张图片。
准备资料
OpenGL(Open Graphics Library) 是 Khronos Group (一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准)开发维护的一个规范,它是硬件无关的。它主要为我们定义了用来操作图形和图片的一系列函数的 API,OpenGL 本身并非 API。
OpenGL ES(OpenGL for Embedded Systems) 是 OpenGL 的子集,针对手机、PDA 和游戏主机等嵌入式设备而设计。该规范也是由 Khronos Group 开发维护。
OpenGL ES 去除了四边形(GL_QUADS)、多边形(GL_POLYGONS) 等复杂图元,以及许多非绝对必要的特性,剩下最核心有用的部分。可以理解成是一个在移动平台上能够支持 OpenGL 最基本功能的精简规范。
一、基本概念
1. 缓存
OpenGL ES一部分运行在CPU上, 一部分运行在GPU上, 为了协调这两部分的数据交换, 定义了Buffers(缓存)的概念. CPU和GPU都有独自控制的内存区域, 缓存可以避免数据在这两块区域之间进行复制, 提高效率
2. 纹理、渲染和纹理渲染
纹理是一个用来保存图像颜色的元素值的缓存,
渲染是指将数据生成图像的过程;
纹理渲染就是将保存在内存中的颜色值等数据, 生成图像的过程
3. 坐标系
3.1 OpenGL ES中的坐标系
OpenGL ES中的坐标系是一个三维坐标系, 用x,y,z来表示, 坐标系的范围是-1~1.
z轴的正方向指向屏幕外. 如果不考虑z轴, 左下角坐标应该为(-1, -1, 0), 右上角坐标为(1,1,0).
3.2 纹理坐标系
纹理坐标系是一个二维坐标系,用s,t轴来表示, t轴为数轴., s轴为横轴 范围是0~1.
在坐标系中, 点的横坐标一般用u来表示, 点的纵坐标一般用v来表示. 左下角坐标为(0, 0), 右上角坐标为(1, 1). 在UIKit中, 左下角为(0, 1), 右上角为(1. 0)
4. 纹理的相关概念
纹理元素(Texel) : 简称纹素. 当一个图像初始化为一个纹理缓存后, 每个像素会变成一个纹素, 纹理的坐标范围是0~1, 在这个单位长度内, 可能包含任意多个纹素.
片段(Fragment) : 视口坐标中的颜色像素. 没有使用纹理时, 会使用对象顶点来计算片段的颜色; 使用纹理时, 会根据纹素来计算.
光栅化(Rasterizing) : 将几何形状数据转换为片段的步骤.
映射(Mapping) : 对齐顶点和纹素的方式. 即将顶点坐标(x,y,z)和纹理坐标(u,v)对应起来.
取样(Sampling) : 在顶点固定后, 每个片段根据计算出来的(u, v)坐标, 去找相应的纹素的过程.
帧缓存(Frame Buffer) : 一个接收渲染结果的缓冲区, 为GPU指定存储渲染结果的区域.
5. 缓存的使用
在实际应用中, 我们需要用到各种缓存. 例如, 在纹理渲染之前, 会将图像的数据初始化为一块纹理缓存. 这块缓存保存了图像的数据.
使用缓存可分为以下步骤:
-
生成(Generate) : 生成缓存标识符, 函数:
glGenBuffers()
.
-
生成(Generate) : 生成缓存标识符, 函数:
-
绑定(Bind) : 为接下来的操作, 绑定一个缓存. 函数:
glBindBuffer()
.
-
绑定(Bind) : 为接下来的操作, 绑定一个缓存. 函数:
-
缓存数据(Buffer Data): 从CPU的内存复制数据到缓存的内存中. 函数:
glBufferData()
/glBufferSubData()
.
-
缓存数据(Buffer Data): 从CPU的内存复制数据到缓存的内存中. 函数:
-
启用(Enable) / 禁用(Disable) : 设置在接下来的渲染中是否使用缓存的数据. 函数:
glEnableVertexAttribArray()
/glDisableVertexAttribArray()
.
-
启用(Enable) / 禁用(Disable) : 设置在接下来的渲染中是否使用缓存的数据. 函数:
-
设置指针(Set Pointers) : 告知缓存的数据类型, 以及相应数据的偏移量. 函数:
glVertexAttribPointer()
.
-
设置指针(Set Pointers) : 告知缓存的数据类型, 以及相应数据的偏移量. 函数:
-
绘图(Draw) : 使用缓存的数据进行绘制. 函数:
glDrawArrays()
/glDrawElements()
.
-
绘图(Draw) : 使用缓存的数据进行绘制. 函数:
-
删除(Delete) : 清空缓存, 释放内存. 函数:
glDeleteBuffers()
.
-
删除(Delete) : 清空缓存, 释放内存. 函数:
6. OpenGL ES上下文
OpenGL ES 是一个状态机, 相关的配置信息会保存在一个上下文(context)当中. 这些值会被一直保存, 直到被修改.
我们也可以配置多个context, 通过调用[EAGLContext setCurrentContext:context]
来进行切换当前context.
7. OpenGL ES 中的图元
图元(Primitive) 是指 OpenGL ES 中支持渲染的基本图形. OpenGL ES 只支持三种基本图形: 顶点, 线段和三角形. 复杂的图形需要通过渲染多个三角形来实现.
8. 如何去渲染三角形?
渲染流程如上图. 在流程中, 顶点着色器和片段着色器都是可编程的部分. 着色器(Shader) 也是一个程序, 它们运行在GPU上, 在主程序运行的时候, 进行动态编,不用写死在代码中. 编写着色器的语言是GLSL(OpenGL Shading Language) .
下面具体分析一下, 途中流程每一步都做了什么:
8.1 顶点数据
为了渲染一个三角形, 我们需要传入一个包含3个三维顶点坐标的数组.
每个顶点都有对应的顶点属性, 顶点属性中可以包含任何我们想要的数据.
为了让OpenGL ES知道我们需要绘制的是一个三角形, 而不是顶点或者线段, 我们需要在调用绘制(Draw)指令的时候, 将图元信息传递给OpenGL ES
8.2 顶点着色器
顶点着色器是运行在GPU上的一个程序, 它会对每个顶点执行一次运算. 它可以使用顶点数据来计算该顶点的颜色, 光照, 纹理, 左边等
顶点着色器的一个重要任务是进行坐标转换. 例如将模型的原始坐标系(一般为三维坐标系)转换为屏幕坐标系
8.3 形状(图元)装配
在顶点着色器输出顶点坐标后, 各个顶点按照绘制命令中的图元信息参数, 以及顶点索引数组, 被组装成一个个图元
通过这一步, 模型中的3D图元已经被转换成屏幕上的2D图元.
8.4 几何着色器
在OpenGL的版本中, 顶点着色器和片段着色器中间, 还有一个可选的着色器, 叫做几何着色器(Geometry Shader).
几何着色器将图元形式的一系列顶点的集合作为输入, 它可以通过产生新的顶点来构造出新的图元, 从而生成其他的形状.
OpenGL ES 目前不支持几何着色器.
8.5 光栅化
在光栅化阶段, 基本图元被转换成供片段着色器使用的片段.
片段表示可以被渲染到屏幕上的像素, 它包含位置, 颜色值, 纹理坐标等信息. 这些是由图元的顶点信息进行插值计算得到的.
在片段着色器运行之前会进行裁剪, 处于视图以外的部分会被裁剪掉, 用来提升执行效率.
8.6 片段着色器
片段着色器的主要作用是计算每一个片段最终的颜色值(或者是否被丢弃), 片段着色器决定了最终屏幕上每一个像素点的颜色值.
8.7 测试与混合
在这一步, OpenGL ES 会根据片段是否被遮挡, 视图上是否已存在绘制好的片段等情况, 对片段进行混合或者丢弃.
最终保留下来的片段会被写入帧缓存(Frame Buffer), 呈现在屏幕上.
9. 如何渲染多边形?
如图所示,一个五边形. 由于OpenGL ES 只能渲染三角形, 所以我们可以把它拆分成 3 个三角形来渲染。
渲染一个三角形,我们需要一个保存 3 个顶点的数组。这意味着我们渲染一个五边形,需要用 9 个顶点。而且我们可以看到,其中 V0 、 V2 、V3 都是重复的顶点,显得有点冗余。
那么我们想复用这些重复的顶点, OpenGL ES 有没有提供方法呢?
有的.
OpenGL ES中, 对于三角形有三种渲染方式, 在给定的顶点数组相同的情况下, 我们可以指定我们想要的连接方式, 如下图所示:
9.1 GL_TRIANGLES
GL_TRIANGLES
就是没有复用顶点的方式, 以每个顶点绘制一个三角形, 第一个三角形使用 V0 、 V1 、V2 ,第二个使用 V3 、 V4 、V5 ,以此类推. 如果顶点的个数不是 3 的倍数,那么最后的 1 个或者 2 个顶点会被舍弃.
9.2 GL_TRIANGLES_STRIP
GL_TRIANGLES_STRIP
在绘制三角形的时候,会复用前两个顶点。第一个三角形依然使用 V0 、 V1 、V2 ,第二个则会使用 V1 、 V2 、V3,以此类推。第 n 个会使用 V(n-1) 、 V(n) 、V(n+1) .
9.3 GL_TRIANGLES_FAN
GL_TRIANGLES_FAN
在绘制三角形的时候,会复用第一个顶点和前一个顶点。第一个三角形依然使用 V0 、 V1 、V2 ,第二个则会使用 V0 、 V2 、V3,以此类推. 第 n 个会使用 V0 、 V(n) 、V(n+1) 。这种方式看上去像是在绕着 V0 画扇形.