Swift+OpenGLES 展現圖片

背景

本篇用於記錄自定義頂點和片元着色程序顯示一個圖片。swift

實現

如下代碼作了詳細註釋,其中swiftoc操做有區別處,也作了詳細註釋,其中包括解決問題的參考文章。api

swift代碼

import UIKit
import OpenGLES
import CoreGraphics

class DowImageView: UIView {
    
    private var mEaglLayer: CAEAGLLayer?
    private var mContext: EAGLContext?
    //一些id標記
    private var mColorRenderBuffer = GLuint()
    private var mColorFrameBuffer = GLuint()
    private var mprograme = GLuint()
    
    //How do you override layerClass in swift: https://stackoverflow.com/questions/24351102/how-do-you-override-layerclass-in-swift
    override class var layerClass: AnyClass {
        get {
            return CAEAGLLayer.self
        }
    }

    /**繪圖流程
     1.建立圖層
     2.建立上下文
     3.清空緩存區
     4.設置RenderBuffer
     5.設置FrameBuffer
     6.開始繪製
     */
    override func layoutSubviews() {
        setupLayer()
        setupContext()
        deleteRenderAndFrameBuffer()
        setupRenderBuffer()
        setupFrameBuffer()
        renderLayer()
    }
    
    private func setupLayer() {
        mEaglLayer = self.layer as? CAEAGLLayer
        self.contentScaleFactor = UIScreen.main.scale
        //kEAGLDrawablePropertyRetainedBacking:繪圖表面顯示後,是否保留其內容
        //kEAGLColorFormatRGBA8:32位RGBA的顏色,4*8=32位
        //kEAGLColorFormatRGB565:16位RGB的顏色,
        //kEAGLColorFormatSRGBA8:sRGB表明了標準的紅、綠、藍,即CRT顯示器、LCD顯示器、投影機、打印機以及其餘設備中色彩再現所使用的三個基本色素。sRGB的色彩空間基於獨立的色彩座標,可使色彩在不一樣的設備使用傳輸中對應於同一個色彩座標體系,而不受這些設備各自具備的不一樣色彩座標的影響。
        mEaglLayer?.drawableProperties = [kEAGLDrawablePropertyRetainedBacking: false,
                                                                  kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8]
    }
    
    private func setupContext() {
        let context = EAGLContext(api: EAGLRenderingAPI.openGLES3)
        EAGLContext.setCurrent(context)
        mContext = context
    }
    
    //清空緩存區
    private func deleteRenderAndFrameBuffer() {
        /*
         buffer分爲frame buffer 和 render buffer2個大類。
         其中frame buffer 至關於render buffer的管理者。
         frame buffer object即稱FBO。
         render buffer則又可分爲3類。colorBuffer、depthBuffer、stencilBuffer。
         */
        
        glDeleteBuffers(1, &mColorRenderBuffer)
        mColorRenderBuffer = 0
        glDeleteBuffers(1, &mColorFrameBuffer)
        mColorFrameBuffer = 0
    }
    
