OpenGL

GLKit绘制旋转立方体

2020-08-11  本文已影响0人  iOSer_jia

关于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去实现,当然,一些复杂的效果仍然需要我们自己编写自定义着色器去实现,这是后话。

上一篇 下一篇

猜你喜欢

热点阅读