GLKit

846 阅读10分钟

1. 框架概述

GLKit 框架的设计目的是为了简化基于 OpenGL/OpenGL ES 的应用开发。它的出现加快了 OpenGL/OpenGL ES 应用程序的开发。使用数学库,背景纹理加载,预先创建的着色器效果以及标准视图和视图控制器来实现渲染循环。

GLKit 框架提供了功能和类,可以减少创建新的基于着色器的应用程序所需的工作量,或者支持依赖早期版本的 OpenGL/OpenGL ES 提供的固定函数顶点或片段处理的现有应用程序。

GLKView: 提供绘制场所(View) GLKViewController: 扩展于标准的 UIKit 设计模式,用于绘制视图内容的管理与呈现

2. GLKit 功能

  • 加载纹理
  • 提供高性能的数学运算
  • 提供常见的着色器
  • 提供视图及视图控制器

3. GLKit 纹理加载

3.1 GLKTextureInfo 属性

  • GLuint name: OpenGL 上下文中纹理名称

  • GLenum target: 纹理绑定的目标

  • GLuint width: 加载的纹理的宽度

  • GLuint height: 加载的纹理的高度

  • GLuint depth: 加载的纹理的深度值

  • GLKTextureInfoAlphaState alphaState: 加载的纹理的透明度分量状态

  • GLKTextureInfoOrigin textureOrigin: 加载的纹理中的原点位置

  • BOOL containsMipmaps: 加载的纹理是否包含 mipmap

  • GLuint mimapLevelCount: mipmap 的 level 层级

3.1 GLKTextureInfo 初始化

-(instancetype)initWithSharegroup:(EAGLSharegroup *)sharegroup
-(instancetype)initWithShareContext:(NSOpenGLContext *)context

3.2 使用 GLKTextureLoader 加载纹理

3.2.1 同步加载纹理

  • 从文件加载 2D 纹理图像并从数据中创建新的纹理

    + (nullable GLKTextureInfo *)textureWithContentsOfFile:(NSString *)path options:(nullable NSDictionary<NSString*, NSNumber*> *)options error:(NSError * __nullable * __nullable)outError
    
  • 从 URL 加载 2D 纹理图像并从数据中创建新的纹理

    + (nullable GLKTextureInfo *)textureWithContentsOfURL:(NSURL *)url options:(nullable NSDictionary<NSString*, NSNumber*> *)options error:(NSError * __nullable * __nullable)outError 
    
  • 从内存空间加载 2D 纹理图像并从数据中创建新的纹理

    + (nullable GLKTextureInfo *)textureWithContentsOfData:(NSData *)data options:(nullable NSDictionary<NSString*, NSNumber*> *)options error:(NSError * __nullable * __nullable)outError
    
  • 从 Quartz 图像加载 2D 纹理图像并从数据中创建新的纹理

    + (nullable GLKTextureInfo *)textureWithCGImage:(CGImageRef)cgImage options:(nullable NSDictionary<NSString*, NSNumber*> *)options error:(NSError * __nullable * __nullable)outError
    

3.2.2 异步加载纹理

  • 从文件中异步加载 2D 纹理图像并从数据中创建新的纹理

    - (void)textureWithContentsOfFile:(NSString *)path options:(nullable NSDictionary<NSString*, NSNumber*> *)options  queue:(nullable dispatch_queue_t)queue completionHandler:(GLKTextureLoaderCallback)block
    
  • 从 URL 异步加载 2D 纹理图像并从数据中创建新的纹理

    - (void)textureWithContentsOfURL:(NSURL *)url options:(nullable NSDictionary<NSString*, NSNumber*> *)options queue:(nullable dispatch_queue_t)queue completionHandler:(GLKTextureLoaderCallback)block
    
  • 从内存空间异步加载 2D 纹理图像并从数据中创建新的纹理

    - (void)textureWithContentsOfData:(NSData *)data options:(nullable NSDictionary<NSString*, NSNumber*> *)options queue:(nullable dispatch_queue_t)queue completionHandler:(GLKTextureLoaderCallback)block
    
  • 从 Quartz 图像异步加载 2D 纹理图像并从数据中创建新的纹理

    - (void)textureWithCGImage:(CGImageRef)cgImage options:(nullable NSDictionary<NSString*, NSNumber*> *)options queue:(nullable dispatch_queue_t)queue completionHandler:(GLKTextureLoaderCallback)block
    

