OpenGL ES 案例04:GLSL加載圖片

OpenGL + OpenGL ES +Metal 系列文章匯總

本案例的主要目的在于理解GLSL語言自定義著色器以及自定義著色器是如何使用的

案例的效果是利用GLSL自定義的著色去加載一張圖片,效果圖如下


案例效果圖

案例的整體流程圖如下


整體流程圖

流程中主要分為4個模塊

  • 準備工作:項目的創(chuàng)建及自定義視圖、屬性等
  • 自定義著色器:利用GLSL編寫自定義的頂點、片元著色器
  • 初始化:創(chuàng)建layer、context,清空緩存區(qū),以及設(shè)置Render和Frame緩存區(qū)
  • 繪制:主要是GLSL加載、頂點數(shù)據(jù)處理以及加載紋理,最后繪制到屏幕上

準備工作

項目的創(chuàng)建及自定義視圖創(chuàng)建等,這里不作過多說明,主要說說著色器文件是如何創(chuàng)建的

  • command + N,開始新建文件

  • 選擇 ios --> Other --> Empty,點擊next


    創(chuàng)建empty
  • 輸入文件名稱,例如shaderv.vsh,點擊create,即創(chuàng)建成功

    命名

自定義著色器

自定義的著色器本質(zhì)上其實是一個字符串,且在Xcode中編寫時,是沒有任何提示的,所以需要格外仔細!

頂點著色器

  • 定義兩個attribute修飾的變量,分別表示頂點坐標position和紋理坐標textCoordinate
  • 定義一個與片元橋接的變量varyTextCoord,用來將紋理坐標從頂點著色器傳遞到片元著色器
  • main函數(shù):如果頂點沒有任何變換操作,則直接將頂點坐標賦值給內(nèi)建變量gl_Position,如果頂點有變換,將變換后的結(jié)果 即最終的頂點坐標數(shù)據(jù),賦值給內(nèi)建變量
//頂點坐標
attribute vec4 position;
紋理坐標
attribute vec2 textCoordinate;
//紋理坐標
varying lowp vec2 varyTextCoord;

void main(){
    //通過varying 修飾的varyTextCoord,將紋理坐標傳遞到片元著色器
    varyTextCoord = textCoordinate;
    //給內(nèi)江變量gl_Position賦值
    gl_Position = position;
}

片與著色器

  • 片元著色器中float類型的精度,如果不寫,可能會報一些異常的錯誤
  • 定義一個與頂點著色器的橋接變量varyTextCoord,即紋理坐標,必須與頂點著色器中一模一樣,如果不一致,紋理坐標數(shù)據(jù)將無法傳遞
  • 定義一個unifom修飾的紋理采樣器colorMap,用于獲取紋理坐標每個像素點的紋素
  • main函數(shù):主要是紋理顏色的填充,通過texture2D內(nèi)建函數(shù)獲取最終的顏色值,有兩個參數(shù),參數(shù)1是紋理圖片,參數(shù)2是紋理坐標,且返回值是一個vec4類型的顏色值,并將該值結(jié)果賦值給內(nèi)建變量
//指定float的默認精度
precision highp float;
//紋理坐標
varying lowp vec2 varyTextCoord;
//紋理采樣器(獲取對應(yīng)的紋理ID)
uniform sampler2D colorMap;

void main(){
    //texture2D(紋理采樣器,紋理坐標),獲取對應(yīng)坐標紋素
    //紋理坐標添加到對應(yīng)像素點上,即將讀取的紋素賦值給內(nèi)建變量 gl_FragColor
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}

初始化

初始化主要分為4部分

  • setupLayer:創(chuàng)建圖層
  • setupContext:創(chuàng)建上下文
  • deleteRenderAndFrameBuffer:清理緩存區(qū)
  • setupRenderBuffer、setupFrameBuffer:設(shè)置RenderBuffer & FrameBuffer

setupLayer函數(shù):創(chuàng)建圖層

layer主要是用于顯示OpenGL ES繪制內(nèi)容的載體
函數(shù)流程如下


