关于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主要提供了以下功能:
- Texture loading提供纹理加载功能,允许加载各种纹理,最多可以使用3个纹理渲染图形
- Math libraries提供提供高效的数学运算功能,主要是向量的矩阵的运算
-
Effects提供常用的着色器包含
GLKBaseEffect
、GLKReflectionMapEffect
、GLKSkyboxEffect
3种 - 提供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可以为我们代理以下便利:
- 不需要我们自己创建RenderBuffer和FrameBuffer
- 不需要我们自己手动加载纹理,直接使用GLKTextureInfo就可以加载纹理
- 不需要我们使用glsl编写顶点着色器和片元着色器,使用GLKBaseEffect,GLKBaseEffect提供了顶点、纹理坐标、纹理、光照、矩阵等常见属性通道
- 数学库提供了便利的矩阵生成方法
对于一些简单的效果,我们完全可以使用GLKit去实现,当然,一些复杂的效果仍然需要我们自己编写自定义着色器去实现,这是后话。
网友评论