    private func setupRenderBuffer() {
        //定義一個緩存區id
        var buffer = GLuint()
        //申請緩存區id
        glGenRenderbuffers(1, &buffer)
        mColorRenderBuffer = buffer
        
        //將id綁定到GL_RENDERBUFFER
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), mColorRenderBuffer)
        //綁定renderBuffer併爲其分配存儲空間
        //https://developer.apple.com/documentation/opengles/eaglcontext/1622262-renderbufferstorage
        mContext?.renderbufferStorage(Int(GL_RENDERBUFFER), from: mEaglLayer)
    }
    
    private func setupFrameBuffer() {
        var buffer = GLuint()
        glGenFramebuffers(1, &buffer)
        mColorFrameBuffer = buffer
        glBindFramebuffer(GLenum(GL_FRAMEBUFFER), mColorFrameBuffer)
       //生成幀緩存區以後,須要將renderbuffer跟framebuffer進行綁定,framebuffer用於管理renderbuffer
        glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), mColorRenderBuffer)
        
    }
    
    private func renderLayer() {
        glClearColor(0.9, 0.8, 0.5, 1.0)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
        let scale = UIScreen.main.scale
        let frame = self.frame
        //設置視口
        glViewport(0, 0, GLsizei(frame.size.width * scale), GLsizei(frame.size.height * scale))

        //讀取頂點、片元着色程序
        let verFile = Bundle.main.path(forResource: "shaderv", ofType: "vsh")
        let fragFile = Bundle.main.path(forResource: "shaderf", ofType: "fsh")
        
        //將着色程序綁定到program
        attachToProgram(with: verFile, fragFIle: fragFile)
        
        //連接
        glLinkProgram(mprograme)
        //獲取連接狀態
        var linkStatus = GLint()
        glGetProgramiv(mprograme, GLenum(GL_LINK_STATUS), &linkStatus)
   
        if linkStatus == GL_FALSE {
            var message = [GLchar]()
            glGetProgramInfoLog(mprograme, GLsizei(MemoryLayout<GLchar>.size * 512), nil, &message)
            let errorInfo = String(cString: message, encoding: .utf8)
            print(errorInfo)
            return
        }
        print("🍺🍻 link success")
        
        glUseProgram(mprograme)
        
        //座標數據
        let attrArr: [GLfloat] = [
            1, -1, -1.0,     1.0, 0.0,
            -1, 1, -1.0,     0.0, 1.0,
            -1, -1, -1.0,    0.0, 0.0,
            
            1, 1, -1.0,      1.0, 1.0,
            -1, 1, -1.0,     0.0, 1.0,
            1, -1, -1.0,     1.0, 0.0,  
        ]
        
        //頂點數據處理
        var attrBuffer = GLuint()
        glGenBuffers(1, &attrBuffer)
        glBindBuffer(GLenum(GL_ARRAY_BUFFER), attrBuffer)
        //由內存copy到顯存
        glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * 30, attrArr, GLenum(GL_DYNAMIC_DRAW))
        
        //將頂點數據經過mPrograme傳遞到頂點着色程序的position
        
        //用來獲取vertex attribute的入口.第二參數字符串必須和shaderv.vsh中的輸入變量:position保持一致
        let position = glGetAttribLocation(mprograme, "position")
        //設置合適的格式從buffer裏面讀取數據
        glEnableVertexAttribArray(GLuint(position))
        //設置讀取方式
        //arg1:index,頂點數據的索引
        //arg2:size,每一個頂點屬性的組件數量,1,2,3,或者4.默認初始值是4.
        //arg3:type,數據中的每一個組件的類型,經常使用的有GL_FLOAT,GL_BYTE,GL_SHORT。默認初始值爲GL_FLOAT
        //arg4:normalized,固定點數據值是否應該歸一化,或者直接轉換爲固定值。(GL_FALSE)
        //arg5:stride,連續頂點屬性之間的偏移量,默認爲0;
        //arg6:指定一個指針,指向數組中的第一個頂點屬性的第一個組件。默認爲0
        glVertexAttribPointer(GLuint(position), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 5), nil)
        //處理紋理數據
        let textCoor = glGetAttribLocation(mprograme, "textCoordinate")
        glEnableVertexAttribArray(GLuint(textCoor))
    //此處bufferoffset取值應注意:https://stackoverflow.com/questions/56535272/whats-wrong-when-i-custom-an-imageview-by-opengles
        glVertexAttribPointer(GLuint(textCoor), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 5), BUFFER_OFFSET(MemoryLayout<GLfloat>.size * 3))
        
        loadTexture(with: "me")
        
        //設置紋理採樣器 0張紋理
        glUniform1i(glGetUniformLocation(mprograme, "colorMap"), 0)
        //繪圖 arg2:從數組緩存中的哪一位開始繪製,通常都定義爲0
        glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)
        
        mContext?.presentRenderbuffer(Int(GL_RENDERBUFFER))
    }
    
    private func BUFFER_OFFSET(_ i: Int) -> UnsafeRawPointer? {
        return UnsafeRawPointer(bitPattern: i)
    }
    
    //從圖片加載紋理
    private func loadTexture(with name: String) {
        
        guard let spriteImage = UIImage(named: name)?.cgImage else { return }
        let width = spriteImage.width
        let height = spriteImage.height
        //獲取圖片字節數: 寬*高*4(RGBA)
        let spriteData = calloc(width * height * 4, MemoryLayout<GLubyte>.size)
        
        //建立上下文
        //https://stackoverflow.com/questions/24109149/cgbitmapcontextcreate-error-with-swift
        /*
         arg1:data,指向要渲染的繪製圖像的內存地址
         arg2:width,bitmap的寬度,單位爲像素
         arg3:height,bitmap的高度,單位爲像素
         arg4:bitPerComponent,內存中像素的每一個組件的位數,好比32位RGBA,就設置爲8
         arg5:bytesPerRow,bitmap的沒一行的內存所佔的比特數
         arg6: 顏色空間
         arg7:colorSpace,bitmap上使用的顏色空間  kCGImageAlphaPremultipliedLast:RGBA
         */
        //bitmapInfo: https://blog.csdn.net/ccflying88/article/details/50753795
        let spriteContext = CGContext(data: spriteData, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: spriteImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
        
        //圖片翻轉
        spriteContext?.translateBy(x: 0, y: CGFloat(height))
        spriteContext?.scaleBy(x: 1.0, y: -1.0)
        //在CGContextRef上繪製圖片
        let rect = CGRect(x: 0, y: 0, width: width, height: height)
        spriteContext?.clear(rect)
        spriteContext?.draw(spriteImage, in: rect)
        
        //綁定紋理到默認id, 只有一個紋理取0
        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)
        
        //載入紋理數據
        /*
         arg1:紋理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
         arg2:加載的層次,通常設置爲0
         arg3:紋理的顏色值GL_RGBA
         arg4:寬
         arg5:高
         arg6:border,邊界寬度
         arg7:format
         arg8:type
         arg9:紋理數據
         */
        glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, GLsizei(width), GLsizei(height), 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), spriteData)
        free(spriteData)
        
    }
    
    private func attachToProgram(with verFile: String?, fragFIle: String?) {
        guard let verFile = verFile, let fragFIle = fragFIle else { return }
        var verShader = GLuint()
        var fragShader = GLuint()
        let program = glCreateProgram()
        compileshader(with: &verShader, type: GLenum(GL_VERTEX_SHADER), file: verFile)
        compileshader(with: &fragShader, type: GLenum(GL_FRAGMENT_SHADER), file: fragFIle)
        
        glAttachShader(program, verShader)
        glAttachShader(program, fragShader)
        
        //綁定後不須要了要釋放掉
        glDeleteShader(verShader)
        glDeleteShader(fragShader)
        
        mprograme = program
    }
    
    private func compileshader(with  shader: inout GLuint,
                               type: GLenum,
                               file: String) {
        
        let content = try? String(contentsOfFile: file, encoding: String.Encoding.utf8)
        let contentCString = content?.cString(using: .utf8)
        var source = UnsafePointer<GLchar>(contentCString)
        
        shader = glCreateShader(type)
        
        //將着色器源碼附加到着色器對象上。
        //arg1:shader,要編譯的着色器對象
        //arg2:numOfStrings,傳遞的源碼字符串數量 1個
        //arg3:strings,着色器程序的源碼(真正的着色器程序源碼)
        //arg4:lenOfStrings,長度,具備每一個字符串長度的數組,或nil,這意味着字符串是nil終止的
        glShaderSource(shader, 1,&source, nil)
        //把着色器源代碼編譯成目標代碼
        glCompileShader(shader)
        
        var sucess = GLint()
        glGetShaderiv(shader, GLenum(GL_COMPILE_STATUS), &sucess)
        if sucess == GL_FALSE {
            var message = [GLchar]()
            glGetShaderInfoLog(shader, GLsizei(MemoryLayout<GLchar>.size * 512), nil, &message)
            let errorInfo = String(cString: message, encoding: .utf8)
            print("shaderErrorInfo:" + (errorInfo ?? ""))
        }
    }
}

