OpenGL ES之GLKit的使用功能和API说明

GLKit简介

一、GLKit框架说明

  • GLKit 框架的设计目标是为了简化基于OpenGL/OpenGL ES的应用开发。它的出现加快OpenGL或OpenGL ES应用程序开发。 使用数学库,背景纹理加载,预先创建的着色器效果,以及标准视图和视图控制器来实现渲染循环。
  • GLKit 框架提供了功能和类,可以减少创建新的基于着色器的应用程序所需的工作量,或支持依赖早期版本的OpenGL或OpenGL ES提供的固定函数顶点或片段处理的现有应用程序。
  • GLKView提供绘制场所(view):GLKViewController扩展于标准的UIKit设计模式,用于绘制视图内容的管理与呈现。
  • 苹果官方文档:GLKit官方说明

二、GLKit的功能

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

使用GLKit视图呈现OpenGL ES内容

OpenGL ES之GLKit的使用功能和API说明_第1张图片
图中展示了绘制OpenGL ES内容的三个步骤:

  • 准备OpenGL ES基础架构
  • 发出绘制命令
  • 将渲染后的内容呈现给Core Animation显示

一、创建和配置GLKit视图(准备OpenGL ES基础架构、发出绘制命令)

  • 当创建视图时,首先创建一个上下文,然后将其传递给视图的initWithFrame:context:方法;
  • 在从故事板加载视图之后,创建一个上下文并将其设置为视图的上下文属性的值;
  • 一个GLKit视图自动创建和配置它自己的OpenGL ES framebuffer对象和renderbuffers。可以使用视图的可绘制属性控制这些对象的属性,如上图所示。如果更改了GLKit视图的大小、比例因子或可绘制属性,那么下次绘制内容时,它会自动删除并重新创建适当的framebuffer对象和renderbuffers;
- (void)viewDidLoad{ 
	[super viewDidLoad];
	// 创建OpenGL ES上下⽂并将其分配给从故事板加载的视图
	GLKView * view =(GLKView *)self.view;
	view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
	// 配置视图创建的渲染缓冲区
	view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
	view.drawableDepthFormat = GLKViewDrawableDepthFormat24; view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
	// 启⽤多重采样
	view.drawableMultisample = GLKViewDrawableMultisample4X;
}

  • 可以使用GLKView实例的drawableMultisample属性启用多层采样。多分段采样是一种反锯齿的形式,它可以平滑锯齿状的边缘,提高大多数3D应用程序中的图像质量,但会使用更多的内存和片段处理时间。

二、用GLKit视图绘图(将渲染后的内容呈现给Core Animation显示)

- (void)drawRect:(CGRect)rect {
	// 清除帧缓冲区
	glClearColor(0.0f0.0f0.1f1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// 使⽤先前配置的纹理,着⾊器和顶点数组绘制 
	glBindTexture(GL_TEXTURE_2D,_planetTexture); 	    
	glUseProgram(_diffuseShading); 
	glUniformMatrix4fv(_uniformModelViewProjectionMatrix,1,0,_modelViewProjectionMatrix.m); 
	glBindVertexArrayOES(_planetMesh); 
	glDrawElements(GL_TRIANGLE_STRIP,256,GL_UNSIGNED_SHORT);
}

三、GLKit视图控制器使OpenGL的动画循环

  • 更新相位增量一个角度变量,并使用它来计算一个变换矩阵
    OpenGL ES之GLKit的使用功能和API说明_第2张图片

  • 对于更新阶段,视图控制器调用它自己的更新方法(或它的委托的glkViewControllerUpdate:方法),在这种方法中,应该准备绘制下一帧。例如,游戏可以使用此方法根据自最后一帧以来收到的输入事件来确定玩家和敌人角色的位置,科学可视化可以使用此方法运行模拟的一个步骤。如果需要时间信息来确定你的应用在下一帧的状态,使用一个视图控制器的时间属性,比如timeSinceLastUpdate属性。

  • 使用GLKit视图和视图控制器绘制和动画OpenGL ES内容

- (void)viewDidLoad {
    [super viewDidLoad];
 
    // Create an OpenGL ES context and assign it to the view loaded from storyboard
    GLKView *view = (GLKView *)self.view;
    view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
 
    // Set animation frame rate
    self.preferredFramesPerSecond = 60;
 
    // Not shown: load shaders, textures and vertex arrays, set up projection matrix
    [self setupGL];
}

- (void)update {
    _rotation += self.timeSinceLastUpdate * M_PI_2; // one quarter rotation per second
 
    // Set up transform matrices for the rotating planet
    GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(_rotation, 0.0f, 1.0f, 0.0f);
    _normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
    _modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix, modelViewMatrix);
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    // Clear the framebuffer
    glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
    // Set shader uniforms to values calculated in -update
    glUseProgram(_diffuseShading);
    glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m);
    glUniformMatrix3fv(_uniformNormalMatrix, 1, 0, _normalMatrix.m);
 
    // Draw using previously configured texture and vertex array
    glBindTexture(GL_TEXTURE_2D, _planetTexture);
    glBindVertexArrayOES(_planetMesh);
    glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT, 0);
}