setupLayer函數(shù)流程
  • 創(chuàng)建特殊圖層,有兩種方式
    • 1)直接使用view自帶的layer
      由于UIView中自帶的layer是繼承自CALayer的,而需要創(chuàng)建的layer是繼承自CAEAGLLayer的,所以需要重寫類方法layerClass,返回[CAEAGLLayer class]
    • 2)使用init創(chuàng)建圖層
      可以直接使用[[CAEAGLLayer alloc] init]創(chuàng)建一個新的layer,將其add到layer上
    • 在本案例中,使用的是view自帶的layer
  • 設(shè)置scale,將layer的大小設(shè)置為跟屏幕大小一致
  • 設(shè)置描述屬性
    • kEAGLDrawablePropertyRetainedBacking 只有true 或者 false兩種
    • kEAGLDrawablePropertyColorFormat有以下三種值
kEAGLDrawablePropertyColorFormat枚舉值 描述
kEAGLColorFormatRGBA8 32位的RGBA顏色值(每個表示8位,所以4*8=32位)
kEAGLColorFormatRGB565 16位的RGB顏色值
kEAGLColorFormatSRGBA8 表示標準的紅、綠、藍,sRGB的色彩空間基于獨立的色彩坐標,可以使色彩在不同的設(shè)備使用傳輸中對應(yīng)于同一個色彩坐標體系,而不受這些設(shè)備各自具有的不同色彩坐標的影響。

下面是兩個屬性的一些說明

屬性 說明 默認值
kEAGLDrawablePropertyRetainedBacking 表示繪圖表面顯示后,是否保留其內(nèi)容 false
kEAGLDrawablePropertyColorFormat 可繪制表面的內(nèi)部顏色緩存區(qū)格式 kEAGLColorFormatRGBA8
//1、創(chuàng)建圖層
- (void)setupLayer{
//    1、創(chuàng)建特殊圖層

    self.myEagLayer = (CAEAGLLayer*)self.layer;
    
//    2、設(shè)置scale
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    
//    3、設(shè)置描述屬性
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false, kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}

+ (Class)layerClass{
    return [CAEAGLLayer class];
}

setupContext函數(shù):創(chuàng)建上下文

上下文主要是用于保存OpenGL ES中的狀態(tài),是一個狀態(tài)機,不論是GLKIt還是GLSL,都是需要context的,主要創(chuàng)建流程如下

setupContext函數(shù)流程
  • 創(chuàng)建Context,并指定OpenGL ES 渲染API的版本號(2、3均可),且判斷是否創(chuàng)建成功
  • 設(shè)置當(dāng)前的context為創(chuàng)建的context,并判斷是都設(shè)置成功
  • 將其賦值給全局的context,
//2、創(chuàng)建上下文
- (void)setupContext{
//    1、指定OpenGL ES 渲染API版本
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
//    2、創(chuàng)建圖形上下文
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
//    3、判斷是否創(chuàng)建成功
    if (!context) {
        NSLog(@"Create context failed!");
        return;
    }
//    4、設(shè)置圖形上下文
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"setCurrentContext failed");
        return;
    }
//    5、將局部變量賦值給全局變量
    self.myContext = context;
}

deleteRenderAndFrameBuffer函數(shù):清理緩存區(qū)

清理緩沖區(qū)的目的在于清除殘留數(shù)據(jù),防止殘留數(shù)據(jù)對本次操作造成影響


deleteRenderAndFrameBuffer函數(shù)流程

需要清空兩個緩存區(qū):RenderBuffer和FrameBuffer

//3、清空緩存區(qū)
- (void)deleteRenderAndFrameBuffer{
//    清空渲染緩存區(qū)
    glDeleteBuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    
//    清空幀緩存區(qū)
    glDeleteBuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
}

設(shè)置RenderBUffe & FrameBuffer

在設(shè)置之前,首先說說RenderBuffer和FrameBuffer

  • RenderBuffer:是一個通過應(yīng)用分配的2D圖像緩沖區(qū),需要附著在FrameBuffer上
  • FrameBuffer:是一個收集顏色、深度和模板緩存區(qū)的附著點,簡稱FBO,即是一個管理者,用來管理RenderBuffer,且FrameBuffer沒有實際的存儲功能,真正實現(xiàn)存儲的是RenderBuffer

下面這張圖很形象的表示了兩者間的關(guān)系


RenderBuffer與FrameBuffer的關(guān)系
  • FrameBuffer有3個附著點
    • 顏色附著點(Color Attachment):管理紋理、顏色緩沖區(qū)
    • 深度附著點(depth Attachment):會影響顏色緩沖區(qū),管理深度緩沖區(qū)(Depth Buffer)
    • 模板附著點(Stencil Attachment):管理模板緩沖區(qū)(Stencil Buffer)
  • RenderBuffer有3種緩存區(qū)
    • 深度緩存區(qū)(Depth Buffer):存儲深度值等
    • 紋理緩存區(qū):存儲紋理坐標中對應(yīng)的紋素、顏色值等
    • 模板緩存區(qū)(Stencil Buffer):存儲模板

