美文网首页OpenGL
GLKit绘制旋转立方体

GLKit绘制旋转立方体

作者: iOSer_jia | 来源:发表于2020-08-11 15:15 被阅读0次

    关于GLKit

    在本文开始之前,我们先看下苹果官方文档对GLKit的介绍。

    Speed up OpenGL ES or OpenGL app development. Use math libraries, background texture loading, pre-created shader effects, and a standard view and view controller to implement your rendering loop.

    可以得知,GLKit预先使用OpenGL ES创建了一些着色器效果,开发者可以通过GLKit实现一些效果,而不用自己编写着色器。GLKit主要提供了以下功能:

    1. Texture loading提供纹理加载功能,允许加载各种纹理,最多可以使用3个纹理渲染图形
    2. Math libraries提供提供高效的数学运算功能,主要是向量的矩阵的运算
    3. Effects提供常用的着色器包含GLKBaseEffectGLKReflectionMapEffectGLKSkyboxEffect3种
    4. 提供Views and View Controllers让开发者更方便快捷的创建绘制一个OpenGL ES界面

    本文将通过一个简单demo初步体验GLKit的功能。

    GLKView

    GLKView是GLKit提供给开发着的一个可以作为OpenGL ES绘制结果展示的界面,它需要一个上下文(context)来将GLKView和绘制内容联系起来,同时,GLKit也提供了drawableColorFormat和drawableDepthFormat设置颜色格式和深度格式。

    if let context = EAGLContext(api: .openGLES3) {
        myContext = context
        let vWidth = UIScreen.main.bounds.size.width
        EAGLContext.setCurrent(context)
                
        glkView = GLKView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: vWidth, height: vWidth)), context: context)
        glkView?.center = view.center
                
        glkView?.backgroundColor = .clear
        glkView?.delegate = self
                
        glkView?.drawableColorFormat = .RGBA8888
        glkView?.drawableDepthFormat = .format24
                
        glDepthRangef(1, 0)
                
        view.addSubview(glkView!)
                
    }
    

    GLKView有个GLKViewDelegate代理,代理只有一个方法- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;,我们可以在这个方法中执行绘制。

    准备顶点数据

    尽管GLKit为开发者封装了很多方法避免我们直接面对OpenGL ES的api,但是,顶点数据仍然需要我们自己保存到顶点缓冲区。

    // 顶点数组
    let vertexes: [GLfloat] = [
        // front
        -0.5, -0.5, 0.5, 0, 0, 0, 0, 1, // (分别为顶点x, y, z 坐标, 纹理s, t坐标,法线x, y, z坐标)
        0.5, -0.5, 0.5, 1, 0, 0, 0, 1,
        0.5, 0.5, 0.5, 1, 1, 0, 0, 1,
        -0.5, 0.5, 0.5, 0, 1, 0, 0, 1,
        // back
        0.5, -0.5, -0.5, 0, 0, 0, 0, -1,
        -0.5, -0.5, -0.5, 1, 0, 0, 0, -1,
        -0.5, 0.5, -0.5, 1, 1, 0, 0, -1,
        0.5, 0.5, -0.5, 0, 1, 0, 0, -1,
        // up
        -0.5, 0.5, 0.5, 0, 0, 0, 1, 0,
        0.5, 0.5, 0.5, 1, 0, 0, 1, 0,
        0.5, 0.5, -0.5, 1, 1, 0, 1, 0,
        -0.5, 0.5, -0.5, 0, 1, 0, 1, 0,
        // down
        -0.5, -0.5, -0.5, 0, 0, 0, -1, 0,
        0.5, -0.5, -0.5, 1, 0, 0, -1, 0,
        0.5, -0.5, 0.5, 1, 1, 0, -1, 0,
        -0.5, -0.5, 0.5, 0, 1, 0, -1, 0,
        // left
        -0.5, -0.5, -0.5, 0, 0, -1, 0, 0,
        -0.5, -0.5, 0.5, 1, 0, -1, 0, 0,
        -0.5, 0.5, 0.5, 1, 1, -1, 0, 0,
        -0.5, 0.5, -0.5, 0, 1, -1, 0, 0,
        // right
        0.5, -0.5, 0.5, 0, 0, 1, 0, 0,
        0.5, -0.5, -0.5, 1, 0, 1, 0, 0,
        0.5, 0.5, -0.5, 1, 1, 1, 0, 0,
        0.5, 0.5, 0.5, 0, 1, 1, 0, 0,
    ]
    
    // 声明一个缓冲区标示        
    var bufferID: GLuint = 0
    // 申请一个缓冲区
    glGenBuffers(1, &bufferID)
    // 绑定顶点缓冲区
    glBindBuffer(GLenum(GL_ARRAY_BUFFER), bufferID)
    // 保存数据到顶点缓冲区
    glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size*vertexes.count, vertexes, GLenum(GL_STATIC_DRAW));
    
    // 下面代码为往着色器传递数据
    
    // 打开顶点attribute通道
    glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
    // 传递顶点数据
    glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue),
                                  3,
                                  GLenum(GL_FLOAT),
                                  GLboolean(GL_FALSE),
                                  GLsizei(MemoryLayout<GLfloat>.size*8),
                                  nil)
    // 打开纹理坐标attribute通道
    glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
    // 传递纹理坐标数据
    glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue),
                                  2,
                                  GLenum(GL_FLOAT),
                                  GLboolean(GL_FALSE),
                                  GLsizei(MemoryLayout<GLfloat>.size*8),
                                  UnsafePointer(bitPattern:MemoryLayout<GLfloat>.size*3))
                                  
    // 打开法线attribute通道
    glEnableVertexAttribArray(GLuint(GLKVertexAttrib.normal.rawValue))
    // 传递法线数据
    glVertexAttribPointer(GLuint(GLKVertexAttrib.normal.rawValue),
                                  3,
                                  GLenum(GL_FLOAT),
                                  GLboolean(GL_FALSE),
                                  GLsizei(MemoryLayout<GLfloat>.size*8),
                                  UnsafePointer(bitPattern: MemoryLayout<GLfloat>.size*5))
    

    相比于使用OC,swift使用C的函数会遇到很多阻力,我们需要使用MemoryLayout拿到GLfloat占用的空间大小,而使用UnsafePointer拿到指针对象。

    纹理数据

    GLKit封装了一个Effect类,我们可以将Effect认为是苹果为我们封装好的着色器,我们可以使用Effect很方便给着色器传递纹理、光照、投影矩阵、模型试图矩阵等数据。
    对于当前案例,我们可以使用GLKBaseEffect作为着色器,GLKBaseEffect可以传递两个纹理、三个光照,可以满足一些常见的业务开发需求。

    // 获取图片
    guard let image = UIImage(named: "kunkun")?.cgImage else {
        print("failed")
        
        return
    }
    
    do {
        // 使用GLKTextureInfo加载纹理
        // GLKTextureInfo是用工厂方法GLKTextureLoader.texture()获得的
        let textInfo = try GLKTextureLoader.texture(with: image, options: [GLKTextureLoaderOriginBottomLeft: true])
        
        // 传递到GLKBaseEffect
        myEffect = GLKBaseEffect()
        myEffect?.texture2d0.enabled = GLboolean(GL_TRUE)
        myEffect?.texture2d0.name = textInfo.name
        myEffect?.texture2d0.target = GLKTextureTarget(rawValue: textInfo.target)!
        
        // 使用光照
        myEffect?.light0.enabled = GLboolean(GL_TRUE)
        myEffect?.light0.position = GLKVector4(v: (-0.5, -0.5, 5.0, 1.0))
        myEffect?.light0.diffuseColor = GLKVector4(v: (1, 1, 1, 1))
    } catch let err {
        print("error: \(err)")
    }
    
    

    传递矩阵

    立方体的旋转可以使用传入矩阵来实现,effect也提供了相关的接口,另外GLKit也提供了生成矩阵的方法。

    // 实现一个定时器并在定时器方法中执行这段代码
    // 具体定时器创建不具体展示了
    
    // 旋转角度
    angle = (angle + 1) % 360
    // 传递模型试图矩阵
    myEffect?.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(Float(angle)), 0.4, 0.3, 0.3)
    // 开始绘制
    self.glkView?.display()
    

    绘制

    具体绘制代码我们在代理方法glkView(_ view: GLKView, drawIn rect: CGRect)中实现

    override func glkView(_ view: GLKView, drawIn rect: CGRect) {
        // 设置清屏颜色
        glClearColor(1, 1, 1, 1)
        // 清空颜色缓冲区和深度缓冲区
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT))
        // 开启深度测试
        glEnable(GLenum(GL_DEPTH_TEST))
        // 准备绘制
        myEffect?.prepareToDraw()
        // 开始绘制
        for i in 0..<6 {
            glDrawArrays(GLenum(GL_TRIANGLE_FAN), GLint(4*i), GLsizei(4))
        }
    }
    

    最终效果

    最终效果.gif

    总结

    对比使用OpenGL ES的api,GLKit可以为我们代理以下便利:

    1. 不需要我们自己创建RenderBuffer和FrameBuffer
    2. 不需要我们自己手动加载纹理,直接使用GLKTextureInfo就可以加载纹理
    3. 不需要我们使用glsl编写顶点着色器和片元着色器,使用GLKBaseEffect,GLKBaseEffect提供了顶点、纹理坐标、纹理、光照、矩阵等常见属性通道
    4. 数学库提供了便利的矩阵生成方法

    对于一些简单的效果,我们完全可以使用GLKit去实现,当然,一些复杂的效果仍然需要我们自己编写自定义着色器去实现,这是后话。

    相关文章

      网友评论

        本文标题:GLKit绘制旋转立方体

        本文链接:https://www.haomeiwen.com/subject/yhhydktx.html