原文地址: https://lm1024.xyz/archives/93
前面对 OpenGL ES3.0做了个基本的介绍,这节我们尝试着来写一个OpenGL ES 的 "hello word"程序 --- 画一个三角形
效果
EAGLContext 和 CAEAGLLayer
EAGLContext对象是管理OpenGL ES渲染上下文,若想使用OpenGL ES 进行绘制工作,则必须一个上下文对象。该对象管理者openGL ES 绘制使用的上下文对象
CAEAGLLayer 将OpenGL ES 渲染结果显示的屏幕上的layer (TODO:开一篇单独介绍)
流程
输入三角形顶点和色值 --> openGL 处理 --> CAEAGLLayer 显示
初始化 OpenGL 环境
var context: EAGLContext? = EAGLContext(api: .openGLES3)
var glLayer: CAEAGLLayer { return layer as! CAEAGLLayer }
open override class var layerClass: Swift.AnyClass {
return CAEAGLLayer.self
}
private func setupLlayer() {
glLayer.opacity = 1.0
glLayer.drawableProperties = [
kEAGLDrawablePropertyRetainedBacking: true,
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8,
]
}
private func setupContext() {
if context == nil {
context = EAGLContext(api: .openGLES2)
}
assert(context != nil && EAGLContext.setCurrent(context), "初始化环境失败")
}
初始化 frameBuffer 和 renderBuffer
为什么要用两个buffer,这个跟OpenGL 内部优化有关系(TODO:另外开一篇博客,写好了贴链接)
frameBuffer 负责管理buffer缓冲区的和显示buffer切换。
renderBuffer 负责将渲染结果推到CAEAGLLayer显示。
//初始化 frameBuff
private func setupFrameBuffer() {
deleteFrameBuff()
glGenFramebuffers(1, &frameBuffer)
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBuffer)
glFramebufferRenderbuffer(
GLenum(GL_FRAMEBUFFER),
GLenum(GL_COLOR_ATTACHMENT0),
GLenum(GL_RENDERBUFFER),
renderBuffer)
}
private func deleteFrameBuff() {
if frameBuffer != 0 {
glDeleteBuffers(1, &frameBuffer)
frameBuffer = 0
}
}
//初始化 renderBuffer
private func setupRenderBuffer() {
deleteRenderBuffer()
glGenRenderbuffers(1, &renderBuffer)
glBindRenderbuffer(GLenum(GL_RENDERBUFFER), renderBuffer)
context?.renderbufferStorage(Int(GL_RENDERBUFFER), from: glLayer)
}
private func deleteRenderBuffer() {
if renderBuffer != 0 {
glDeleteBuffers(1, &renderBuffer)
renderBuffer = 0
}
}
- glGenFramebuffers(_ n: GLsizei, _ framebuffers: UnsafeMutablePointer
!) 申请 n 个 frameBuffer 并返回申请的 frameBuffer的id列表 - glBindRenderbuffer(_ target: GLenum, _ renderbuffer: GLuint) 将申请到的 frameBuffer 绑定到 OpenGL ES 中
- glFramebufferRenderbuffer 将frameBuffer 和 renderbuffer关联
- glGenRenderbuffers:申请n个renderBuffer
- glBindRenderbuffer:将申请到的 renderBuffer 绑定到 OpenGL ES 中
- glDeleteBuffers: 删除指定的buffer
检查 frameBuffer 状态
private func checkFrameBuffer() throws -> Bool {
let status: GLenum = glCheckFramebufferStatus(GLenum(GL_FRAMEBUFFER))
var result: Bool = false
var errorMessage: String = ""
print("\(GLenum(GL_FRAMEBUFFER_UNSUPPORTED))," + "\(GLenum(GL_FRAMEBUFFER_COMPLETE))," + "\(GLenum(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT))," + "\(GLenum(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS)),")
switch status {
case GLenum(GL_FRAMEBUFFER_UNSUPPORTED):
errorMessage = "framebuffer不支持该格式"
result = false
case GLenum(GL_FRAMEBUFFER_COMPLETE):
print("frame buffer 创建成功")
result = true
case GLenum(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT):
errorMessage = "Framebuffer不完整 缺失组件"
result = false
case GLenum(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS):
errorMessage = "Framebuffer 不完整, 附加图片必须要指定大小"
result = false
default:
errorMessage = "未知错误"
result = false
}
if errorMessage.isEmpty == false {
let error: NSError = NSError(domain: "com.colin.error", code: Int(status), userInfo: ["errorMSG": errorMessage])
throw error
}
return result
}
编写shader脚本
- 顶点着色器脚本
attribute vec4 position;
attribute vec4 color;
varying vec4 colorVarying;
void main(void) {
colorVarying = color;
gl_Position = position;
}
- 片段着色器脚本
varying lowp vec4 colorVarying;
void main(void) {
gl_FragColor = colorVarying;
}
编译、链接shader脚本
open class ShaderCompiler{
open class func loadShader(_ vertPath:String,fragPath:String)throws -> GLuint {
var verShader:GLuint = 0,fragShader:GLuint = 0
do {
verShader = try self.compileShader(vertPath, type:GLenum(GL_VERTEX_SHADER))
fragShader = try self.compileShader(fragPath, type: GLenum(GL_FRAGMENT_SHADER))
//创建着色程序
let program:GLuint = glCreateProgram()
//装配 着色程序
glAttachShader(program, verShader)
glAttachShader(program, fragShader)
glLinkProgram(program)
programLog(GLenum(GL_LINK_STATUS), program: program)
return program
} catch let error {
print(error)
throw error
}
}
open class func compileShader(_ shaderPath:String,type:GLenum)throws -> GLuint{
do {
let shaderStr:String = try String.init(contentsOfFile: shaderPath)
let shaderStringUTF8 = shaderStr.cString(using:.utf8)
var shaderStringUTF8Pointer = UnsafePointer(shaderStringUTF8)
var shaderStringLength: GLint = GLint(Int32(shaderStr.count))
let shaderHandle:GLuint = glCreateShader(type)
glShaderSource(shaderHandle, 1,&shaderStringUTF8Pointer, &shaderStringLength)
glCompileShader(shaderHandle)
shaderLog(GLenum(GL_COMPILE_STATUS), shader: shaderHandle)
return shaderHandle
} catch let error {
print(error)
throw error
}
}
open class func programLog(_ statue:GLenum,program:GLuint) {
var linkSucc:GLint = 0
glGetProgramiv(program, GLenum(statue), &linkSucc)
if linkSucc == GL_FALSE
{
var message:Array = Array.init(repeating: 0, count: 256)
glGetProgramInfoLog(program, GLsizei(MemoryLayout.stride * message.count), nil, &message[0])
let msg = String.init(cString: message)
print("gl link program fail msg = \(msg)")
exit(1)
} else {
print("link ok")
}
}
open class func shaderLog(_ statue:GLenum,shader:GLuint){
var status:GLint = 0
glGetShaderiv(shader, statue, &status)
if status == GL_FALSE {
var message:Array = Array.init(repeating: 0, count: 256)
glGetShaderInfoLog(shader, GLsizei(MemoryLayout.stride * message.count), nil, &message[0])
let msg = String.init(cString: message)
print("glGetShaderiv shaderIngoLog type = \(statue) shader = \(shader) "+" \(msg)")
exit(1)
} else {
print("\(statue) \(shader) shader 操作成功")
}
}
}
初始化顶点坐标
//前三位为顶点坐标 后四位为顶点色值rgba
var vertices: [GLfloat] = [
-0.5, 0.5, 1.0, 1, 0, 0, 1,
-0.5, -0.5, 1.0, 0, 1, 0, 1,
0.5, -0.5, 1.0, 0, 0, 1, 1,
]
将顶点坐标推给OpenGL
private func setupVBOs() {
var vertexBuffer: GLuint = 0
glGenBuffers(1, &vertexBuffer)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)
glBufferData(GLenum(GL_ARRAY_BUFFER),
MemoryLayout.size * vertices.count,
vertices,
GLenum(GL_STATIC_DRAW))
}
渲染
private func render() {
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBuffer)
glBindRenderbuffer(GLenum(GL_RENDERBUFFER), renderBuffer)
glClearColor(0, 1, 1, 1)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
glViewport(0, 0, GLsizei(frame.width),GLsizei(frame.height))
glVertexAttribPointer(GLuint(self.positon),
3,
GLenum(GL_FLOAT),
GLboolean(GL_FALSE),
GLsizei(MemoryLayout.size * 7),
nil)
let offset: GLsizeiptr = MemoryLayout.size * 3
glVertexAttribPointer(GLuint(self.color),
4,
GLenum(GL_FLOAT),
GLboolean(GL_FALSE),
GLsizei(MemoryLayout.size * 7),
UnsafeRawPointer(bitPattern: offset))
glDrawArrays(GLenum(GL_TRIANGLES), 0, 3)
context?.presentRenderbuffer(Int(GL_RENDERBUFFER))
}
写在最后
写这篇博客的时候刚刚开始学 openGLES,在写这个博客的demo时候,各种查资料零零散散 费半日之力才成。在写的过程中出现了许多问题主要还是openGL es api方面的熟悉,api中各种参数代表的含义还是有多多的不了解。通过写这个demo我主要学习到一下几点
- 所有的buffer 都需要有 申请-->绑定-->使用的一个过程
- shader的主要流程有, 创建-->加载-->编译-->检测编译状态-->装配到着色程序-->着色程序进行链接-->使用着色程序的过程
- 顶点buffer必须有glGenBuffers --> glBindBuffer --> glBufferData --> glEnableVertexAttribArray --> glVertexAttribPointer 流程。
demo地址