4. GLKView

4.1 GLKView 属性

  • id <GLKViewDelegate> delegate: 视图代理对象

  • EAGLContext *context: 绑定的上下文,记录 OpenGL 的状态

  • NSInteger drawableWidth: 底层缓存区对象的宽度(以像素为单位)

  • NSInteger drawableHeight: 底层缓存区对象的高度(以像素为单位)

  • GLKViewDrawableColorFormat drawableColorFormat: 颜色渲染缓冲区的格式

    • GLKViewDrawableColorFormatRGBA8888
    • GLKViewDrawableColorFormatRGB565
    • GLKViewDrawableColorFormatSRGBA8888

    一般我们使用 GLKViewDrawableColorFormatRGBA8888 , 表示颜色值的 RGBA 的每一个分别占 8 位

  • GLKViewDrawableDepthFormat drawableDepthFormat: 深度渲染缓冲区的格式

    • GLKViewDrawableDepthFormatNone: 不使用深度测试
    • GLKViewDrawableDepthFormat16: 深度测试计算精度 16, 一般业务场景使用
    • GLKViewDrawableDepthFormat24: 深度测试计算精度 24, 3D游戏业务场景使用
  • GLKViewDrawableStencilFormat drawableStencilFormat: 模板渲染缓冲区的格式

    • GLKViewDrawableStencilFormatNone: 不使用模板缓冲区
    • GLKViewDrawableStencilFormat8: 启用模板缓冲区
  • GLKViewDrawableMultisample drawableMultisample: 多重采样缓冲区的格式

    • GLKViewDrawableMultisampleNone: 不使用多重采样缓冲区
    • GLKViewDrawableMultisample4X: 启用多重采样缓冲区
  • UIImage *snapshot: 返回一个当前绘制的内容的截图UIImage对象

  • BOOL enableSetNeedsDisplay: 是否响应 UIViewsetNeedsDisplay 方法,如果设为 YES,那么就会像 UIView 一样,如果当前视图正在显示,当下一次绘制循环的时候就会再调用一次drawRect方法,否则就不会调用drawRect方法。

4.2 GLKView 初始化

GLKView 的初始化除了指定 frame 以外还比须指定一个上下文context

- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context

4.3 GLKView 方法

  • - (void)bindDrawable: 将底层的 frameBuffer 对象绑定到 OpenGL ES

  • - (void)deleteDrawable: 删除与视图绑定的可绘制对象,一般需要开发人员在 dealloc 方法里面调用

  • - (void)display: 立即重绘视图内容,类似于 OpenGL 里面的 glutPostRedisplay() 方法

  • - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect: 代理方法,必须实现,一般在这里写绘图的代码

5. GLKViewController

5.1 GLKViewController 属性

  • id <GLKViewControllerDelegate> delegate: 代理对象

  • NSInteger preferredFramesPerSecond: 每秒图像帧数量,默认每秒 30 帧

  • NSInteger framesPerSecond: 实际渲染每秒有多少帧

  • BOOL paused: 用于暂停或者开启控制器的渲染

  • BOOL pauseOnWillResignActive: 是否在控制器即将进入后台暂停渲染循环

  • BOOL resumeOnDidBecomeActive: 是否在控制器已经进入前台开始渲染循环

  • NSInteger framesDisplayed: 从绘制开始到目前为止总共渲染了多少帧

  • NSTimeInterval timeSinceFirstResume: 自视图控制器第一次恢复发送更新事件以来经过的时间量

  • NSTimeInterval timeSinceLastResume: 自上次视图控制器恢复发送更新事件以来经过的时间量

  • NSTimeInterval timeSinceLastUpdate: 自上次视图控制器调用代理方法glkViewControllerUpdate已经经过的时间量

  • NSTimeInterval timeSinceLastDraw: 自上次视图控制器调用GLKViewdisplay() 方法以来经过的时间量

5.2 GLKViewController 方法

5.3 GLKViewControllerDelegate 方法

  • 处理更新事件,在显示每个帧之前调用(必须实现)

    - (void)glkViewControllerUpdate:(GLKViewController *)controller
    
  • 暂停/恢复通知,在渲染循环暂停或恢复之前调用(可选实现)

    - (void)glkViewController:(GLKViewController *)controller willPause:(BOOL)pause
    

