本案例的目的在于熟悉GLKit框架的使用,如果还有不了解的,可以看看这篇文章二、GLKit 及 常见API
整体效果图如下:
准备工作
- 创建一个iOS项目,并将系统创建的ViewController的父类由UIViewController修改为
GLKViewController
,其中的view的父类由UIView修改为GLKView
- OC版本
- 在ViewController.h文件中导入GLKit框架的头文件
#import <GLKit/GLKit.h>
- 在ViewController.h文件中导入Opengl ES相关头文件
#import <OpenGLES/ES3/gl.h>、#import <OpenGLES/ES3/glext.h>
- Swift版本
- 直接在ViewController.swift中导入GLKit框架即可
import GLKit
下面来讲讲如何使用这个框架加载图片
整体的流程图如下
整体流程图
主要有以下几步:
- setupConfig函数: OpenGL ES 相关初始化,主要是初始化上下文及GLKView对象
- setupVertex函数:设置顶点数据,包括顶点坐标和纹理坐标等
- setupTexture函数:设置纹理
- GLKViewDelegate代理方法:将纹理绘制到屏幕上
注:代码分为OC和swift两个版本
setupConfig函数
该函数主要是初始化上下文、设置GLKView视图以及设置背景色,整体的流程如下
setupConfig函数流程
根据流程图所示,分为4部分
- 初始化context
- 获取GLKView对象,并设置其context,
- 配置渲染缓冲区
- 设置背景颜色
初始化context
主要是初始化上下文,在一个项目中可以有多个上下文,但是当前只能有一个
- OC版本
//1.初始化上下文&设置当前上下文 /* EAGLContext 是苹果iOS平台下实现OpenGLES 渲染层. kEAGLRenderingAPIOpenGLES1 = 1, 固定管线 kEAGLRenderingAPIOpenGLES2 = 2, kEAGLRenderingAPIOpenGLES3 = 3, */ context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3]; //判断context是否创建成功 if (!context) { NSLog(@"Create ES context Failed"); } //设置当前上下文 [EAGLContext setCurrentContext:context];
- Swift版本
//1、初始化上下文,并判断是否创建成功 context = EAGLContext.init(api: .openGLES3) guard let cont = self.context else{return} //2、设置当前上下文 EAGLContext.setCurrent(cont)
获取GLKView对象,并设置其context,
由于之前的准备工作时,已经经控制器的view的修改为了GLKView,所以只需要获得view即可,将类型转换为GLKView,并设置其context为刚刚初始化的context
//OC版本 //2.获取GLKView & 设置context GLKView *view =(GLKView *) self.view; view.context = context; //Swift版本 let glView = self.view as! GLKView glView.context = cont
glView.context = cont
配置相应缓冲区
主要是配置颜色缓存区和深度缓存区
颜色缓存区格式
通过GLKView的属性drawableColorFormat
设置,有以下三种,默认是GLKViewDrawableColorFormatRGBA8888
- GLKViewDrawableColorFormatRGBA8888:尾部的8888表示RGBA各占8bit
- GLKViewDrawableColorFormatRGB565:如果app允许更小范围的颜色,可以设置为这个值,可以让app消耗更少的资源
- GLKViewDrawableColorFormatSRGBA8888,
深度缓存区格式
通过GLKView的属性drawableDepthFormat
设置,有以下三种,默认是GLKViewDrawableDepthFormat24
- GLKViewDrawableDepthFormatNone :表示没有缓冲区
- GLKViewDrawableDepthFormat16:一般用于3D游戏,相比24而言,消耗更少的资源
- GLKViewDrawableDepthFormat24:一般用于3D游戏
//OC版本 //3.配置视图创建的渲染缓存区. view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; view.drawableDepthFormat = GLKViewDrawableDepthFormat16; //swift版本 glView.drawableColorFormat = .RGBA8888 glView.drawableDepthFormat = .format24
设置背景颜色
这个背景色是针对GLKView设置的,这个方法在OC与Swift基本没有区别
glClearColor(0.3, 0.4, 0.5, 1.0)
setupVertex函数
主要是设置顶点数据,并将数据从CPU传递到GPU
主要分为3部分:
- 创建顶点数据
- 开辟顶点缓冲区
- 打开通道
创建顶点数据
使用一维数组创建,每个顶点包括5个数据,即顶点坐标(x,y,z)+纹理坐标(S,T)
//OC版本 //1.设置顶点数组(顶点坐标,纹理坐标) /* 纹理坐标系取值范围[0,1];原点是左下角(0,0); 故而(0,0)是纹理图像的左下角, 点(1,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, //左下 }; //Swift版本 var vertexData: [GLfloat] = [ 0.5, -0.5, 0.0, 1.0, 0.0, //右下 0.5, 0.5, 0.0, 1.0, 1.0, //右上 -0.5, 0.5, 0.0, 0.0, 1.0, //左上 0.5, -0.5, 0.0, 1.0, 0.0, //右下 -0.5, 0.5, 0.0, 0.0, 1.0, //左上 -0.5, -0.5, 0.0, 0.0, 0.0, //左下 ];
开辟顶点缓冲区
是将内存中的顶点数据copy到顶点缓冲区中
- 顶点数据:开发者可以选择设置函数指针,在调用绘制方法时,直接由内存传入顶点数据,即顶点数据是存储在内存中的
- 顶点缓冲区:高性能的做法是提前分配一块显存,将顶点数据预先传入到显存当中,这部分显存即为顶点缓冲区
copy的过程分为3步
- 创建顶点缓存区标识符ID
- 绑定顶点缓冲区,明确这块显示的用途
- 从内存copy到GPU显存
具体的代码如下:
- OC版本
//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);
- Swift版本
//2、拷贝到顶点缓冲区 var bufferID: GLuint = 0 // 创建顶点缓冲区标识符 glGenBuffers(1, &bufferID) // 绑定顶点缓冲区 glBindBuffer(GLenum(GL_ARRAY_BUFFER), bufferID) // coppy顶点数据 glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.stride*vertexData.count, &vertexData, GLenum(GL_STATIC_DRAW))
注:
OC中
sizeof
计算数据大小,这个在swift中并不适用,swift在默认情况下是安全的,即在swift中是禁止使用内存操作的,但是swift并没有对内存的使用进行禁止,所以可以使用
MemoryLayout<GLfloat>.stride*vertexData.count
来获取数据在内存中的大小
打开通道
在ios中,默认情况下,所有顶点着色器的属性(Attribute)变量是关闭的,即顶点着色器在server端是不可用的,即使你已经使用了glBufferData
方法,顶点着色器是无法读取传递过来的顶点数据的,也就无法看到想要的效果
- 必须通过
glEnableVertexAttribArray
方法打开通道,才能让顶点着色器能够访问CPU复制到GPU的数据 - 注:数据在GPU是否可见,即着色器是否可以读取到数据,取决于是都开启了Attribute通道,开启了则可以读取,未开启则无法读取
由于有两部分坐标(顶点坐标+纹理坐标),所以通道需要打开两次,具体的代码如下
- OC版本
//顶点坐标数据 glEnableVertexAttribArray(GLKVertexAttribPosition); /* 参数1-index:指定要修改的顶点属性的索引值,例如 GLKVertexAttribPosition 参数2-size:每次读取的数据,position是3个(x,y,z),颜色是4个(r,g,b,a),纹理是两个(s,t) 参数3-type:数组中每个组件的类型,可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。 参数4-normalized:指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值,默认是GL_FALSE 参数5-stride:指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0 参数6-ptr:指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0 */ 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);
- Swift版本
//3、打开通道(需要打开两次) //oc中的sizeof,在swift中需要使用 GLsizei(MemoryLayout<CGFloat>.size * 5) //swift 指针:UnsafeMutablePointer<GLubyte> glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue)) glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.stride*5), nil) glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue)) glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.stride*5), UnsafeMutableRawPointer(bitPattern: 3*MemoryLayout<GLfloat>.stride))
注:
glVertexAttribPointer的最后一个参数在OC中是
(GLfloat *)NULL + 0
在Swift中则是
UnsafeRawPointer
类型,由于下标是动态变化的,要用UnsafeMutableRawPointer
创建指针
setupTexture函数
主要是设置纹理,并使用苹果GLKit 提供GLKBaseEffect 完成着色器工作(顶点/片元)
setupTexture函数流程
分为以下3步:
- 获取纹理图片路径
- 设置纹理参数
- 初始化effect,并设置相关属性
获取纹理图片路径
这个在iOS开发中是常用的操作,就不过多说明
//OC版本 NSString *filePath = [[NSBundle mainBundle]pathForResource:@"kunkun" ofType:@"jpg"]; //Swift版本 let path = Bundle.main.path(forResource: "mouse", ofType: "jpg")
设置纹理参数
主要是设置纹理与屏幕坐标的映射,因为纹理默认的原点(0,0)在左下角,而屏幕的原点在左上角,需要将其设置为GLKTextureLoaderOriginBottomLeft
- OC版本
//2.设置纹理参数 //纹理坐标原点是左下角,但是图片显示原点应该是左上角. NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil]; GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
- Swift版本
//2、设置纹理参数 guard let textureInfo = try? GLKTextureLoader.texture(withContentsOfFile: path!, options: [GLKTextureLoaderOriginBottomLeft:NSNumber.init(integerLiteral: 1)] ) else { return }
初始化effect
使用苹果GLKit 提供GLKBaseEffect 完成着色器工作(顶点/片元)
//OC版本 cEffect = [[GLKBaseEffect alloc]init]; cEffect.texture2d0.enabled = GL_TRUE; cEffect.texture2d0.name = textureInfo.name; //Swift版本 effect = GLKBaseEffect() effect.texture2d0.enabled = GLboolean(GL_TRUE) effect.texture2d0.name = textureInfo.name
代理方法
GLKViewDelegate的代理方法时必须实现的,在这个方法中绘制视图的内容
- OC版本
/* GLKView对象使其OpenGL ES上下文成为当前上下文,并将其framebuffer绑定为OpenGL ES呈现命令的目标。然后,委托方法应该绘制视图的内容。 */ - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { //1. glClear(GL_COLOR_BUFFER_BIT); //2.准备绘制 [cEffect prepareToDraw]; //3.开始绘制 glDrawArrays(GL_TRIANGLES, 0, 6); }
- Swift版本
override func glkView(_ view: GLKView, drawIn rect: CGRect) { glClear(GLbitfield(GL_COLOR_BUFFER_BIT)) //准备绘制 effect.prepareToDraw() //开始绘制 glDrawArrays(GLenum(GL_TRIANGLES), 0, 6) }