zoukankan      html  css  js  c++  java
  • 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, 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
  • 相关阅读:
    IdentityServer4系列 | 资源密码凭证模式
    IdentityServer4系列 | 客户端凭证模式
    IdentityServer4系列 | 快速搭建简易项目
    Java9系列第九篇-对HTTP2协议的支持与非阻塞HTTP-API
    跨站资源共享CORS原理深度解析
    Java9系列第8篇-Module模块化编程
    Java9系列第7篇:Java.util.Optional优化与增强
    Kubernetes的Local Persistent Volumes使用小记
    CoProcessFunction实战三部曲之三:定时器和侧输出
    CoProcessFunction实战三部曲之二:状态处理
  • 原文地址:https://www.cnblogs.com/duzhaoquan/p/12905065.html
Copyright © 2011-2022 走看看