GLKit的API说明

一、GLKit 纹理加载

① GLKTextureInfo创建OpenGL纹理信息
name : OpenGL 上下⽂中纹理名称
target : 纹理绑定的目标
height : 加载的纹理高度
width : 加载纹理的宽度
textureOrigin : 加载纹理中的原点位置
alphaState: 加载纹理中alpha分量状态
containsMipmaps: 布尔值,加载的纹理是否包含mip贴图
② GLTextureLoader 简化从各种资源文件中加载纹理
  • 初始化
- initWithSharegroup: 初始化⼀个新的纹理加载到对象中
- initWithShareContext: 初始化⼀个新的纹理加载对象
  • 从文件中加载处理
 + textureWithContentsOfFile:options:errer: 从⽂件加载2D纹理图像并从数据中
创建新的纹理
 + textureWithContentsOfFile:options:queue:completionHandler: 从文件中异步
加载2D纹理图像,并根据数据创建新纹理
  • 从URL加载纹理
 + textureWithContentsOfURL:options:error: 从URL加载2D纹理图像并从数据创建新纹理
 + textureWithContentsOfURL:options:queue:completionHandler: 从URL异步加载2D纹理图像,并根据数据创建新纹理
  • 从内存中表示创建纹理
+ textureWithContentsOfData:options:errer: 从内存空间加载2D纹理图像,并根据数据创建新纹理
- textureWithContentsOfData:options:queue:completionHandler:从内存空间异步加载2D纹理图像,并从数据中创建新纹理
  • 从CGImages创建纹理
- textureWithCGImage:options:error: 从Quartz图像 加载2D纹理图像并从数据创建新纹理
- textureWithCGImage:options:queue:completionHandler: 从Quartz图像异步加载2D纹理图像,并根据数据创建新纹理
  • 从URL加载多维创建纹理
+ cabeMapWithContentsOfURL:options:errer: 从单个URL加载⽴方体贴图纹理图像,并根据数据创建新纹理
- cabeMapWithContentsOfURL:options:queue:completionHandler:从单个URL异步加载⽴方体贴图纹理图像,并根据数据创建新纹理

  • 从文件加载多维数据创建纹理
+ cubeMapWithContentsOfFile:options:errer: 从单个文件加载⽴方体贴图纹理对象,并从数据中创建新纹理
- cubeMapWithContentsOfFile:options:queue:completionHandler:从单个文件异步加载⽴方体贴图纹理对象,并从数据中创建新纹理
+ cubeMapWithContentsOfFiles:options:errer: 从⼀系列文件中加载⽴方体贴图纹理图像,并从数据总创建新纹理
- cubeMapWithContentsOfFiles:options:options:queue:completionHandler:从⼀系列⽂件异步加载⽴方体贴图纹理图像,并从数据中创建新纹理

二、GLKView 使⽤OpenGL ES 绘制内容的视图默认实现

  • 初始化
 - initWithFrame:context: 初始化新视图
  • 代理
delegate 视图的代理
  • 配置帧缓存区对象
drawableColorFormat 颜⾊色渲染缓存区格式
drawableDepthFormat 深度渲染缓存区格式
drawableStencilFormat 模板渲染缓存区的格式
drawableMultisample 多重采样缓存区的格式
  • 帧缓存区属性
drawableHeight 底层缓存区对象的高度(以像素为单位)
drawableWidth 底层缓存区对象的宽度(以像素为单位)
  • 绘制视图的内容
context 绘制视图内容时使用的OpenGL ES上下⽂
 - bindDrawable 将底层FrameBuffer对象绑定到OpenGL ES
enableSetNeedsDisplay 布尔值,指定视图是否响应使得视图内容无效的消息
 - display ⽴即重绘视图内容
snapshot 绘制视图内容并将其作为新图像对象返回
  • 删除视图FrameBuffer对象
- deleteDrawable 删除与视图关联的可绘制对象

三、GLKViewDelegate ⽤于 GLKView 对象回调⽅法

