概述
-
GLKit
框架是为了简化基于OpenGL
/OpenGL ES
的应用开发。 - 它内置数学方法和各种功能,其中的
Effect
有一点点固定管线的感觉。 - 使用体感是面向对象,底层仍然是面向过程。
案例一:绘制一张图片
导头
import UIKit
import GLKit
import OpenGLES.ES3.gl
import OpenGLES.ES3.glext
// 忽略警告
@available(iOS,deprecated:1.0)
UIViewController 改为 GLKViewController
// 使用 GLKViewController
class ViewController: GLKViewController {
View 改为 GLKView
[图片上传失败...(image-de7578-1596009288359)]
定义 EAGLContext 和 GLKBaseEffect
// 上下文
var context: EAGLContext?
// 类似 OpenGL里的固定管线
var effect: GLKBaseEffect?
viewDidLoad() 3个准备方法
override func viewDidLoad() {
super.viewDidLoad()
// 1. 准备好 context 和 glkView
setupConfig()
// 2. 顶点数据 => 顶点缓冲区 => 顶点着色器
setupVertexData()
// 3. 图片文件 => 纹理 => effect
setupTexture()
// (4.) 代理方法里 设置绘制
}
setupConfig()
func setupConfig() {
// 1. 初始化上下文
context = EAGLContext(api: .openGLES3)
guard let _context = context else {
print("Initial ES context Failed!!")
return
}
// 2. 可以有多个上下文,但只能有一个当前上下文
EAGLContext.setCurrent(_context)
// 3. 获取 GLKView
guard let glkView = view as? GLKView else {
print("Convert UIView to GLKView Failed!!")
return
}
// view 绑定上下文
glkView.context = _context
// 颜色缓冲区格式
glkView.drawableColorFormat = .RGBA8888
// 深度缓冲区格式 (可以选16位)
glkView.drawableDepthFormat = .format24
// 4. 设置背景颜色 橘色,这里也能用 glkView.backgroundColor
glClearColor(1.0, 0.4, 0.0, 1.0)
}
setupVertexData()
func setupVertexData() {
// 1. 顶点数据,包含 顶点坐标(xyz) 和 纹理坐标(st)
// 之后载入照片用到2个三角形,共6个顶点
let vertextData: [GLfloat] = [
0.8, -0.2, 0.0, 1.0, 0.0,
0.8, 0.2, 0.0, 1.0, 1.0,
-0.8, 0.2, 0.0, 0.0, 1.0,
// x y z s t
0.8, -0.2, 0.0, 1.0, 0.0,
-0.8, 0.2, 0.0, 0.0, 1.0,
-0.8, -0.2, 0.0, 0.0, 0.0,
]
// 2. 创建 缓冲区(拿房间号)
var bufferID = GLuint()
glGenBuffers(1, &bufferID)
// 3. 绑定 顶点缓冲区(指定用途,还能指定为 深度缓冲区等)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), bufferID)
// 4. copy 内存 => 显存(顶点缓冲区)
glBufferData(
GLenum(GL_ARRAY_BUFFER), //copy到哪:顶点缓冲区
MemoryLayout<GLfloat>.stride * vertextData.count, //空间大小
vertextData, //内容
GLenum(GL_STATIC_DRAW)) //用途:绘制
// 5.1. 打开通道 以使用缓冲区数据
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
// 5.2. 顶点着色器(顶点坐标) 读取 <= 顶点缓冲区
glVertexAttribPointer(
GLuint(GLKVertexAttrib.position.rawValue), //传给谁:顶点position
3, //position 由 xyz 组成
GLenum(GL_FLOAT), //数据类型
GLboolean(GL_FALSE), //是否需要归一化
GLsizei(MemoryLayout<GLfloat>.stride * 5), //每次读取的步长
UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.stride * 0)) //指针起始偏移
// 6.1. 打开通道 以使用缓冲区数据
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
// 6.2. 顶点着色器(纹理坐标) 读取 <= 顶点缓冲区
glVertexAttribPointer(
GLuint(GLKVertexAttrib.texCoord0.rawValue), //传给谁:顶点position
2, //tex 由 st 组成
GLenum(GL_FLOAT), //数据类型
GLboolean(GL_FALSE), //是否需要归一化
GLsizei(MemoryLayout<GLfloat>.stride * 5), //每次读取的步长
UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.stride * 3)) //指针起始偏移
}
setupTexture()
func setupTexture() {
// 1. 图片路径
guard let filePath = Bundle.main.path(forResource: "pcr", ofType: "jpg") else {
print("load image failed!!")
return
}
// 2.1. 由于纹理坐标原点为左下角,图片显示原点为左上角,需要设置 (否则是倒的)
let options = [GLKTextureLoaderOriginBottomLeft : NSNumber(booleanLiteral: true)]
// 2.2. 图片 转 纹理
guard let textureInfo = try? GLKTextureLoader.texture(withContentsOfFile: filePath, options: options) else {
print("convert image to texture failed!!")
return
}
// 3.1. 初始化 GLKBaseEffect
effect = GLKBaseEffect()
guard let _effect = effect else {
print("Initial GLKBaseEffect failed!!")
return
}
// 3.2. 打开 effect 的纹理通道
_effect.texture2d0.enabled = GLboolean(GL_TRUE)
// 3.3. 绑定 纹理
_effect.texture2d0.name = textureInfo.name
}
代理方法 glkView(_: , drawIn: ) 设置绘制
- 相当于
OpenGL
的RenderScene()
// MARK:- GLKViewDelegate
override func glkView(_ view: GLKView, drawIn rect: CGRect) {
// 1. 清空 颜色缓冲区
glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
guard let _effect = effect else {
print("Initial GLKBaseEffect failed!!")
return
}
// 2. 准备绘制
_effect.prepareToDraw()
// 3. 绘制:三角形,从第0个顶点开始,共6个顶点
glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)
}
案例二:立方体旋转
导头、改
GLKView
和GLKViewController
与案例一是一样的,不再赘述。关注注释:基本上只注释案例一里没有的代码。
基本的声明定义
// 顶点数据 结构体
struct MDVertex {
let positionCoord: GLKVector3
let textureCoord: GLKVector2
let normalVec: GLKVector3
}
@available(iOS,deprecated: 12.0)
class ViewController: GLKViewController {
var context: EAGLContext!
var effect: GLKBaseEffect!
// 顶点数据 数组
var vertexes: [MDVertex]!
// 顶点缓冲区ID
var vertexBufferID: GLuint!
// 记录旋转角度
var angle: Int = 0
// 计时器
var displayLink: CADisplayLink!
deinit 释放
deinit {
if EAGLContext.current() == context {
EAGLContext.setCurrent(nil)
}
if let _ = vertexBufferID {
glDeleteBuffers(1, &vertexBufferID)
vertexBufferID = 0
}
displayLink.invalidate()
}
viewDidLoad() 2个准备方法
override func viewDidLoad() {
super.viewDidLoad()
// 1.1. 准备好 context 和 glkView
// 1.2. 顶点数据 => 顶点缓冲区 => 顶点着色器
// 1.3. 图片文件 => 纹理 => effect
commonInit()
// 2. 计时器,做旋转动画:调用的 update()方法 改变effect的 MV矩阵并重绘
addCADisplayLink()
// (3.) 代理方法里 设置绘制
}
commonInit()
- 相当于案例一的3个准备方法。
func commonInit() {
// context / glkView 的一系列操作
context = EAGLContext(api: .openGLES3)
assert(context != nil, "Initialize context failed!!")
EAGLContext.setCurrent(context)
guard let glkView = view as? GLKView else { return }
glkView.context = context
glkView.drawableColorFormat = .RGBA8888
// 启用 深度缓冲区
glkView.drawableDepthFormat = .format24
glkView.backgroundColor = .clear
// 深度缓冲区 默认(0, 1),这里相当于翻转Z轴,使正方形朝屏幕外
glDepthRangef(1, 0)
// 加载纹理的一系列操作
guard let filePath = Bundle.main.path(forResource: "pcr", ofType: "jpg") else { return }
guard let cgImage = UIImage(contentsOfFile: filePath)?.cgImage else { return }
let options = [GLKTextureLoaderOriginBottomLeft : NSNumber(booleanLiteral: true)]
guard let textureInfo = try? GLKTextureLoader.texture(with: cgImage, options: options) else { return }
// effect
effect = GLKBaseEffect()
// effect 设置纹理
effect.texture2d0.name = textureInfo.name
// effect 设置光照
effect.light0.enabled = GLboolean(GL_TRUE)
effect.light0.diffuseColor = GLKVector4Make(1, 1, 1, 1)
effect.light0.position = GLKVector4Make(0.5, 0, 5, 1)
// 顶点坐标
let ver1 = GLKVector3Make(-0.5, 0.5, 0.5)
let ver2 = GLKVector3Make(-0.5, -0.5, 0.5)
let ver3 = GLKVector3Make( 0.5, 0.5, 0.5)
let ver4 = GLKVector3Make( 0.5, -0.5, 0.5)
let ver5 = GLKVector3Make( 0.5, 0.5, -0.5)
let ver6 = GLKVector3Make(-0.5, 0.5, -0.5)
let ver7 = GLKVector3Make( 0.5, -0.5, -0.5)
let ver8 = GLKVector3Make(-0.5, -0.5, -0.5)
// 纹理坐标
let tex1 = GLKVector2Make(0, 1)
let tex2 = GLKVector2Make(0, 0)
let tex3 = GLKVector2Make(1, 1)
let tex4 = GLKVector2Make(1, 0)
// 法线向量
let normal1 = GLKVector3Make( 0, 0, 1)
let normal2 = GLKVector3Make( 0, 1, 0)
let normal3 = GLKVector3Make( 0,-1, 0)
let normal4 = GLKVector3Make(-1, 0, 0)
let normal5 = GLKVector3Make( 1, 0, 0)
let normal6 = GLKVector3Make( 0, 0,-1)
// 用于初始化的随便取值的顶点数据
let meanlessVertex =
MDVertex(positionCoord: ver1, textureCoord: tex1, normalVec: normal1)
// 先用初始化来开辟内存空间
vertexes = [MDVertex].init(repeating: meanlessVertex, count: 36)
// 初始化36个顶点数据:每个三角形3个顶点,每个面2个三角形,一个立方体6个面,3x2x6 = 36
vertexes[00] = MDVertex(positionCoord: ver1, textureCoord: tex1, normalVec: normal1)
vertexes[01] = MDVertex(positionCoord: ver2, textureCoord: tex2, normalVec: normal1)
vertexes[02] = MDVertex(positionCoord: ver3, textureCoord: tex3, normalVec: normal1)
vertexes[03] = MDVertex(positionCoord: ver2, textureCoord: tex2, normalVec: normal1)
vertexes[04] = MDVertex(positionCoord: ver3, textureCoord: tex3, normalVec: normal1)
vertexes[05] = MDVertex(positionCoord: ver4, textureCoord: tex4, normalVec: normal1)
vertexes[06] = MDVertex(positionCoord: ver3, textureCoord: tex3, normalVec: normal2)
vertexes[07] = MDVertex(positionCoord: ver1, textureCoord: tex1, normalVec: normal2)
vertexes[08] = MDVertex(positionCoord: ver5, textureCoord: tex4, normalVec: normal2)
vertexes[09] = MDVertex(positionCoord: ver1, textureCoord: tex1, normalVec: normal2)
vertexes[10] = MDVertex(positionCoord: ver5, textureCoord: tex4, normalVec: normal2)
vertexes[11] = MDVertex(positionCoord: ver6, textureCoord: tex2, normalVec: normal2)
vertexes[12] = MDVertex(positionCoord: ver4, textureCoord: tex3, normalVec: normal3)
vertexes[13] = MDVertex(positionCoord: ver2, textureCoord: tex1, normalVec: normal3)
vertexes[14] = MDVertex(positionCoord: ver7, textureCoord: tex4, normalVec: normal3)
vertexes[15] = MDVertex(positionCoord: ver2, textureCoord: tex1, normalVec: normal3)
vertexes[16] = MDVertex(positionCoord: ver7, textureCoord: tex4, normalVec: normal3)
vertexes[17] = MDVertex(positionCoord: ver8, textureCoord: tex2, normalVec: normal3)
vertexes[18] = MDVertex(positionCoord: ver1, textureCoord: tex3, normalVec: normal4)
vertexes[19] = MDVertex(positionCoord: ver2, textureCoord: tex1, normalVec: normal4)
vertexes[20] = MDVertex(positionCoord: ver6, textureCoord: tex4, normalVec: normal4)
vertexes[21] = MDVertex(positionCoord: ver2, textureCoord: tex1, normalVec: normal4)
vertexes[22] = MDVertex(positionCoord: ver6, textureCoord: tex4, normalVec: normal4)
vertexes[23] = MDVertex(positionCoord: ver8, textureCoord: tex2, normalVec: normal4)
vertexes[24] = MDVertex(positionCoord: ver3, textureCoord: tex3, normalVec: normal5)
vertexes[25] = MDVertex(positionCoord: ver4, textureCoord: tex1, normalVec: normal5)
vertexes[26] = MDVertex(positionCoord: ver5, textureCoord: tex4, normalVec: normal5)
vertexes[27] = MDVertex(positionCoord: ver4, textureCoord: tex1, normalVec: normal5)
vertexes[28] = MDVertex(positionCoord: ver5, textureCoord: tex4, normalVec: normal5)
vertexes[29] = MDVertex(positionCoord: ver7, textureCoord: tex2, normalVec: normal5)
vertexes[30] = MDVertex(positionCoord: ver6, textureCoord: tex1, normalVec: normal6)
vertexes[31] = MDVertex(positionCoord: ver8, textureCoord: tex2, normalVec: normal6)
vertexes[32] = MDVertex(positionCoord: ver5, textureCoord: tex3, normalVec: normal6)
vertexes[33] = MDVertex(positionCoord: ver8, textureCoord: tex2, normalVec: normal6)
vertexes[34] = MDVertex(positionCoord: ver5, textureCoord: tex3, normalVec: normal6)
vertexes[35] = MDVertex(positionCoord: ver7, textureCoord: tex4, normalVec: normal6)
// 顶点缓冲区的一系列操作
vertexBufferID = GLuint()
glGenBuffers(1, &vertexBufferID)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBufferID)
// 顶点数据数组的大小 = 40x36 = 1440
let bufferSize: GLsizeiptr = MemoryLayout<MDVertex>.stride * vertexes.count
// copy顶点数组 内存 => 显存
glBufferData(
GLenum(GL_ARRAY_BUFFER),
bufferSize,
vertexes,
GLenum(GL_STATIC_DRAW))
/*
glVertexAttribPointer函数的最后一个参数,偏移值,要注意下!
MDVertex结构体 内含 GLKVector3,GLKVector2,GLKVector3
独立使用时:内存占用大小 GLKVector3 = 12,GLKVector2 = 8
但在结构体:内存占用大小 GLKVector3 = 16,GLKVector2 = 8
详情查阅 "字节对齐"
所以 MDVertex结构体 内存占用大小 = 16+8+16 = 40
*/
// 顶点
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
glVertexAttribPointer(
GLuint(GLKVertexAttrib.position.rawValue),
3,
GLenum(GL_FLOAT),
GLboolean(GL_FALSE),
GLsizei(MemoryLayout<MDVertex>.stride),
UnsafeRawPointer(bitPattern: 0))
// 纹理
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
glVertexAttribPointer(
GLuint(GLKVertexAttrib.texCoord0.rawValue),
2,
GLenum(GL_FLOAT),
GLboolean(GL_FALSE),
GLsizei(MemoryLayout<MDVertex>.stride),
UnsafeRawPointer(bitPattern: 16))
// 法线
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.normal.rawValue))
glVertexAttribPointer(
GLuint(GLKVertexAttrib.normal.rawValue),
3,
GLenum(GL_FLOAT),
GLboolean(GL_FALSE),
GLsizei(MemoryLayout<MDVertex>.stride),
UnsafeRawPointer(bitPattern: 16 + 8))
}
addCADisplayLink()
func addCADisplayLink() {
// 记录旋转角度
angle = 0
// 定时器调用方法 update
displayLink = CADisplayLink(target: self, selector: #selector(update))
// 加入到 Runloop
displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common)
//CADisplayLink 类似定时器,提供一个周期性调用.属于QuartzCore.framework中.
//具体可以参考该博客 https://www.cnblogs.com/panyangjun/p/4421904.html
}
update()
@objc
func update() {
// 1. 改变旋转角度
angle = (angle + 2) % 360
// 2. MV矩阵 = 绕 (0.3, 1, 0.7)轴线 旋转 angle角度(转弧度)
effect.transform.modelviewMatrix = GLKMatrix4MakeRotation(
GLKMathDegreesToRadians(Float(angle)),
0.3,
1,
0.7)
// 3. 重新渲染
if let glkView = view as? GLKView {
glkView.display()
}
}
代理方法 glkView(_: , drawIn: ) 设置绘制
// MARK:- GLKViewDelegate
override func glkView(_ view: GLKView, drawIn rect: CGRect) {
glEnable(GLenum(GL_DEPTH_TEST))
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
effect.prepareToDraw()
glClearColor(0.8, 0.8, 0.8, 1.0)
glDrawArrays(GLenum(GL_TRIANGLES), 0, GLsizei(vertexes.count))
}