初学OpenGL ES,使用swift时有些地方需要注意尤其是C的指针代码在 swift中的使用,eg:基础指针UnsafeRawPointer,类型指针UnsafeMutablePointer<GLubyte>,
获取类型的大小方法MemoryLayout<GLfloat>.size 本文中都有使用到,对于学OpenGL ES 的小伙伴可以借鉴,有问题也可随时交流,本文还介绍了几种纹理反转的方法。
本片主要介绍使用编译链接自定义的shader。用简单的glsl语言来实现顶点、片元着色器,并对图形进行简单的变换。
OpenGLES只是用来做渲染的,所iOS要提供一个载体,就是CAEAGLLayer,创建的方法是,通过重写UIView的类属性(OC中是累方法)返回CAEAGLLayer.self。它是一个对core animation的封装,它能满足所有的OpenGLES的方法访问。
- 关于CAEAGLLayer
在制定该图层关联的视图作为渲染器的目标图形上下文之前,可以使用drawableProperties属性更改呈现属性。此属性允许您配置呈现表面的颜色格式以及表面是否保留其内容。 因为OpenGL ES渲染的效果是要提交到用户使用的核心动画上,所以你使用在该layer上的任何效果和动画都会影响你渲染的3D效果,为了时性能最佳你应该做一下操作:设置图层为不透明,设置图层边界以匹配显示的尺寸,确保图层没有做变换。
尽量避免在CAEAGLLayer添加其layer。如果必须要添加其他非OpenGL内容,那么如果你将透明的2D内容置于GL内容之上,并确保OpenGL内容是不透明的且没有转换过,那么性能还是可以接受的。当在竖屏上绘制横向内容时,你应该自己旋转内容,而不是使用CAEAGLLayer转换来旋转它。
- 使用OpenGL ES 渲染一张图片主要总结为一下步骤:
1.创建图层
2.创建上下文
3.清空缓存区
4.设置RenderBuffer
5.设置FrameBuffer
6.开始绘制,此步骤中包含编译连接使用着色器程序,以及加载纹理图片
7.析构函数中释放buffer
1.设置图层
kEAGLDrawablePropertyRetainedBacking 表示绘图表面显示后,是否保留其内容。
kEAGLDrawablePropertyColorFormat
可绘制表面的内部颜色缓存区格式,这个key对应的值是一个NSString指定特定颜色缓存区对象。默认是kEAGLColorFormatRGBA8;
kEAGLColorFormatRGBA8:32位RGBA的颜色,4*8=32位
kEAGLColorFormatRGB565:16位RGB的颜色
kEAGLColorFormatSRGBA8:sRGB代表了标准的红、绿、蓝,即CRT显示器、LCD显示器、投影机、打印机以及其他设备中色彩再现所使用的三个基本色素。sRGB的色彩空间基于独立的色彩坐标,可以使色彩在不同的设备使用传输中对应于同一个色彩坐标体系,而不受这些设备各自具有的不同色彩坐标的影响。
func createGLLayer() { glLayer = (self.layer as! CAEAGLLayer) self.contentScaleFactor = UIScreen.main.scale glLayer.drawableProperties = [kEAGLDrawablePropertyRetainedBacking : false,kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8] } //重写父类类属性layerClass,将View返回的图层从CALayer替换成CAEAGLLayer override class var layerClass: AnyClass{ return CAEAGLLayer.self }
2.设置图形上下文
1).指定OpenGL ES 渲染API版本,我们使用3.0,2.0和3.0差不多
2).创建图形上下文
3).判断是否创建成功
4).设置图形上下文
func setUpContext(){ if let con = EAGLContext(api: EAGLRenderingAPI.openGLES3){ EAGLContext.setCurrent(con) self.context = con }else{ print("创建context失败") } }
3.清除缓冲区
buffer分为frame buffer 和 render buffer2个大类。
其中frame buffer 相当于render buffer的管理者。
frame buffer object即称FBO。
render buffer则又可分为3类。colorBuffer、depthBuffer、stencilBuffer。
func clearRenderBufferAndFrameBuffer() { glDeleteBuffers(1, &colorRederBuffer) colorRederBuffer = 0 glDeleteBuffers(1, &colorFrameBuffer) colorFrameBuffer = 0 }
4.设置渲染缓冲区
func setUpRenderBuffer() { //申请一个缓冲区标志 glGenRenderbuffers(1, &colorRederBuffer) //将标识符绑定到GL_RENDERBUFFER glBindRenderbuffer(GLenum(GL_RENDERBUFFER), colorRederBuffer) //将可绘制对象drawable object's CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象 context.renderbufferStorage(Int(GLenum(GL_RENDERBUFFER)), from: glLayer) }
5.设置帧缓冲区
生成帧缓存区之后,则需要将renderbuffer跟framebuffer进行绑定,
调用glFramebufferRenderbuffer函数进行绑定到对应的附着点上,后面的绘制才能起作用
func setUpFrameBuffer() { //申请一个缓冲区标志 glGenRenderbuffers(1, &colorFrameBuffer) //将标识符绑定到GL_FRAMEBUFFER glBindFramebuffer(GLenum(GL_FRAMEBUFFER), colorFrameBuffer) //将渲染缓存区myColorRenderBuffer 通过glFramebufferRenderbuffer函数绑定到 GL_COLOR_ATTACHMENT0上。 glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), colorRederBuffer) }
6.开始正式绘制
func renderLayer() { //1.设置背景颜色 glClearColor(1, 0, 0, 1) //2.清楚深度缓冲区 glClear(GLbitfield(GL_DEPTH_BUFFER_BIT)) //3.设置视口 let scale = UIScreen.main.scale glViewport(GLint(frame.origin.x * scale), GLint(frame.origin.y * scale), GLsizei(frame.size.width * scale), GLsizei(frame.size.height * scale)) //4.读取顶点着色程序、片元着色程序 let spath = Bundle.main.path(forResource: "shaderv", ofType: "vsh") ?? "" let fpath = Bundle.main.path(forResource: "shaderf", ofType: "fsh") ?? "" let (sucess,program) = loadShader(vertexPath: spath, fragmentPath: fpath) if !sucess { return } //5.设置顶点、纹理坐标 let vertexs:[GLfloat] = [ 0.5, -0.5, -1.0, 1.0, 0.0, -0.5, 0.5, -1.0, 0.0, 1.0, -0.5, -0.5, -1.0, 0.0, 0.0, 0.5, 0.5, -1.0, 1.0, 1.0, -0.5, 0.5, -1.0, 0.0, 1.0, 0.5, -0.5, -1.0, 1.0, 0.0, ] //6.处理定点数据(copy到缓冲区) var verbuffer = GLuint() glGenBuffers(1, &verbuffer) glBindBuffer(GLenum(GL_ARRAY_BUFFER), verbuffer) glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * 30, vertexs, GLenum(GL_DYNAMIC_DRAW)) //7.将顶点数据通过Programe传递到顶点着色程序的position属性上 //1.glGetAttribLocation,用来获取vertex attribute的入口的. //2.告诉OpenGL ES,通过glEnableVertexAttribArray,打开开关 //3.最后数据是通过glVertexAttribPointer传递过去的。 //顶点坐标 //(1)注意:第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致 let positon = glGetAttribLocation(program, "position") //(2).设置合适的格式从buffer里面读取数据 glEnableVertexAttribArray(GLuint(positon)) //(3).设置读取方式 //参数1:index,顶点数据的索引 //参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4. //参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT //参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE) //参数5:stride,连续顶点属性之间的偏移量,默认为0; //参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0 glVertexAttribPointer(GLuint(positon), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 5), UnsafeRawPointer(bitPattern: 0)) //8.纹理坐标数据通过Programe传递到顶点着色程序的textCoordinate属性上 let texture = glGetAttribLocation(program, "textCoordinate") glEnableVertexAttribArray(GLuint(texture)) glVertexAttribPointer(GLuint(texture), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 5), UnsafeRawPointer(bitPattern:MemoryLayout<GLfloat>.size * 3)) //9.加载纹理图片 setUpTextureImage(imageName: "timg.jpeg") //10.设置纹理采样器 glUniform1i(glGetUniformLocation(program, "colorMap"), 0) //11.绘制 glDrawArrays(GLenum(GL_TRIANGLES), 0, 6) //12.提交 context.presentRenderbuffer(Int(GL_RENDERBUFFER)) } //设置纹理图片 func setUpTextureImage(imageName:String) { guard let image = UIImage(named: imageName)?.cgImage else { return } let width = image.width let height = image.height //开辟内存,绘制到这个内存上去 let spriteData: UnsafeMutablePointer = UnsafeMutablePointer<GLubyte>.allocate(capacity: MemoryLayout<GLubyte>.size * width * height * 4) UIGraphicsBeginImageContext(CGSize( width, height: height)) //获取context let spriteContext = CGContext(data: spriteData, width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: image.colorSpace!, bitmapInfo: image.bitmapInfo.rawValue) spriteContext?.draw(image, in: CGRect(x: 0, y: 0, width, height: height)) UIGraphicsEndImageContext() //绑定纹理 glBindTexture(GLenum(GL_TEXTURE_2D), 0) //设置纹理参数 //缩小/放大过滤器 glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR) //环绕方式 glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE) //载入纹理 /* 参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 参数2:加载的层次,一般设置为0 参数3:纹理的颜色值GL_RGBA 参数4:宽 参数5:高 参数6:border,边界宽度 参数7:format 参数8:type 参数9:纹理数据 */ glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE),spriteData) //释放内存 free(spriteData) }
7.稀构方法中清空缓冲区
deinit { glDeleteBuffers(1, &colorFrameBuffer) glDeleteBuffers(1, &colorRederBuffer) }
- 加载一张纹理图片
func setUpTextureImage(imageName:String) { guard let image = UIImage(named: imageName)?.cgImage else { return } let width = image.width let height = image.height //开辟内存,绘制到这个内存上去 let spriteData: UnsafeMutablePointer = UnsafeMutablePointer<GLubyte>.allocate(capacity: MemoryLayout<GLubyte>.size * width * height * 4) UIGraphicsBeginImageContext(CGSize( width, height: height)) //获取context let spriteContext = CGContext(data: spriteData, width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: image.colorSpace!, bitmapInfo: image.bitmapInfo.rawValue)
//图片反转,
spriteContext?.translateBy(x: 0, y: CGFloat(height))//向下平移图片的高度
spriteContext?.scaleBy(x: 1, y: -1)//反转图片
spriteContext?.draw(image, in: CGRect(x: 0, y: 0, width, height: height)) UIGraphicsEndImageContext() //绑定纹理 glBindTexture(GLenum(GL_TEXTURE_2D), 0) //设置纹理参数 //缩小/放大过滤器 glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR) //环绕方式 glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE) glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE) //载入纹理 /* 参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 参数2:加载的层次,一般设置为0 参数3:纹理的颜色值GL_RGBA 参数4:宽 参数5:高 参数6:border,边界宽度 参数7:format 参数8:type 参数9:纹理数据 */ glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE),spriteData) //释放内存 free(spriteData) }
- 加载着色器程序方法
func loadShader(vertexPath:String,fragmentPath:String) -> (Bool,GLuint){ let program:GLuint = glCreateProgram() //vertexShader guard let verShader:GLuint = compileShader(type: GLenum(GL_VERTEX_SHADER), filePath: vertexPath) else { return (false,program) } //把编译后的着色器代码附着到最终的程序上 glAttachShader(program, verShader) //释放不需要的shader glDeleteShader(verShader) //fragmentShader guard let fragShader = compileShader(type: GLenum(GL_FRAGMENT_SHADER), filePath: fragmentPath)else{ return (false,program) } glAttachShader(program, fragShader) glDeleteShader(fragShader) //链接着色器代程序 glLinkProgram(program) //获取链接状态 var status:GLint = 0 glGetProgramiv(program, GLenum(GL_LINK_STATUS), &status) if status == GLenum(GL_FALSE){ print("link Error") //打印错误信息 let message = UnsafeMutablePointer<GLchar>.allocate(capacity: 512) glGetProgramInfoLog(program, GLsizei(MemoryLayout<GLchar>.size * 512), nil, message) let str = String.init(utf8String: message) print(str ?? "没有取到ProgramInfoLog") return (false,program) }else{ print("link sucess!") //链接成功,使用着色器程序 glUseProgram(program) return (true,program) } }
//读取并编译着色器程序
func compileShader(type:GLenum,filePath:String) -> GLuint? {
//创建一个空着色器
let verShader:GLuint = glCreateShader(type)
//获取源文件中的代码字符串
guard let shaderString = try? String.init(contentsOfFile: filePath, encoding: String.Encoding.utf8)else {
return nil
}
//转成C字符串赋值给已创建的shader
shaderString.withCString { (pointer) in
var pon:UnsafePointer<GLchar>? = pointer
glShaderSource(verShader, 1, &pon, nil)
}
//编译
glCompileShader(verShader)
return verShader
}
- 顶点着色器代码
attribute vec4 position; attribute vec2 textCoordinate; varying lowp vec2 varyTextCoord; uniform mat4 rotateMatrix; void main() { // varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y); varyTextCoord = textCoordinate; gl_Position = position; // gl_Position = position * rotateMatrix; }
- 片元着色器代码
varying lowp vec2 varyTextCoord; uniform sampler2D colorMap; void main() { gl_FragColor = texture2D(colorMap, varyTextCoord); // gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0 - varyTextCoord.y)); }
- 关于纹理反转问题
由于iOSlayer中坐标系OpneGL坐标系不一致(顶点在左上角)所以加载出来的图片会到置,需要做一下处理使图片正,主要纹理反转的方法有如下:
//1.矩阵反转
func rotateTextureImage(program:GLuint) { //获取旋转180度的矩阵 var rotateM = GLKMatrix4MakeZRotation(Float.pi).getArray() //rotateM = GLKMatrix4Identity.getArray() glUniformMatrix4fv(glGetUniformLocation(program, "rotateMatrix"), 1, 0, rotateM) } extension GLKMatrix4 { /// 转成数组 /// - Returns: 结果数组 func getArray() ->[Float] { [ m00,m01,m02,m03, m10,m11,m12,m13, m20,m21,m22,m23, m30,m31,m32,m33, ] } }
//2.加载图片时反转
spriteContext?.translateBy(x: 0, y: CGFloat(height))//向下平移图片的高度 spriteContext?.scaleBy(x: 1, y: -1)//反转图片
//3.坐标反转,改变纹理坐标
let vertexs:[GLfloat] = [ 0.5, -0.5, -1.0, 1.0f, 1.0f, -0.5, 0.5, -1.0, 0.0f, 0.0f, -0.5, -0.5, -1.0, 0.0f, 1.0f, 0.5, 0.5, -1.0, 1.0f, 0.0f, -0.5, 0.5, -1.0, 0.0f, 0.0f, 0.5, -0.5, -1.0, 1.0f, 1.0f, ]
//4.顶点着色器中反转
//顶点着色器传递给片元着色器是改变Y坐标1-y坐标,使Y方向反转 varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
//5.片元着色器中反转
//直接在片元着色器中反转Y坐标 gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0 - varyTextCoord.y));
因为想着在着色器程序中使用较少的代码原则,是倾向于使用第2种方法
- 具体github代码地址:
https://github.com/duzhaoquan/OpenGLESLoadImage.git