6. GLKBaseEffect

GLKBaseEffectAppleGLKit 里面封装的一个工具类,这个工具类可以完成一些着色器的工作,但是不等同于 OpenGL 里面的 shaderManager

6.1 GLKBaseEffect 属性

  • GLboolean colorMaterialEnabled: 表示是否计算光照与材质交互时是否使用颜色顶点属性,默认值为GL_FALSE

  • GLboolean lightModelTwoSided: 表示是否为基元的两侧计算光照,默认值为GL_FALSE

  • GLboolean useConstantColor: 使用使用颜色常量,默认值为GL_TRUE

  • GLKEffectPropertyTransform *transform: 绑定效果时应用于顶点数据的模型视图,投影和纹理变换的矩阵,默认值单元矩阵Identity Matrices

  • GLKEffectPropertyLight *light0, *light1, *light2: 场景中的第一个光照属性,第二个光照属,第三个光照属。默认不使用光照

  • GLKLightingType lightingType: 用于计算每个片元的光照策略,默认为:GLKLightingTypePerVertex

    • GLKLightingTypePerVertex:表示在三角形中每个顶点执行光照计算,然后再三角形进行插值
    • GLKLightingTypePerPixel:表示光照计算的输入在三角形内插值,并且在每个片元执行光照计算
  • GLKVector4 lightModelAmbientColor: 环境颜色,应用于效果渲染的所有图元,默认为 { 0.2, 0.2, 0.2, 1.0 }

  • GLKEffectPropertyMaterial *material: 计算渲染图元光照使用的材质属性,默认值为默认的材料属性:Default material state

  • GLKEffectPropertyTexture *texture2d0, *texture2d1: 第一个属性纹理和第二个属性纹理,GLKBaseEffect 最多支持使用两个纹理

  • NSArray<GLKEffectPropertyTexture*> *textureOrder: 纹理应用与渲染图元的顺序,默认为:texture2d0, texture2d1

  • GLKVector4 constantColor: 颜色常量,默认黑色:{ 1.0, 1.0, 1.0, 1.0 }

  • GLKEffectPropertyFog *fog: 应用与场景的雾属性,默认不启用

  • NSString *label: 名称,类似于创建 dispatch_queue_create()函数创建队列的标签,默认为@"GLKBaseEffect"

6.2 GLKBaseEffect 方法

准备绘制 - (void) prepareToDraw

7. GLKit 加载图片案例分享

GLKit 显示图片案例流程图

7.1 公共部分初始化代码

- (void)setupConfig
{
    // 初始化上下文
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (context == nil) {
        NSLog(@"create openGL ES context failed!"); return;
    }
    
    // 设置为当前上下文
    [EAGLContext setCurrentContext:context];
    
    // 获取 GLKView
    GLKView *glView = (GLKView *)self.view;
    [glView setContext:context];
    
    glView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    glView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    
    // 设置背景色
    glClearColor(1.0, 0.7, 0.2, 1.0);
}

7.2 加载顶点数据