setupRenderBuffer函數(shù)

主要是創(chuàng)建RenderBufferID并申請標識符,將標識符綁定至GL_RENDERBUFFER,并且將layer的相關(guān)存儲綁定到RenderBuffer對象

設(shè)置的流程如下


setupRenderBuffer函數(shù)流程
//4、設(shè)置RenderBuffer
- (void)setupRenderBuffer{
//    1、定義一個緩存區(qū)ID
    GLuint buffer;
    
//    2、申請一個緩存區(qū)標識符
    glGenRenderbuffers(1, &buffer);
    
//    3、賦值給全局變量
    self.myColorRenderBuffer = buffer;
    
//    4、將標識符綁定到GL_RENDERBUFFER
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    
//    5、將可繪制對象drawable object的CAEAGLLayer的存儲綁定到OpenGL ES renderBuffer對象
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}

setupFrameBuffer函數(shù)

主要是創(chuàng)建FrameBuffer的ID并申請標識符,將標識符綁定至GL_FRAMEBUFFER,然后將RenderBuffer通過glFramebufferRenderbuffer函數(shù)綁定到FrameBuffer中的GL_COLOR_ATTACHMENT0附著點上,通過FrameBuffer來管理RenderBuffer,RenderBuffer存儲相關(guān)數(shù)據(jù)到相應(yīng)緩存區(qū)

設(shè)置流程如下


setupFrameBuffer函數(shù)流程
//5、設(shè)置FrameBuffer
- (void)setupFrameBuffer{
//    1、定義一個ID
    GLuint buffer;
    
//    2、申請一個緩存區(qū)標識符
    glGenBuffers(1, &buffer);
    
//    3、賦值給全局變量
    self.myColorFrameBuffer = buffer;
    
//    4、將標識符綁定到GL_FRAMEBUFFER
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    
//    5、將渲染緩存區(qū)的myColorRenderBuffer 通過 glFramebufferRenderbuffer函數(shù)綁定到GL_COLOR_ATTACHMENT0上
    /*
     glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
     參數(shù)1:綁定到的目標
     參數(shù)2:FrameBuffer的附著點
     參數(shù)3:需要綁定的渲染緩沖區(qū)目標
     參數(shù)4:渲染緩沖區(qū)
     */
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}

注:綁定renderBuffer和FrameBuffer是有順序的,先有RenderBuffer,才有FrameBuffer

繪制

繪制的整體流程圖圖所示

繪制整體流程

主要包含5部分

  • 初始化:初始化背景顏色,清理緩存,并設(shè)置視口大小
  • GLSL自定義著色器加載:對自定義著色器進行加載,大致步驟為讀取-->加載-->編譯-->program鏈接-->使用
  • 頂點數(shù)據(jù)設(shè)置及處理:將頂點坐標和紋理坐標讀取到自定義的頂點著色器中
  • 加載紋理:將png/jpg圖片解壓成位圖,并讀取紋理每個像素點的紋素
  • 繪制:開始繪制,存儲到RenderBuffer,從RenderBuffer將圖片顯示到屏幕上

初始化

需要注意的是,需要將視口的大小設(shè)置為與屏幕大小一致

//    設(shè)置清屏顏色 & 清除屏幕
    glClearColor(0.3f, 0.45f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
//    1、設(shè)置視口大小
    CGFloat scale = [[UIScreen mainScreen] scale];
    glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);

GLSL自定義著色器加載

自定義著色器的加載主要分為以下幾步

  • 讀取自定義著色器
  • compileShader & loadShaders函數(shù):編譯&加載著色器
  • 鏈接program & 判斷鏈接是否成功
  • 使用program

讀取自定義著色器
讀取自定義著色器文件的前提是需要獲得文件的路徑,將其傳入loadShaders函數(shù)進行加載

NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
    NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];

