OpenGL ES 入门:GLKit加载图片

简介: OpenGL ES 入门:GLKit加载图片

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


整体效果图如下:

微信图片_20220514165839.png


准备工作


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


下面来讲讲如何使用这个框架加载图片


整体的流程图如下

微信图片_20220514170056.png


整体流程图


主要有以下几步:


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

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


setupConfig函数


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

微信图片_20220514170229.png


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

微信图片_20220514170820.png

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

微信图片_20220514172217.png


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

微信图片_20220514172749.png

  • 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)
    }
相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
6月前
|
XML 小程序 Java
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
124 0
|
存储 编解码 算法
Opengl ES之LUT滤镜(上)
Opengl ES之连载系列
458 0
|
数据安全/隐私保护 开发者
OpenGL ES 多目标渲染(MRT)
Opengl ES连载系列
322 0
|
数据安全/隐私保护 索引
Opengl ES之纹理数组
Opengl ES连载系列
259 0
|
数据安全/隐私保护
Opengl ES之水印贴图
Opengl ES之连载系列
152 0
|
缓存 C++
Opengl ES之FBO
Opengl ES连载系列
151 0
|
Java 数据安全/隐私保护 Android开发
Opengl ES之矩阵变换(下)
Opengl ES连载系列
130 0
|
Java API 数据安全/隐私保护
Opengl ES之矩阵变换(上)
Opengl ES连载系列
148 0
|
存储
Opengl ES之踩坑记
Opengl ES之连载系列
137 0
|
存储 编解码 算法
Opengl ES之RGB转NV21
Opengl ES连载系列
156 0