- (void)setupVertetData
{
    // 1. 顶点数据, 纹理坐标
    // 把顶点数据和纹理坐标放在同一个数组里
    GLfloat vertexData[] = {
        
        //x    y    z     s    t
        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  // 左下角
    };
    
    // 2.将上面的顶点数据从内存 copy 到显存中, 是的 openGL ES 的数据访问更高效
    // 2.1 创建顶点缓冲区标识符 id
    GLuint bufferID;
    glGenBuffers(1, &bufferID);
    // 2.2 绑定缓存区
    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    // 2.3 将顶点数据复制到顶点缓冲区里面
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    
    // 3.打开存储顶点坐标数据的 attributed 通道
    /*
     注意: iOS 默认情况下,出于性能的考虑所有顶点属性(attribute)通道是关闭的,不管你使用 GLKit 和 GLSL 都是关闭的, 所以需要我们自己手动打开
     需要用到什么就指定哪一个 index 即可 glEnableVertexAttribArray (GLuint index)
     GLKVertexAttribPosition    // 顶点
     GLKVertexAttribNormal,     // 法线
     GLKVertexAttribColor,      // 颜色值
     GLKVertexAttribTexCoord0,  // 纹理 0 (只有两个纹理, 因为 GLKit 的 GLKBaseEffect 只支持两个纹理)
     GLKVertexAttribTexCoord1   // 纹理 1 (只有两个纹理, 因为 GLKit 的 GLKBaseEffect 只支持两个纹理)
     */
    glEnableVertexAttribArray(GLKVertexAttribPosition); // 打开顶点坐标 attributed 通道
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);// 打开纹理坐标 attributed 通道
    
    // 4. 告知 OpenGL 读取数据的格式 (前三个为顶点坐标, 后面两个是纹理坐标)
    /*
     glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr)
     参数 1: indx, 读取对应数据类型的索引
     参数 2: size, 读取该类型数据的步长
     参数 3: type, 该数据的数据类型
     参数 4: normalized 是否需要归一化, 一般为不需要 GL_FALSE
     参数 5: stride 第一次数据读取和第二次数据读取的间隔
     参数 6: ptr 读取数据的地址
     
     所谓归一化其实就是把数据转为标准格式, 范围转为[-1, 1]
     */
    // 4.1 顶点坐标读取方式
    /*
     参数 1: indx, 读取对应数据类型的索引: GLKVertexAttribPosition
     参数 2: size, 读取该类型数据的步长: 3 (顶点数据坐标有三个 x, y, z)
     参数 3: type, 该数据的数据类型: GL_FLOAT 类型
     参数 4: normalized 是否需要归一化, 一般为不需要 GL_FALSE
     参数 5: stride 第一次数据读取和第二次数据读取的间隔: 5 (第一个顶点数据的 x坐标值和第二个顶点数据坐标值 中间隔了 5 个值)
     参数 6: ptr 读取数据的地址 (GLfloat 类型首地址开始读写, 偏移 0 个位置)
     */
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat  *)NULL + 0);
    // 4.2 纹理坐标读取方式
    /*
    参数 1: indx, 读取对应数据类型的索引: GLKVertexAttribTexCoord0
    参数 2: size, 读取该类型数据的步长: 2 (顶点数据坐标有三个 s, t)
    参数 3: type, 该数据的数据类型: GL_FLOAT 类型
    参数 4: normalized 是否需要归一化, 一般为不需要 GL_FALSE
    参数 5: stride 第一次数据读取和第二次数据读取的间隔: 5 (第一个纹理顶点数据的 s坐标值和第二个纹理顶点数据坐标值 中间隔了 5 个值)
    参数 6: ptr 读取数据的地址 (GLfloat 类型首地址开始读写, 偏移 3 个位置, 因为前面 3 个是顶点数据的 x,y,z, 从第 4 个开始才是纹理坐标 s,t 的值)
    */
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat  *)NULL + 3);
}

7.3 设置纹理代码

- (void)setupTexture
{
    // 1. 获取图片路径
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"];
    
    // 2. 设置纹理相关参数
    /*
     openGL 默认的纹理坐标左下角为 (0,0), 但是 UIView 的左上角为(0,0), 设置了这个属性为 YES 后系统会自动帮我们翻转图片, 如果为 NO 的话我们的显示的效果是倒的
     */
    NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft: @(YES)};
    NSError *error;
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:&error];
    if (error) {
        NSLog(@"GLKTextureLoader 生成 GLKTextureInfo 失败:%@", error);
        return;
    }
    
    // 3. 用 GLKBaseEffect 完成着色器的工作
    // GLKBaseEffect 是 Apple 封装的 OpenGL 的一个工具, 可以完成着色器相关的工作, 方便开发者使用
    baseEffect = [[GLKBaseEffect alloc] init];
    baseEffect.label = @"kun_texture";
    baseEffect.texture2d0.name = textureInfo.name;
    baseEffect.texture2d0.enabled = GL_TRUE;
}

7.4 GLKViewDelegate 代码

// 绘制视图内容
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    // 1. 清除 颜色/深度/模板缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    // 2. baseEffect 准备好可以开始绘图
    [baseEffect prepareToDraw];
    
    // 3. 绘图
    /*
     glDrawArrays (GLenum mode, GLint first, GLsizei count)
     参数 1: mode: 使用什么图元绘制
     参数 2: first: 从顶点数组中第几个顶点开始绘制
     参数 3: count: 总共多少个顶点 (一个正方形分两个三角形绘制,总共 6 个)
     */
    glDrawArrays(GL_TRIANGLES, 0, 6);
}