/**
 oc寫法
 - (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
 
 NSString* content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
 const GLchar* source = (GLchar *)[content UTF8String];

 *shader = glCreateShader(type);
 
 glShaderSource(*shader, 1, &source,NULL);

 glCompileShader(*shader);
 
 }
 
 */

複製代碼

頂點着色程序shaderv.vsh代碼

attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;

void main() {
    varyTextCoord = textCoordinate;
    gl_Position = position;
}

複製代碼

片元着色程序shaderf.fsh代碼

varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;

void main() {
    lowp vec2 textCoord = varyTextCoord;
    gl_FragColor = texture2D(colorMap, textCoord);
}
複製代碼

精度限定符

精度限定符包括highpmediumplowp。 字符常量和布爾型沒有精度修飾符。數組

指定變量精度(放在數據類型以前)
highp vec4 position;
varying lowp vec4 color;
複製代碼
指定默認精度
//使用:precision precision-qualifier type;
eg: precision highp float;
複製代碼

在頂點着色程序中若是沒有默認精度,則floatint精度都是highp緩存

在片元着色程序中float沒有默認精度,必須指定默認精度或爲每個都指定精度。bash

實現效果

GLSL知識補充

向量操做

  • 向量中元素獲取能夠經過x,y,z,w下標獲取:

v1.x = 3.0f;app

v1.xy = vec2(2.0f,3.0f);ide

v1.xyz = vec3(3.0f,4.0f,5.0f);ui

  • 能夠經過顏色控制r,g,b,a獲取

v1.r = 3.0f; v1.rgba = vec4(1.0f,1.0f,1.0f,1.0f);spa

  • 能夠經過紋理座標s,t,p,q獲取

v1.st = vec2(1.0f,0.0f);.net

  • 支持調換操做

v1.rgba = v2.bgra;

v2.bgra = v1.rgba;

  • 支持一次性對全部份量操做

v1.xyz = vOtherVerex.xyz + vec3(5.0f,4.0f,3.0f);

矩陣

mat4 m1,m2,m3;
//單元矩陣
mat4 m2 = mat4(1.0f,0.0f,0.0f,0.0f
               0.0f,1.0f,0.0f,0.0f,
               0.0f,0.0f,1.0f,0.0f,
               0.0f,0.0f,0.0f,1.0f);
mat4 m4 = mat4(1.0f);
mat4 m3 = mat4(0.5,0.5,0.5,0.5,
複製代碼

結構體

struct forStruct{
 vec4 color;
 float start;
 float end;
}fogVar;
fogVar = fogStruct(vec4(1.0,0.0,0.0,1.0),0.5,2.0);
vec4 color = fogVar.color;
float start = fogVar.start;
複製代碼

數組

float floatArray[4];
vec4 vecArray[2];

float a[4] = float[](1.0,2.0,3.0,4.0);
vec2 c[2] = vec2[2](vec2(1.0,2.0),vec2(3.0,4.0));
複製代碼

參考

How do you override layerClass in swift

renderbufferStorage

CGBitmapContextCreate error with swift

Swift kCGImageAlphaPremultipliedLast unresolved

相關文章
相關標籤/搜索