loadShaders函數(shù) & compileShader函數(shù)

  • 【compileShader函數(shù)】:在將著色器加載/附著到program上時,需要先進行編譯,分為以下幾步


    compileShader函數(shù)流程
    • 根據(jù)文件路徑讀取著色器文件中的源碼字符串,并將其轉(zhuǎn)換為c中的字符串,類型為GLchar
    • 根據(jù)傳入的著色器類型type,調(diào)用glCreateShader函數(shù)創(chuàng)建一個帶有唯一標識ID的著色器,此時著色器中并沒有附加相對應(yīng)的源碼
    • 將讀取的著色器源碼通過glShaderSource函數(shù)附加到創(chuàng)建的shader上,并將shader的ID返回給loadShaders函數(shù)中shader,以ID來獲取并使用對應(yīng)的著色器
    • 通過glCompileShader函數(shù)將shader上附加的源碼編譯成目標代碼
//編譯shader
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
    //1.讀取文件路徑字符串
    NSString* content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    const GLchar* source = (GLchar *)[content UTF8String];

    NSLog(@"content %@, source %s", content, source);
    //2.創(chuàng)建一個shader(根據(jù)type類型)
    *shader = glCreateShader(type);

    //3.將著色器源碼附加到著色器對象上。
    //參數(shù)1:shader,要編譯的著色器對象 *shader
    //參數(shù)2:numOfStrings,傳遞的源碼字符串?dāng)?shù)量 1個
    //參數(shù)3:strings,著色器程序的源碼(真正的著色器程序源碼)
    //參數(shù)4:lenOfStrings,長度,具有每個字符串長度的數(shù)組,或NULL,這意味著字符串是NULL終止的
    glShaderSource(*shader, 1, &source,NULL);
    
    NSLog(@"shader %d", *shader);

    //4.把著色器源代碼編譯成目標代碼
    glCompileShader(*shader);
}
  • 【loadShaders函數(shù)】:分別將頂點著色器和片元著色器編譯完成后,并返回著色器對應(yīng)的ID,然后通過glAttachShader函數(shù)將頂點和片元的shader分別附著到program上,然后釋放不再使用的shader,并賦值給全局的program
    loadShaders函數(shù)流程
//加載shader
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
    //1.定義2個零時著色器對象
    GLuint verShader, fragShader;
    //創(chuàng)建program
    GLint program = glCreateProgram();

    //2.編譯頂點著色程序、片元著色器程序
    //參數(shù)1:編譯完存儲的底層地址
    //參數(shù)2:編譯的類型,GL_VERTEX_SHADER(頂點)、GL_FRAGMENT_SHADER(片元)
    //參數(shù)3:文件路徑
    [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
    
    NSLog(@"verShader %d, fragShader: %d", verShader, fragShader);

    //3.創(chuàng)建最終的程序
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);

    //4.釋放不需要的shader
    glDeleteShader(verShader);
    glDeleteShader(fragShader);

    return program;
}

鏈接program

  • 通過glLinkProgram函數(shù)鏈接program
  • 可以通過glGetProgramiv函數(shù)通過制定值GL_LINK_STATUS獲取鏈接的狀態(tài),判斷鏈接是成功還是失敗
  • 如果鏈接失敗,可以通過glGetProgramIngoLog函數(shù)獲取錯誤信息日志,根據(jù)錯誤信息一致去排查問題

注1:如果提示鏈接失敗,需要做如下檢查

  • 一般shader 有問題:
    • 1、檢查 編寫的shader 是否有誤
    • 2、檢查 傳遞值的地方 標識是否寫錯了
  • 如果確認shader沒有問題,再看看layoutSubviews中函數(shù)調(diào)用是否有誤(博主遇到的一個問題就是由于調(diào)用寫錯了,導(dǎo)致鏈接失敗,最關(guān)鍵的是還沒有錯誤信息,通過斷點調(diào)試發(fā)現(xiàn)shader一直是0,著色器文件也是正長的,然后瘋狂排查問題,最后發(fā)現(xiàn)是在調(diào)用時粗心了,context的調(diào)用寫成了layer的調(diào)用)

注2:如果提示鏈接成功,但是圖片沒有加載出來

  • 檢查RenderBuffer和FrameBuffer的設(shè)置是否有問題

使用program
通過glUseProgram函數(shù)來使用鏈接成功的program

 glUseProgram(self.myPrograme);

頂點數(shù)據(jù)設(shè)置及處理

通過數(shù)組存儲頂點數(shù)據(jù),并將頂點坐標和紋理坐標讀取到自定義的頂點著色器中
分為以下三步

  • 設(shè)置頂點數(shù)據(jù):主要是初始化頂點坐標和紋理坐標
  • 開辟頂點緩存區(qū):用于將頂點數(shù)據(jù)從CPU拷貝至GPU
  • 打開頂點/片元的通道

