GLSL實(shí)現(xiàn)圖片加載及紋理翻轉(zhuǎn)策略

想要使用OpenGL ES完成圖片的加載,大概的流程可以分為以下幾點(diǎn):
1.創(chuàng)建圖層
2.創(chuàng)建上下文
3.清空緩存區(qū)
4.設(shè)置RenderBuffer
5.設(shè)置FrameBuffer
6.開始繪制

準(zhǔn)備工作

因?yàn)槭鞘褂米远x著色器,所以我們需要新建兩個空文件,分別命名為shaderv.vsh和shaderf.fsh,依次來區(qū)分頂點(diǎn)和片元著色器(注意:在這兩個文件中添加注釋有可能會誘發(fā)一些錯誤)。

//shaderv.vsh
attribute vec4 position;            //所顯示圖片的頂點(diǎn)
attribute vec2 textCoordinate;      //紋理坐標(biāo)
varying lowp vec2 varyTextCoord;    //也是紋理坐標(biāo),作用是把紋理坐標(biāo)從頂點(diǎn)傳到片元

void main(){
    varyTextCoord = textCoordinate;    //頂點(diǎn)到片元了
    gl_Position = position;          //頂點(diǎn)原樣輸出   gl_Position:內(nèi)建變量,最終的結(jié)果
}
//shaderf.fsh
precision highp float;             //定義float類型的精度
varying lowp vec2 varyTextCoord;   //紋理坐標(biāo)
uniform sampler2D colorMap;        //傳進(jìn)來的紋理

void main(){
    gl_FragColor = texture2D(colorMap, varyTextCoord);//紋理圖片,紋理坐標(biāo)
}

創(chuàng)建圖層

這里有一點(diǎn)需要注意,就是我們需要重寫layerClass,將其返回的圖層從CALayer替換成CAEAGLLayer。

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

    //3.設(shè)置描述屬性,這里設(shè)置不維持渲染內(nèi)容以及顏色格式為RGBA8
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];
  
}

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

創(chuàng)建上下文

-(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.設(shè)置圖形上下文
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"setCurrentContext failed!");
        return;
    }
    //5.將局部context,變成全局的
    self.myContext = context;
}

清空緩存區(qū)

-(void)deleteRenderAndFrameBuffer
{
    glDeleteBuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    
    glDeleteBuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;   
}

設(shè)置RenderBuffer

-(void)setupRenderBuffer
{
    //定義一個緩存區(qū)ID
    GLuint buffer; 
    //申請一個緩存區(qū)標(biāo)志
    glGenRenderbuffers(1, &buffer);
    self.myColorRenderBuffer = buffer;
    //將標(biāo)識符綁定到GL_RENDERBUFFER
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    //將可繪制對象drawable object's  CAEAGLLayer的存儲綁定到OpenGL ES renderBuffer對象
    //和myEagLayer綁定
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}

設(shè)置FrameBuffer

設(shè)置兩個緩沖區(qū)的流程基本相比,只有最后設(shè)置完后,需要將兩個緩沖區(qū)綁定并捆綁到附著點(diǎn)上。

-(void)setupFrameBuffer
{
    //1.定義一個緩存區(qū)ID
    GLuint buffer;
    //2.申請一個緩存區(qū)標(biāo)志
    glGenRenderbuffers(1, &buffer);  
    //3.
    self.myColorFrameBuffer = buffer;
    //4.
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    /*生成幀緩存區(qū)之后,則需要將renderbuffer跟framebuffer進(jìn)行綁定,
     調(diào)用glFramebufferRenderbuffer函數(shù)進(jìn)行綁定到對應(yīng)的附著點(diǎn)上,后面的繪制才能起作用
     */
    //GL_COLOR_ATTACHMENT0附著點(diǎn)
    //5.將渲染緩存區(qū)myColorRenderBuffer 通過glFramebufferRenderbuffer函數(shù)綁定到 GL_COLOR_ATTACHMENT0上。
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);   
}

開始繪制

