我的
OpenGL
专题学习目录,希望和大家一起学习交流进步!
- OpenGL学习(一)-- 术语了解
- OpenGL学习(二)-- Xcode 搭建 OpenGL 环境
- OpenGL学习(三)-- OpenGL 基础渲染
- OpenGL学习(四)-- 正面&背面剔除和深度测试
- OpenGL学习(五)-- 裁剪与混合
- OpenGL学习(六)-- 基础纹理
- OpenGL学习(七)-- 基础变化综合练习实践总结
- OpenGL学习(八)-- OpenGL ES 初探(上)
- OpenGL学习(九)-- OpenGL ES 初探(下)GLKit
- OpenGL学习(十)-- 着色语言 GLSL 语法介绍
- OpenGL学习(十一)-- 用 GLSL 实现加载图片
- OpenGL学习(十二)-- OpenGL ES 纹理翻转的策略对比
一、GLKit 框架简介
GLKit 框架的设计⽬标是为了简化基于 OpenGL / OpenGL ES 的应用开发。它的出现加快 OpenGL ES 或 OpenGL 应⽤程序开发。使⽤数学库,背景纹理加载,预先创建的着色器效果,以及标准视图和视图控制器来实现渲染循环。
- 不用自己写着色器: GLKit 框架提供了功能和类,可以减少创建新的基于着色器的应⽤程序所需的⼯作量,或者支持依赖早期版本的 OpenGL ES 或 OpenGL 提供的固定函数顶点或片段处理的现有应用程序。
- 功能:
- 1、加载纹理
- 2、提供高性能的数学运算
- 3、提供常⻅的着⾊器
- 4、提供视图以及视图控制器
简单的来说,GLKit 就是为了让 iOS 开发者在使用OpenGL ES 或 OpenGL 的时候更简便更容易上手,封装了一堆库,我们直接只写核心代码就行了。
虽然苹果弃用 OpenGL ES ,但 iOS 开发者可以继续使用。
二、用 GLKit 进行视图渲染
1、GLKView
GLKView 继承 UIView,提供绘制场所(View)。 下面看一下 GLKView 使用 OpenGL ES 绘制内容的视图默认实现:
1、初始化视图
- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;
初始化新视图。2、设置视图的代理
3、配置帧缓冲区对象
drawableColorFormat
颜色缓冲区 的格式drawableDepthFormat
深度缓冲区 的格式drawableStencilFormat
模板缓冲区 的格式drawableMultisample
多重采样缓冲区 的格式4、设置帧缓冲区属性
drawableHeight
底层缓存区对象的高度(以像素为单位)drawableWidth
底层缓存区对象的宽度(以像素为单位)5、绘制视图的内容
context
存储绘制视图内容时使用的 OpenGL ES 上下文状态。- (void)bindDrawable;
将底层 FrameBuffer 对象绑定到 OpenGL ESenableSetNeedsDisplay
布尔值,指定视图是否响应使得视图内容无效的消息。- (void)display;
立即重绘视图内容。snapshot
UIImage 类型,绘制视图内容并将其作为新图像对象返回。6、删除视图 FrameBuffer 对象
- (void)deleteDrawable;
删除与视图关联的可绘制对象。7、实现 GLKViewDelegate 代理方法
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;
绘制视图内容(必须实现代理)
2、GLKViewController
GLKViewController 继承 UIViewController,(扩展于标准的 UIKit 设计模式,用于绘制视图内容的管理与呈现)
1、配置帧速率
preferredFramesPerSecond
视图控制器调用视图以及更新视图内容的速率,默认为 30。
framesPerSecond
视图控制器调用视图以及更新视图内容的实际速率。2、配置 GLKViewController 代理
3、控制帧更新:
paused
布尔值,渲染循环是否已暂停。pauseOnWillResignActive
布尔值,当前程序重新激活活动状态时视图控制器是否自动暂停渲染循环。resumeOnDidBecomeActive
布尔值,当前程序变为活动状态时视图控制是否自动恢复呈现循环。4、获取有关 View 的更新信息:
framesPerSecond
视图控制器自创建以来发送的帧更新数。timeSinceFirstResume
视图控制器第一次恢复发送更新事件以来经过的时间量。timeSinceLastResume
自上次视图控制器恢复发送更新事件以来更新的时间量。timeSinceLastUpdate
自上次视图控制器调用委托方法以及经过的时间量。timeSinceLastDraw
自上次视图控制器调用视图 display 方法以来经过的时间量。5、实现代理方法:
- (void)glkViewControllerUpdate:(GLKViewController *)controller;
处理更新事件
- (void)glkViewController:(GLKViewController *)controller willPause:(BOOL)pause;
暂停/恢复通知
3、GLKBaseEffect
GLKBaseEffect 是 GLKit 提供的一种简单的光照/着色系统,用于基于着色器 OpenGL 渲染。
1、命名 Effect:
label
给 Effect(效果) 命名。2、配置模型视图转换:
transform
绑定效果时应用于顶点数据的模型视图,投影和纹理变换。3、配置光照效果:
lightingType
用于计算每个片段的光照策略,GLKLightingType
。
typedef NS_ENUM(GLint, GLKLightingType) { GLKLightingTypePerVertex, GLKLightingTypePerPixel } NS_ENUM_AVAILABLE(10_8, 5_0);
GLKLightingTypePerVertex 表示在三⻆形中每个顶点执行光照计算,然后在三⻆形进⾏插值。 GLKLightingTypePerPixel 表示光照计算的输入在三角形内插入,并且在每个⽚段执行光照计算。
4、配置光照:
lightModelTwoSided
布尔值,表示为基元的两侧计算光照。material
计算渲染图元光照使⽤的材质属性。lightModelAmbientColor
环境颜⾊,应⽤效果渲染的所有图元。light0
,light1
,light2
分别为场景中第 1、2、3 个光照属性。 注意: GLKit 最多就支持3个光照。5、配置纹理:
texture2d0
是 readonly 的,第一个纹理属性。texture2d1
是 readonly 的,第二个纹理属性。textureOrder
纹理应⽤于渲染图元的顺序。 **注意:**最多就支持俩纹理,三个光照,所以 GLKit 有局限性,如果要支持多个纹理,就不能用 GLKit 了,得自己写了。6、配置雾化:
fog
应用于场景的雾属性。7、配置颜色信息:
colorMaterialEnabled
布尔值,表示计算光照与材质交互时是否使用颜色顶点属性。useConstantColor
布尔值,指示是否使用常量颜⾊。constantColor
不提供每个顶点颜色数据时使⽤的常量颜⾊。8、准备绘制效果:
- (void) prepareToDraw;
准备渲染效果(绘制时同步所有效果更改以保持一致状态)。注意:绘制之前必须写。
三、GLKit 牛刀小试
思维导图如下:

