005-自定義shader加載圖片

OpenGL ES通過CAEAGLLayer類連接到Core Animation,該類是Core Animation層的一種特殊類型,其內容來自OpenGL ES渲染緩沖區(qū)。Core Animation將渲染緩沖區(qū)的內容與其他圖層合成, 并在屏幕上顯示結果圖像。

Core Animation與OpenGL ES共享渲染緩沖區(qū)

要將核心動畫層CAEAGLLayer用于OpenGL ES渲染,請執(zhí)行以下操作:

  1. 創(chuàng)建一個CAEAGLLayer對象并配置其屬性
  2. 分配一個OpenGL ES上下文,并使其成為當前上下文
  3. 創(chuàng)建幀緩沖區(qū)對象
  4. 創(chuàng)建一個顏色渲染緩沖區(qū)
  5. 檢索顏色渲染緩沖區(qū)的高度和寬度。
  6. 分配并附加一個渲染緩存區(qū)。
  7. 測試幀緩沖區(qū)的完整性。
  8. 創(chuàng)建著色器加載glsh文件
  9. 鏈接著色器
  10. 使用著色器
  11. 處理頂點
  12. 開啟頂點著色器通道
  13. 加載紋理
  14. 將渲染結果繪制到緩沖區(qū)
  15. 通過將CAEAGLLayer將緩沖區(qū)數據渲染到屏幕上

shader

著色器創(chuàng)建流程

OpenGL ES通過CAEAGLLayer類連接到Core Animation,該類是Core Animation層的一種特殊類型,其內容來自OpenGL ES渲染緩沖區(qū)。Core Animation將渲染緩沖區(qū)的內容與其他圖層合成, 并在屏幕上顯示結果圖像。

要將核心動畫層用于OpenGL ES渲染,請執(zhí)行以下操作:
準備屬性

#import <OpenGLES/ES2/gl.h>
GLuint textureID;
@property(nonatomic,strong)CAEAGLLayer *myEagLayer;
@property(nonatomic,strong)EAGLContext *myContext;
@property(nonatomic,assign)GLuint myColorRenderBuffer;
@property(nonatomic,assign)GLuint myColorFrameBuffer;
@property(nonatomic,assign)GLuint myPrograme;
@end

  1. 創(chuàng)建一個CAEAGLLayer對象并配置其屬性。
    OpenGL ES通過CAEAGLLayer類連接到Core Animation,該類是Core Animation層的一種特殊類型,其內容來自OpenGL ES渲染緩沖區(qū)。為了獲得最佳性能,請將圖層opaque屬性的值設置為YES。

(可選)通過將值的新字典分配給對象的drawableProperties屬性來配置渲染表面的表面屬性CAEAGLLayer。您可以為渲染緩沖區(qū)指定像素格式,并指定將渲染緩沖區(qū)的內容發(fā)送到Core Animation后是否將其丟棄。

/*
重寫layerClass,將CCView返回的圖層從CALayer替換成CAEAGLLayer
*/
+(Class)layerClass
{
    return [CAEAGLLayer class];
}

//1.設置圖層
-(void)setupLayer
{
    //1.創(chuàng)建特殊圖層
    self.myEagLayer = (CAEAGLLayer *)self.layer;
    
    //2.設置scale
    [self setContentScaleFactor:[[UIScreen mainScreen]scale]];


    //3.設置描述屬性,這里設置不維持渲染內容以及顏色格式為RGBA8
    /*
     kEAGLDrawablePropertyRetainedBacking  表示繪圖表面顯示后,是否保留其內容。
     kEAGLDrawablePropertyColorFormat
         可繪制表面的內部顏色緩存區(qū)格式,這個key對應的值是一個NSString指定特定顏色緩存區(qū)對象。默認是kEAGLColorFormatRGBA8;
     
         kEAGLColorFormatRGBA8:32位RGBA的顏色,4*8=32位
         kEAGLColorFormatRGB565:16位RGB的顏色,
         kEAGLColorFormatSRGBA8:sRGB代表了標準的紅、綠、藍,即CRT顯示器、LCD顯示器、投影機、打印機以及其他設備中色彩再現所使用的三個基本色素。sRGB的色彩空間基于獨立的色彩坐標,可以使色彩在不同的設備使用傳輸中對應于同一個色彩坐標體系,而不受這些設備各自具有的不同色彩坐標的影響。
     */
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];
  
}
  1. 分配一個OpenGL ES上下文,并使其成為當前上下文。請參閱配置OpenGL ES上下文。