- glkView:drawInRect: 绘制视图内容 (必须实现代理)

四、GLKViewController 管理 OpenGL ES 渲染循环的视图控制器

  • 更新
- (void) update 更新视图内容
- (void) glkViewControllerUpdate:
  • 配置帧速率
preferredFramesPerSecond 视图控制器调用视图以及更新视图内容的速率
framesPerSencond 视图控制器调⽤视图以及更新其内容的实际速率
  • 配置GLKViewController代理
delegate 视图控制器的代理
  • 控制帧更新
paused 布尔值,渲染循环是否已暂停
pausedOnWillResignActive 布尔值,当前程序重新激活动状态时视图控制器是否⾃动暂停渲染循环
resumeOnDidBecomeActive 布尔值,当前程序变为活动状态时视图控制是否自动恢复呈现循
  • 获取有关View更新信息
frameDisplayed 视图控制器自创建以来发送的帧更新数
timeSinceFirstResume ⾃视图控制器第一次恢复发送更新事件以来经过的时间量
timeSinceLastResume 自上次视图控制器恢复发送更新事件以来更更新的时间量
timeSinceLastUpdate ⾃上次视图控制器调用委托方法以及经过的时间量
glkViewControllerUpdate:
timeSinceLastDraw ⾃上次视图控制器调用视图display方法以来经过的时间量

五、GLKViewControllerDelegate 渲染循环回调⽅法

  • 处理更新事件
- glkViewControllerUpdate: 在显示每个帧之前调⽤
  • 暂停/恢复通知
- glkViewController:willPause: 在渲染循环暂停或恢复之前调⽤

六、GLKBaseEffect 一种简单光照/着色系统,⽤于基于着色器OpenGL渲染

  • 命名Effect
label 给Effect(效果)命名
  • 配置模型视图转换
transform 绑定效果时应⽤于顶点数据的模型视图,投影和纹理变换
  • 配置光照效果
lightingType ⽤于计算每个片段的光照策略,GLKLightingType
GLKLightingType
    GLKLightingTypePerVertex 表示在三角形中每个顶点执行光照计算,然后在三角形进⾏插值
    GLKLightingTypePerPixel 表示光照计算的输入在三角形内插入,并且在每个片段执⾏光照计算
  • 配置光照
lightModelTwoSided 布尔值,表示为基元的两侧计算光照
material 计算渲染图元光照使用的材质属性
lightModelAmbientColor 环境颜色,应⽤效果渲染的所有图元.
light0 场景中第⼀个光照属性
light1 场景中第二个光照属性 
light2 场景中第三个光照属性
  • 配置纹理
texture2d0 第一个纹理属性 
texture2d1 第⼆个纹理属性
textureOrder 纹理应用于渲染图元的顺序
  • 配置雾化
fog 应⽤于场景的雾属性
  • 配置颜色信息
colorMaterialEnable 布尔值,表示计算光照与材质交互时是否使用颜⾊顶点属性
useConstantColor 布尔值,指示是否使⽤用常量颜色
constantColor 不提供每个顶点颜⾊数据时使⽤常量颜⾊
  • 准备绘制
- prepareToDraw 准备渲染效果

示例代码

简单渲染加载一张jpg/png图片:

一、OpenGL ES 初始化

① 初始化上下文并设置当前上下文
    /* 初始化上下文并设置当前上下文
     * EAGLContext 是苹果iOS平台下实现OpenGLES 渲染层
     * kEAGLRenderingAPIOpenGLES1 = 1, 固定管线
     * kEAGLRenderingAPIOpenGLES2 = 2,
     * kEAGLRenderingAPIOpenGLES3 = 3,
     */
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    // 判断是否创建成功
    if (!context) {
        NSLog(@"Creat failed");
    }
    // 设置上下文
    [EAGLContext setCurrentContext:context];
② 配置视图创建的渲染缓存区
  • OpenGL ES 有一个缓存区,它用以存储将在屏幕中显示的颜色,可以使用其属性来设置缓冲区中的每个像素的颜色格式。drawableColorFormat: 颜色缓存区格式:
    GLKViewDrawableColorFormatRGBA8888 = 0;默认缓存区的每个像素的最小组成部分(RGBA)使用8个bit(故每个像素4个字节,4*8个bit)GLKViewDrawableColorFormatRGB565:如果APP允许更小范围的颜色,即可设置这个,会让APP消耗更小的资源(内存和处理时间)

  • drawableDepthFormat: 深度缓存区格式
    GLKViewDrawableDepthFormatNone = 0,意味着完全没有深度缓冲区
    GLKViewDrawableDepthFormat16,
    GLKViewDrawableDepthFormat24,
    如果要使用这个属性(一般用于3D游戏),应该选择GLKViewDrawableDepthFormat16
    或GLKViewDrawableDepthFormat24,差别就是使用GLKViewDrawableDepthFormat16将 消耗更少的资源。

    // 设置上下文
    [EAGLContext setCurrentContext:context];
    // 获取GLKView 设置context
    GLKView *view = (GLKView *)self.view;
    view.context = context;
    
    // 配置视图创建的渲染缓存区
    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;