設(shè)置頂點數(shù)據(jù)沒什么好說的,就是以一個一維數(shù)組,下面說說后面的兩步

開辟頂點緩存區(qū)
開啟頂點緩存區(qū),這部分其實跟之前使用GLKit框架開啟緩存區(qū)步驟是一致的,沒什么變化,有以下四步

  • 通過GLuint定義一個頂點緩存區(qū)ID
  • 通過glGenBuffers函數(shù),申請一個頂點緩存區(qū)標識符
  • 通過glBindBuffers函數(shù),將緩存區(qū)的標識符綁定到GL_ARRAY_BUFFER
  • 通過glBufferData函數(shù),將頂點數(shù)據(jù)copy到GPU中
    GLuint attrBuffer;
    glGenBuffers(1, &attrBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

打開頂點/片元的通道
在iOS中,attribute通道默認是關(guān)閉的,需要手動開啟,而數(shù)據(jù)有頂點坐標和紋理坐標兩種,需要分別開啟兩次,這里的開啟與GLKit框架中是有所區(qū)別的,

  • 由于本案例使用的是自定義著色器,所以需要自己獲取vertex attribut的入口,
  • 在GLKit中使用的是封裝好的固定著色器,直接指定入口即可

使用自定義著色器打開通道,一般有以下三步(相對于GLKit而言,只多了一個獲取入口的步驟,后面兩步是沒有多大變化的)

  • 通過glGetAttribLocation函數(shù),獲取vertex attribute的入口,需要傳入兩個參數(shù),一個是program,一個是自定義著色器文件中變量名字符串,這里著重強調(diào)下!??!第二個參數(shù)的字符串必須與著色器文件中對應(yīng)的變量名保持一致!
  • 通過glEnableVertexAttribArray函數(shù),設(shè)置合適的格式從buffer里讀取數(shù)據(jù),即設(shè)置讀取入口
  • 通過glVertexAttribPointer函數(shù),設(shè)置讀取方式
//     (1)注意:第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:position保持一致
    GLuint position = glGetAttribLocation(self.myPrograme, "position");
//     (2).設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
    glEnableVertexAttribArray(position);
//     (3).設(shè)置讀取方式
//          參數(shù)1:index,頂點數(shù)據(jù)的索引
//          參數(shù)2:size,每個頂點屬性的組件數(shù)量,1,2,3,或者4.默認初始值是4.
//                 參數(shù)3:type,數(shù)據(jù)中的每個組件的類型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默認初始值為GL_FLOAT
//          參數(shù)4:normalized,固定點數(shù)據(jù)值是否應(yīng)該歸一化,或者直接轉(zhuǎn)換為固定值。(GL_FALSE)
//          參數(shù)5:stride,連續(xù)頂點屬性之間的偏移量,默認為0;
//          參數(shù)6:指定一個指針,指向數(shù)組中的第一個頂點屬性的第一個組件。默認為0
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
    
//    9、處理紋理數(shù)據(jù)
//    (1).glGetAttribLocation,用來獲取vertex attribute的入口的.
//    注意:第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:textCoordinate保持一致
//    (2).設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
//    (3).設(shè)置讀取方式
    GLuint textColor = glGetAttribLocation(self.myPrograme, "textCoordinate");
    glEnableVertexAttribArray(textColor);
    glVertexAttribPointer(textColor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL+3);

加載紋理

這部分的內(nèi)容主要是將png/jpg圖片解壓成位圖,并通過自定義著色器讀取紋理每個像素點的紋素,包含兩部分
setupTexture函數(shù)
將png/jpg解壓成位圖,加載成紋理數(shù)據(jù),其中紋理的解壓縮使用的都是CoreGraphic,加載紋理的流程如下圖

setupTexture函數(shù)流程

  • 紋理解壓縮:將UIImage轉(zhuǎn)換為CGImageRef
  • 圖片重繪:使用CGContextRef常見的上下文,調(diào)用CGContextDrawImage函數(shù)使用默認方式進行繪制,再繪制之前,需要獲取圖片的大小、寬、高等數(shù)據(jù),因為繪制時需要使用這些數(shù)據(jù)
  • 綁定紋理:通過glBindTexture函數(shù)綁定,當(dāng)只有一個紋理的時候,默認的紋理ID是0,且0一直是激活狀態(tài),因此是可以省略glGenTexture
  • 設(shè)置紋理屬性:通過glTexParameteri函數(shù)分別設(shè)置 放大/縮小的過濾方式S/T的環(huán)繞模式
  • 載入紋理:通過glTexImage2D函數(shù)載入紋理,載入完成后,釋放指向紋理數(shù)據(jù)的指針
//     從圖片中加載紋理
- (GLuint)setupTexture: (NSString *)fileName {
//    1、將UIImage轉(zhuǎn)換為CGImageRef & 判斷圖片是否獲取成功
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    
    if (!spriteImage) {
        NSLog(@"Failed to lead image %@", fileName);
        exit(1);
    }
    
//    2、讀取圖片的大小、寬和高
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    
//    3、獲取圖片字節(jié)數(shù) 寬*高*4(RGBA)
    GLubyte *spriteData = (GLubyte *)calloc(width*height*4, sizeof(GLubyte));
    
//    4、創(chuàng)建上下文
    /*
    參數(shù)1:data,指向要渲染的繪制圖像的內(nèi)存地址
    參數(shù)2:width,bitmap的寬度,單位為像素
    參數(shù)3:height,bitmap的高度,單位為像素
    參數(shù)4:bitPerComponent,內(nèi)存中像素的每個組件的位數(shù),比如32位RGBA,就設(shè)置為8
    參數(shù)5:bytesPerRow,bitmap的沒一行的內(nèi)存所占的比特數(shù)
    參數(shù)6:colorSpace,bitmap上使用的顏色空間  kCGImageAlphaPremultipliedLast:RGBA
    */
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    NSLog(@"kCGImageAlphaPremultipliedLast %d", kCGImageAlphaPremultipliedLast);
    
    
//    5、在CGContextRef上 --- 將圖片繪制出來
    /*
    CGContextDrawImage 使用的是Core Graphics框架,坐標系與UIKit 不一樣。UIKit框架的原點在屏幕的左上角,Core Graphics框架的原點在屏幕的左下角。
    CGContextDrawImage
    參數(shù)1:繪圖上下文
    參數(shù)2:rect坐標
    參數(shù)3:繪制的圖片
    */
    CGRect rect = CGRectMake(0, 0, width, height);
    
//    6、使用默認方式繪制
    CGContextDrawImage(spriteContext, rect, spriteImage);
    
//    7、畫圖完畢就釋放上下文
    CGContextRelease(spriteContext);
    
//    8、綁定紋理到默認的紋理ID
    glBindTexture(GL_TEXTURE_2D, 0);
    
//    9、設(shè)置紋理屬性
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    float fw = width, fh = height;
//    10、載入紋理2D數(shù)據(jù)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    
//    11、釋放spriteData
    free(spriteData);
    return 0;
}
  • 【總結(jié)】圖片解壓縮的一般步驟
    • 將UIImage 轉(zhuǎn)換為CGImageRef & 判斷圖片是否獲取成功
    • 設(shè)置圖的大小,寬和高
    • 獲取圖片字節(jié)數(shù) = 寬4(rgba)
    • 創(chuàng)建CGContextRef上下文
    • 使用CGContextDrawImage繪制圖片

設(shè)置紋理采樣器
主要是獲取紋理中對應(yīng)像素點的的顏色值,即紋素

  • 通過glGetUniformLocation函數(shù),獲取fragment uniform的入口,需要傳入兩個參數(shù),一個是program,一個是自定義片元著色器文件中變量名字符串colorMap,這里著重強調(diào)下?。?!第二個參數(shù)的字符串必須與著色器文件中對應(yīng)的變量名保持一致!
  • 通過glUniform1i函數(shù)獲取紋素,有兩個參數(shù),第一個參數(shù)是 fragment uniform的入口,本質(zhì)也是一個ID,第二個參數(shù)是紋理的ID,使用的是默認的ID 0
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);

繪制

開始繪制,存儲到RenderBuffer,從RenderBuffer將圖片顯示到屏幕上

  • 調(diào)用glDrawArrays函數(shù)指定圖元連接方式進行繪制
  • context調(diào)用presentRenderbuffer函數(shù)將繪制好的圖片渲染到屏幕上進行顯示
//    12、繪制
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
//    13、從渲染緩存區(qū)顯示到屏幕上
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];

完整的代碼見github - 10_GLSL_01_加載圖片

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容