-(void)setupContext
{
    //1.指定OpenGL ES 渲染API版本,我們使用2.0
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    //2.創(chuàng)建圖形上下文
    EAGLContext *context = [[EAGLContext alloc]initWithAPI:api];
    //3.判斷是否創(chuàng)建成功
    if (!context) {
        NSLog(@"Create context failed!");
        return;
    }
    //4.設置圖形上下文
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"setCurrentContext failed!");
        return;
    }
    //5.將局部context,變成全局的
    self.myContext = context;
    
}
  1. 創(chuàng)建幀緩沖區(qū)對象(如上面的創(chuàng)建離屏幀緩沖區(qū)對象中所述)。

清空緩存區(qū) 默認情況下,您必須假定應用程序呈現渲染緩沖區(qū)后,渲染緩沖區(qū)的內容將被丟棄。這意味著您的應用每次呈現框架時,都必須在呈現新框架時完全重新創(chuàng)建框架的內容。出于這個原因,上面的代碼總是擦除顏色緩沖區(qū)。

-(void)deleteRenderAndFrameBuffer
{
    /*
     buffer分為frame buffer 和 render buffer2個大類。
     其中frame buffer 相當于render buffer的管理者。
     frame buffer object即稱FBO。
     render buffer則又可分為3類。colorBuffer、depthBuffer、stencilBuffer。
     */
    
    glDeleteBuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    
    glDeleteBuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
    
}
  1. 創(chuàng)建一個渲染緩沖區(qū),通過調用上下文的renderbufferStorage:fromDrawable:方法并將圖層對象作為參數來分配其存儲空間。寬度,高度和像素格式取自圖層,用于為渲染緩沖區(qū)分配存儲空間。
GLuint colorRenderbuffer;
glGenRenderbuffers(1, &colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

//4.設置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's  CAEAGLLayer的存儲綁定到OpenGL ES renderBuffer對象
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}

注意: 當“核心動畫”層的邊界或屬性發(fā)生變化時,您的應用應重新分配渲染緩沖區(qū)的存儲。如果不重新分配渲染緩沖區(qū),則渲染緩沖區(qū)的大小將與圖層的大小不匹配;在這種情況下,Core Animation可以縮放圖像的內容以適合圖層。

  1. 檢索顏色渲染緩沖區(qū)的高度和寬度。
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

在較早的示例中,顯式提供了渲染緩沖區(qū)的寬度和高度,以為緩沖區(qū)分配存儲空間。在這里,代碼在分配了其存儲空間之后,從顏色渲染緩沖區(qū)中檢索寬度和高度。您的應用程序之所以這樣做是因為彩色渲染緩沖區(qū)的實際尺寸是根據圖層的邊界和比例因子計算的。附加到幀緩沖區(qū)的其他渲染緩沖區(qū)必須具有相同的尺寸。除了使用高度和寬度來分配深度緩沖區(qū)外,還可以使用它們來分配OpenGL ES視口并幫助確定應用程序的紋理和模型所需的細節(jié)級別。請參閱支持高分辨率顯示器。

  1. 分配幀緩沖區(qū)并附加一個渲染緩沖區(qū)。
屏幕快照 2019-10-08 下午9.59.32.png
-(void)setupFrameBuffer
{
    //1.定義一個緩存區(qū)ID
    GLuint buffer;
    
    //2.申請一個緩存區(qū)標志
    glGenFramebuffers(1, &buffer);
    
    //3.
    self.myColorFrameBuffer = buffer;
    
    //4.
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    
    /*生成幀緩存區(qū)之后,則需要將renderbuffer跟framebuffer進行綁定,
     調用glFramebufferRenderbuffer函數進行綁定到對應的附著點上,后面的繪制才能起作用
     */
    
    //5.將渲染緩存區(qū)myColorRenderBuffer 通過glFramebufferRenderbuffer函數綁定到 GL_COLOR_ATTACHMENT0上。
    //渲染緩沖區(qū)是存放渲染結果的
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
    
    //6. 檢測幀緩沖區(qū)是否正確, 在OpenGL ES中所以的驗證狀態(tài)的函數, 盡可能的在調試時使用, 因為這些函數會占用資源
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
    if(status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"failed to make complete framebuffer object %x", status);
    }
    
}
  1. 測試幀緩沖區(qū)的完整性
    檢測幀緩沖區(qū)是否正確, 在OpenGL ES中所以的驗證狀態(tài)的函數, 盡可能的在調試時使用, 因為這些函數會占用資源

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
    if(status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"failed to make complete framebuffer object %x", status);
    }
  1. 創(chuàng)建著色器

