OpenGL-ES-案例01:GLKit加载图片

1,299 阅读8分钟

本案例的目的在于熟悉GLKit框架的使用,如果还有不了解的,可以看看这篇文章二、GLKit 及 常见API

整体效果图如下:

image

准备工作

  • 创建一个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

下面来讲讲如何使用这个框架加载图片 整体的流程图如下

image

主要有以下几步:

  • setupConfig函数: OpenGL ES 相关初始化,主要是初始化上下文及GLKView对象
  • setupVertex函数:设置顶点数据,包括顶点坐标和纹理坐标等
  • setupTexture函数:设置纹理
  • GLKViewDelegate代理方法:将纹理绘制到屏幕上

注:代码分为OC和swift两个版本

setupConfig函数

该函数主要是初始化上下文、设置GLKView视图以及设置背景色,整体的流程如下

image

根据流程图所示,分为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

配置相应缓冲区

主要是配置颜色缓存区和深度缓存区

颜色缓存区格式

通过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

image

主要分为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 完成着色器工作(顶点/片元)

image

分为以下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的代理方法时必须实现的,在这个方法中绘制视图的内容

image

  • 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)    }

完整的代码见github - 08_GLKit_Image_OC、08_GLKit_Image_Swift,分别提供了OC和Swift两个版本