-(void)renderLayer
{
    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);
    
    //2.讀取頂點(diǎn)著色程序、片元著色程序
    NSString *vertFile = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
    NSString *fragFile = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
    
    NSLog(@"vertFile:%@",vertFile);
    NSLog(@"fragFile:%@",fragFile);
    
    //3.加載shader
    self.myPrograme = [self loadShaders:vertFile Withfrag:fragFile];
    
    //4.鏈接
    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;
    }
    
    NSLog(@"Program Link Success!");
    glUseProgram(self.myPrograme);
    
    //5.設(shè)置頂點(diǎn)、紋理坐標(biāo)
    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,
    };
    
    
    //6.-----處理頂點(diǎn)數(shù)據(jù)--------
    GLuint attrBuffer;
    glGenBuffers(1, &attrBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

    //7.將頂點(diǎn)數(shù)據(jù)通過myPrograme中的傳遞到頂點(diǎn)著色程序的position
    GLuint position = glGetAttribLocation(self.myPrograme, "position");
    
    //設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
    glEnableVertexAttribArray(position);
    
    //設(shè)置讀取方式
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
    
    //8.----處理紋理數(shù)據(jù)-------
    GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");
    glEnableVertexAttribArray(textCoor);
    glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
    
    //9.加載紋理
    [self setupTexture:@"touxiang"];
    
    //10. 設(shè)置紋理采樣器 sampler2D
    glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
    
    //11.繪圖
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
    //12.從渲染緩存區(qū)顯示到屏幕上
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER]; 
}

//從圖片中加載紋理
- (GLuint)setupTexture:(NSString *)fileName {
    //1、將 UIImage 轉(zhuǎn)換為 CGImageRef
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    if (!spriteImage) {
        NSLog(@"Failed to load 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)建上下文
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    
    //5、在CGContextRef上--> 將圖片繪制出來
    CGRect rect = CGRectMake(0, 0, width, height);
   
    //6.使用默認(rèn)方式繪制
    CGContextDrawImage(spriteContext, rect, spriteImage);
   
    //7、畫圖完畢就釋放上下文
    CGContextRelease(spriteContext);
    
    //8、綁定紋理到默認(rèn)的紋理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;
}

紋理翻轉(zhuǎn)

這樣我們就實(shí)現(xiàn)了一個圖片的加載,但是運(yùn)行后發(fā)現(xiàn)加載的圖片是倒置的,這是因?yàn)榧y理坐標(biāo)的原點(diǎn)(0,0)是在左下角,而屏幕的坐標(biāo)原點(diǎn)(0,0)是在左上角,此時我們想要將圖片顯示正確的話,有以下幾張方法:

  1. GLKTextureLoader載入紋理時
    載入時options設(shè)置 GLKTextureLoaderOriginBottomLeft 。
NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft : @(YES)};
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];

2.解壓圖片時,將圖片翻轉(zhuǎn)

CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;

size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
GLubyte *spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));

CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
  
CGRect rect = CGRectMake(0, 0, width, height);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextDrawImage(spriteContext, rect, spriteImage); 

CGContextRelease(spriteContext);
glBindTexture(GL_TEXTURE_2D, 0);
  1. 設(shè)置旋轉(zhuǎn)矩陣,shader中旋轉(zhuǎn)頂點(diǎn),不翻轉(zhuǎn)紋理
GLuint rotate = glGetUniformLocation(self.program, "rotateMatrix");
float radians = M_PI;
float sinR = sin(radians);
float cosR = cos(radians);
GLfloat zRotation[16] = {
    cosR, -sinR, 0, 0,
    sinR, cosR, 0, 0,
    0, 0, 1.0, 0,
    0.0, 0, 0, 1.0
};
glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);

4.設(shè)置翻轉(zhuǎn)的紋理坐標(biāo)映射關(guān)系

GLfloat datas[] =
{
    0.5f, -0.5f, 0.0f,   1.0f, 1.0f, //右下
    -0.5f, 0.5f, 0.0f,   0.0f, 0.0f, // 左上
    -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, // 左下
    0.5f, 0.5f, 0.0f,    1.0f, 0.0f, // 右上
    -0.5f, 0.5f, 0.0f,   0.0f, 0.0f, // 左上
    0.5f, -0.5f, 0.0f,   1.0f, 1.0f, // 右下
};
  1. 修改頂點(diǎn)著色器的紋理坐標(biāo)
attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
    varyTextCoord = vec2(textCoordinate.x, 1.0-textCoordinate.y);
    gl_Position = position;
}
  1. 修改頂點(diǎn)著色器的紋理坐標(biāo)
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
    lowp coord = vec2(varyTextCoord.x, 1.0-varyTextCoord.y);
    gl_FragColor = texture2D(colorMap, coord);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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