#pragma mark --shader
//加載shader
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
    //1.定義2個零時著色器對象
    GLuint verShader, fragShader;
    
    NSString* content = "著色器文件";
    const GLchar* source = (GLchar *)[content UTF8String];
    
    //2.創(chuàng)建一個shader(根據type類型)編譯的類型,GL_VERTEX_SHADER(頂點)、GL_FRAGMENT_SHADER(片元)
    verShader = glCreateShader(type);
    
    //3.將著色器源碼附加到著色器對象上。
    //參數1:shader,要編譯的著色器對象 *shader
    //參數2:numOfStrings,傳遞的源碼字符串數量 1個
    //參數3:strings,著色器程序的源碼(真正的著色器程序源碼)
    //參數4:lenOfStrings,長度,具有每個字符串長度的數組,或NULL,這意味著字符串是NULL終止的
    glShaderSource(*shader, 1, &source,NULL);
    
    //4.把著色器源代碼編譯成目標代碼
    glCompileShader(*shader);
}

  1. 鏈接著色器到program, GLSL編譯
    在OpenGL ES中, 每個program對象有且僅有一個Vertex Shader對象和一個Fragment Shader 對象鏈接它. vsh/fsh著色器 -> program ->GPU
//創(chuàng)建著色器最終的程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);

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

//鏈接著色器程序
glLinkProgram(self.myPrograme);

GLint linkStatus;
//獲取鏈接狀態(tài)
glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
    GLchar message[512];
    glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
    NSString *messageString = [NSString stringWithUTF8String:message];
    NSLog(@"Program Link Error:%@",messageString);
    return;
}


  1. GLSL編譯 使用著色器program
//使用program
glUseProgram(self.myPrograme);
  1. 處理頂點
//前3個是頂點坐標,后2個是紋理坐標
GLfloat attrArr[] =
{
    0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
    
    0.5f, 0.5f, -1.0f,      1.0f, 1.0f,
    -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
};

//(1)頂點緩存區(qū)
GLuint attrBuffer;
//(2)申請一個緩存區(qū)標識符
glGenBuffers(1, &attrBuffer);
//(3)將attrBuffer綁定到GL_ARRAY_BUFFER標識符上
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
//(4)把頂點數據從CPU內存復制到GPU上
/*
 GL_STATIC_DRAW 適用于多次渲染且其內容僅指定一次且永不更改的頂點緩沖區(qū)。
 GL_DYNAMIC_DRAW 適用于多次渲染且其內容在渲染循環(huán)中更改的頂點緩沖區(qū)。
 GL_STREAM_DRAW 用于頂點緩沖區(qū),渲染次數很少,然后丟棄。
 */
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

  1. 開啟頂點著色器輸入變量通道
//(1)注意:第二參數字符串必須和shaderv.vsh中的輸入變量:position保持一致
GLuint position = glGetAttribLocation(self.myPrograme, "position");

//(2).設置合適的格式從buffer里面讀取數據, 被attribute修飾的默認是關閉通道, 需要使用glEnableVertexAttribArray打開, 其他修飾符不需要
glEnableVertexAttribArray(position);

//(3).設置讀取方式, 直接從VBO里面讀取數據
//參數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,連續(xù)頂點屬性之間的偏移量,默認為0;
//參數6:指定一個指針,指向數組中的第一個頂點屬性的第一個組件。默認為0
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);


//9.----處理紋理數據-------
//(1).glGetAttribLocation,用來獲取vertex attribute的入口的.
//注意:第二參數字符串必須和shaderv.vsh中的輸入變量:textCoordinate保持一致
GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");

//(2).設置合適的格式從buffer里面讀取數據
glEnableVertexAttribArray(textCoor);

//(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,連續(xù)頂點屬性之間的偏移量,默認為0;
//參數6:指定一個指針,指向數組中的第一個頂點屬性的第一個組件。默認為0
glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
  1. 加載紋理
//從圖片中加載紋理
- (GLuint)setupTexture:(NSString *)fileName {
    
    UIImage *image = [UIImage imageNamed:fileName];
    GLubyte *spriteData = [self getImageDataWithImage:image];

//    這里為什么不需要申請紋理操作
    //紋理申請的操作寫在紋理著色器里面了 texture2D(colorMap, varyTextCoord)
//    glGenTextures(1, &textureID);
    
//    第0個紋理是默認激活的
//    glActiveTexture(GL_TEXTURE0);
    //8、綁定紋理到默認的紋理ID(
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //9.設置紋理屬性
    /*
     參數1:紋理維度
     參數2:線性過濾、為s,t坐標設置模式
     參數3:wrapMode,環(huán)繞模式
     */
    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 = image.size.width, fh = image.size.height;
    
    //10.載入紋理2D數據
    /*
     參數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(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    
    //11.釋放spriteData
    free(spriteData);   
    return 0;
}
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), textureID);

  1. 將渲染結果繪制到緩沖區(qū)
glDrawArrays(GL_TRIANGLES, 0, 6);

  1. 通過將CAEAGLLayer將緩沖區(qū)數據渲染到屏幕上
/*
 可繪制對象CAEAGLLayer綁定到OpenGL ES renderBuffer對象
 [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
 */
//從綁定的GL_RENDERBUFFER里面拿出數據繪制到EAGLayer上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容