swift 使用OpenGL ES 渲染一张图片

      初学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: width, height: height))
        //获取context
        let spriteContext = CGContext(data: spriteData, width: 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: 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: width, height: height))
        //获取context
        let spriteContext = CGContext(data: spriteData, width: 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: 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值