首先我创建一个带默认 storyboard 的工程,为了方便,直接把自带的 View 的 Class类型改为了 GLKView,当然我们也可以用代码 alloc 创建。

然后我们在 .h 文件中导入头文件 GLKit,并且把 ViewController 的父类改为 GLKViewController。
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController
接下来在 .m 文件中导入头文件。
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
定义两个全局变量 EAGLContext 和 GLKBaseEffect
@interface ViewController ()
{
EAGLContext *context;
GLKBaseEffect *cEffect;
}
@end
1、OpenGL ES 相关初始化
先来创建一个方法,命名为 setUpConfig,用来进行 OpenGL ES 的相关初始化
1)、初始化上下文 & 设置当前上下文
- 初始化上下文:
context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
EAGLContext
是苹果 iOS 平台下实现 OpenGL ES 渲染层。 参数代表使用哪种 OpenGL ES 的 API 初始化,OpenGL ES 1 使用的是固定管线,2 和 3 差别不大。
kEAGLRenderingAPIOpenGLES1 = 1
,kEAGLRenderingAPIOpenGLES2 = 2
,kEAGLRenderingAPIOpenGLES3 = 3
。
- 设置当前上下文:
[EAGLContext setCurrentContext:context];
2)、获取GLKView & 设置context
GLKView *view = (GLKView *) self.view;
view.context = context;
3)、配置视图创建的渲染缓存区
(1). drawableColorFormat
:颜色缓存区格式
简介:OpenGL ES 有一个缓存区,它用以存储将在屏幕中显示的颜色。你可以使用其属性来设置缓冲区中的每个像素的颜色格式。
view.drawableColorFormat = >GLKViewDrawableColorFormatRGBA8888;
GLKViewDrawableColorFormatRGBA8888 = 0
, 默认缓存区的每个像素的最小组成部分 (RGBA) 使用 8 个 bit,(所以每个像素 4 个字节,4 * 8 个 bit)。GLKViewDrawableColorFormatRGB565
, 如果你的 APP 允许更小范围的颜色,即可设置这个。会让你的 APP 消耗更小的资源(内存和处理时间)
(2). drawableDepthFormat
:深度缓存区格式
view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
GLKViewDrawableDepthFormatNone = 0
, 意味着完全没有深度缓冲区GLKViewDrawableDepthFormat16
,GLKViewDrawableDepthFormat24
, 如果你要使用这个属性(一般用于 3D 游戏),你应该选择GLKViewDrawableDepthFormat16
或GLKViewDrawableDepthFormat24
。这里的差别是使用GLKViewDrawableDepthFormat16
将消耗更少的资源。
下面为 setUpConfig 方法完整代码:
- (void)setUpConfig {
// 1.初始化上下文&设置当前上下文
context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
//判断context是否创建成功
if (!context) {
NSLog(@"Create ES context Failed");
}
//设置当前上下文
[EAGLContext setCurrentContext:context];
//2.获取GLKView & 设置context
GLKView *view =(GLKView *) self.view;
view.context = context;
//3.配置视图创建的渲染缓存区.
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
//4.设置背景颜色
glClearColor(1, 0, 0, 1.0);
}
2、加载顶点/纹理坐标数据
再创建一个方法,命名为 setUpVertexData,用来加载顶点/纹理坐标数据。
1)设置顶点数组(顶点坐标和纹理坐标):
我这里是把顶点坐标和纹理坐标放到了一个数组里,当然你也可以分别放到两个数组里,但是我觉得一个数组后续操作更方便些,如果是两个数组,还要开辟两个缓冲区。
GLfloat vertexData[] = { 0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下 0.5, 0.5, -0.0f, 1.0f, 1.0f, //右上 -0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上 0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下 -0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上 -0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下 };
这里一共是两个三角形组成的,所以是六个顶点。前三个元素组成顶点坐标,第四个和第五个元素组成二维的纹理坐标,后面以此类推。 纹理坐标系取值范围 [0,1];原点是左下角 (0,0);故而 (0,0) 是纹理图像的左下角, 点 (1,1) 是右上角.
2)开辟顶点缓存区:
- 顶点数组: 开发者可以选择设定函数指针,在调用绘制方法的时候,直接由内存传入顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组
- 顶点缓存区: 性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中。这部分的显存,就被称为顶点缓冲区。
(1)创建顶点缓冲区标识符 ID
GLuint bufferID; glGenBuffers(1, &bufferID);
glGenBuffers(GLsizei n, GLuint *buffers)
的第一个参数是表明有 1 个缓冲区。顶点缓冲对象(Vertex Buffer Objects, VBO)(2)绑定顶点缓存区(明确作用)
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glBindBuffer(GLenum target, GLuint buffer)
的第一个参数代表是做什么用的,GL_ARRAY_BUFFER 代表数组缓冲区。(3)将顶点数组的数据 copy 到顶点缓存区中(内存——>GPU 显存中)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage)
target
参数: 指定是什么类型的数据,和上面glBindBuffer
中的保持一致。
size
参数: 这个数据有多大。
data
参数: 数据的地址。这里因为是数组,所以数组名就是它的首地址。
usage
参数: 绘制方式,静态绘制还是动态绘制。
3)打开读取通道:
(1)默认是关闭的
在 iOS 中, 默认情况下,出于性能考虑,所有顶点着色器的属性 (Attribute) 变量都是关闭的。 意味着,顶点数据在着色器端(服务端)是不可用的。即使你已经使用
glBufferData
方法,将顶点数据从内存拷贝到顶点缓存区中(GPU 显存中)。 所以,必须由glEnableVertexAttribArray
方法打开通道,指定访问属性,才能让顶点着色器能够访问到从 CPU 复制到 GPU 的数据。注意: 数据在 GPU 端是否可见,即着色器能否读取到数据,由是否启用了对应的属性决定,这就是
glEnableVertexAttribArray
的功能,允许顶点着色器读取 GPU(服务器端)数据。
(2)方法简介 A、glEnableVertexAttribArray 方法:
glEnableVertexAttribArray(GLuint index)
功能: 打开对应 attribute 通道的开关。
index 参数: 代表属性通道 顶点
GLKVertexAttribPosition
, 法线GLKVertexAttribNormal
, 颜色值GLKVertexAttribColor
, 纹理1GLKVertexAttribTexCoord
, 纹理2GLKVertexAttribTexCoord1
B、glVertexAttribPointer 方法:
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
功能: 上传顶点数据到显存的方法(设置合适的方式从buffer里面读取数据)
- 参数列表:
1、
indx
参数: 指定要修改的顶点属性的索引值2、
size
参数: 每次读取数量(步长)。(如 position 是由 3 个 (x,y,z) 组成,而颜色是 4 个 (r,g,b,a),纹理则是 2 个)3、
type
参数: 指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE,GL_UNSIGNED_BYTE,GL_SHORT,GL_UNSIGNED_SHORT,GL_FIXED
, 和GL_FLOAT
,初始值为GL_FLOAT
。4、
normalized
参数: 指定当被访问时,固定点数据值是否应该被 归一化(GL_TRUE
) 或者直接转换为固定点值 (GL_FALSE
),一般设为GL_FALSE
。5、
stride
参数: 指定连续顶点属性之间的偏移量。如果为 0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为 06、
ptr
参数: 指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为 0
下面为 setUpVertexData 方法完整代码:
- (void)setUpVertexData {
// 1.设置顶点数组(顶点坐标,纹理坐标)
GLfloat vertexData[] = {
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
0.5, 0.5, -0.0f, 1.0f, 1.0f, //右上
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
-0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下
};
//2.开辟顶点缓存区
//(1).创建顶点缓存区标识符ID
GLuint bufferID;
glGenBuffers(1, &bufferID);
//(2).绑定顶点缓存区.(明确作用)
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
//(3).将顶点数组的数据copy到顶点缓存区中(GPU显存中)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
//3.打开读取通道.
//顶点坐标数据
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
//纹理坐标数据
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
}
3、加载纹理数据(使用GLBaseEffect)
再创建一个方法,命名为 setUpTexture,用来加载纹理数据(使用 GLBaseEffect)。
注意: 因为纹理原点是:左下角(0,0); view 原点是:左上角(0,0); 所以在设置纹理的 options 参数时,需要传
GLKTextureLoaderOriginBottomLeft
翻转一下,不然纹理就是倒着的。这是 GLKit 里的解决办法,在 OpenGL ES 里就没这么方便了。
- (void)setUpTexture {
//1.获取纹理图片路径
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"凡几多" ofType:@"jpg"];
//2.设置纹理参数
//纹理坐标原点是左下角,但是图片显示原点应该是左上角.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
//3.使用苹果GLKit 提供GLKBaseEffect 完成着色器工作(顶点/片元)
cEffect = [[GLKBaseEffect alloc]init];
cEffect.texture2d0.enabled = GL_TRUE;
cEffect.texture2d0.name = textureInfo.name;
}
4、实现代理方法 GLKViewDelegate
GLKView 对象使其 OpenGL ES 上下文成为当前上下文,并将其 framebuffer 绑定为 OpenGL ES 呈现命令的目标。然后,委托方法应该绘制视图的内容。
//绘制视图的内容
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
//1.
glClear(GL_COLOR_BUFFER_BIT);
//2.准备绘制
[cEffect prepareToDraw];
//3.开始绘制,用三角形,从第0个顶点开始画,一共画6个
glDrawArrays(GL_TRIANGLES, 0, 6);
}
5、最终调用
- (void)viewDidLoad {
[super viewDidLoad];
//1.OpenGL ES 相关初始化
[self setUpConfig];
//2.加载顶点/纹理坐标数据
[self setUpVertexData];
//3.加载纹理数据(使用GLBaseEffect)
[self setUpTexture];
}
转载请备注原文出处,不得用于商业传播——凡几多