二、加载顶点/纹理坐标数据

① 创建顶点数据并开辟顶点缓存区
  • 顶点数组:可以选择设定函数指针,在调用绘制方法的时候,直接由内存传入顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组。
  • 顶点缓存区: 性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中,这部分的显存,就被称为顶点缓冲区。
    /* 设置顶点数组(顶点坐标,纹理坐标)
     * 纹理坐标系取值范围[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, // 左下
    };
    
    /* 顶点数组:可以选择设定函数指针,在调用绘制方法的时候,直接由内存传入顶点数据
     * 也就是说这部分数据之前是存储在内存当中的,被称为顶点数组
     *
     * 顶点缓存区: 性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中
     * 这部分的显存,就被称为顶点缓冲区
     */
    
    // 开辟顶点缓存区
    GLuint bufferID;
    // 创建顶点缓存区标识符ID
    glGenBuffers(1, &bufferID);
    // 绑定顶点缓存区
    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    // 将顶点数组的数据copy到顶点缓存区中(GPU显存中)
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
② 打开读取通道
  • 在iOS中,默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的。意味着,顶点数据在着色器端(服务端)是不可用的, 即使已经使用glBufferData方法,将顶点数据从内存拷贝到顶点缓存区中(GPU显存中)。
  • 必须由glEnableVertexAttribArray 方法打开通道,指定访问属性,才能让顶点着色器能够访问到从CPU复制到GPU的数据。
  • 注意:数据在GPU端是否可见,即着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。
    /* glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
     * 功能:上传顶点数据到显存的方法(设置合适的方式从buffer里面读取数据)
     * index:指定要修改的顶点属性的索引值
     * size:每次读取数量(如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a),纹理则是2个)
     * type:指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。
     * normalized:指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)
     * stride:指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0
     * ptr:指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0
     */

    // 顶点坐标数据
    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);


    

三、加载纹理数据(使用GLBaseEffect)

    // 获取纹理图片路径
    NSString *name = [[NSBundle mainBundle] pathForResource:@"祎祎美女" ofType:@"jpg"];
    // 设置纹理参数:纹理坐标原点是左下角,但是图片显示原点应该是左上角
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:name options:options error:nil];
    // 使用苹果GLKit 提供GLKBaseEffect 完成着色器工作(顶点/片元)
    effect = [[GLKBaseEffect alloc] init];
    effect.texture2d0.enabled = GL_TRUE;
    effect.texture2d0.name = textureInfo.name;

四、实现GLKViewDelegate协议

/* 绘制视图的内容
 * GLKView对象使其OpenGL ES上下文成为当前上下文,并将其framebuffer绑定为OpenGL ES呈现命令的目标,然后委托方法应该绘制视图的内容
 */
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    glClear(GL_COLOR_BUFFER_BIT);
    
    [effect prepareToDraw];
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

五、解决图片拉伸问题

  • 我们的代码明明看起来是绘制了一个正方形来加载图片,为什么最后的效果,图片却被拉伸变形了呢?这是由于视口(视图)宽高比的问题产生了拉伸问题。
  • 为了解决这个问题,可以设置透视投影矩阵。又由于可视范围的问题,我们需要将顶点向后移4.0单位。
    // 透视投影矩阵
    CGFloat aspect = fabs(self.view.bounds.size.width / self.view.bounds.size.height);
    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 0.1, 100.0);
    effect.transform.projectionMatrix = projectionMatrix;
    // 向后(屏幕里面)平移4.0
    GLKMatrix4 modelviewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -4.0);
    effect.transform.modelviewMatrix = modelviewMatrix;

六、效果展示

OpenGL ES之GLKit的使用功能和API说明_第3张图片

七、完整代码传送门

OpenGL ES之加载一张图片的简单实现

你可能感兴趣的:(iOS高级进阶,Swift高级进阶,OpenGL,ES,GLKView,动画循环,渲染纹理,GLKBaseEffect,循环渲染)