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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM