OpenGL 十 - 002、GLSL案例-紋理圖片繪制與翻轉


案例:使用編譯鏈接自定義的着色器(shader),用簡單的 glsl 語言來實現頂點、片元着色器,繪制圖形並進行簡單的變換。

思路:

 1.創建圖層

 2.創建上下文

 3.清空緩存區

 4.設置 RenderBuffer

 5.設置 FrameBuffer

 6.開始繪制

Demo 

一、准備工作

步驟 1~5

 1 - (void)layoutSubviews {
 2     
 3     // 1. 創建設置圖層
 4     // 設置 layer
 5     self.myEGLLayer = (CAEAGLLayer *)self.layer;
 6     
 7     // 設置 scale
 8     [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
 9     
10     // 設置屬性
11     /*
12      kEAGLDrawablePropertyRetainedBacking:繪圖表面顯示后,是否保留其內容。
13      kEAGLDrawablePropertyColorFormat:可繪制表面的內部顏色緩存區格式,這個key對應的值是一個NSString指定特定顏色緩存區對象。默認是kEAGLColorFormatRGBA8;
14      
15      kEAGLColorFormatRGBA8:32位RGBA的顏色,4*8=32位
16      kEAGLColorFormatRGB565:16位RGB的顏色,
17      kEAGLColorFormatSRGBA8:sRGB代表了標准的紅、綠、藍,即CRT顯示器、LCD顯示器、投影機、打印機以及其他設備中色彩再現所使用的三個基本色素,sRGB的色彩空間基於獨立的色彩坐標,可以使色彩在不同的設備使用傳輸中對應於同一個色彩坐標體系,而不受這些設備各自具有的不同色彩坐標的影響。
18      */
19 //    self.myEGLLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@(NO),kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8};
20     self.myEGLLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];
21 
22     
23     // 2. 設置上下文
24     self.myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
25     if (!self.myContext) {
26         NSLog(@"create context failed!");
27         return;
28     }
29     BOOL isSetSuccess = [EAGLContext setCurrentContext:self.myContext];
30     if (!isSetSuccess) {
31         return;
32     }
33     
34     
35     // 3. 清空緩沖區
36     glDeleteBuffers(1, &_myColorRenderBuffer);
37     self.myColorRenderBuffer = 0;
38     glDeleteBuffers(1, &_myColorFrameBuffer);
39     self.myColorFrameBuffer = 0;
40     
41     
42     // 4. 設置渲染緩沖區 renderBuffer
43     // 生成緩沖區 ID
44     GLuint rb;
45     glGenRenderbuffers(1, &rb);
46     self.myColorRenderBuffer = rb;
47     // 綁定緩沖區
48     glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
49     
50     // 綁到 context: contect 與 eagllayer綁定在一起
51     [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEGLLayer];
52     
53     
54     // 5. 設置幀緩沖區 FrameBuffer
55     glGenBuffers(1, &_myColorFrameBuffer);
56     glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
57     
58     // 渲染緩沖區 與 幀緩沖區綁在一起
59     /*
60      target:
61      attachment:將 renderBuffer 附着到frameBuffer的哪個附着點上
62      renderbuffertarget
63      renderbuffer
64      */
65     //    glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
66     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
67     
68     // 開始繪制
69     [self renderLayer];
70     
71 }

二、開始繪制

1、首先獲取並使用 鏈接后着⾊器對象,過程:

  a1、創建⼀個頂點着⾊器對象和⼀個⽚段着⾊器對象

  b1、將源代碼鏈接到每個着⾊器對象

  c1、編譯着⾊器對象

  d1、創建⼀個程序對象

  e1、將編譯后的着⾊器對象連接到程序對象

  f1、鏈接程序對象

a1、着色器:

頂點着色器代碼:

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

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

片元着色器代碼:

precision highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;

void main() { gl_FragColor = texture2D(colorMap, varyTextCoord); }

注意:使用 .vsh / .fsh 區分頂點、片元着色器 --> .vsh --> 頂點着色器 / .fsh --> 片元着色器 --> .vsh / .fsh 文件,只是用來給開發者區分着色器代碼。其本質是一串字符串。

 

b1 ~ f1 過程:

 1 - (void)renderLayer {
 2     
 3     glClearColor(0.7, 0.7, 0.7, 1);
 4     glClear(GL_COLOR_BUFFER_BIT);// 清空顏色緩沖區
 5     
 6     /// 1. 設置視口
 7     CGFloat mainScale = [UIScreen mainScreen].scale;
 8     glViewport(self.frame.origin.x * mainScale, self.frame.origin.y * mainScale, self.frame.size.width * mainScale, self.frame.size.height * mainScale);
 9     
10     /// 2. 讀取着色器代碼
11     // 路徑
12     NSString *verPath = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
13     NSString *fragPath = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
14     
15     /// 3. 加載着色器
16     self.myProgram = [self loadShadersWithVertex:verPath Withfrag:fragPath];
17     
18     /// 4. 鏈接 program
19     glLinkProgram(self.myProgram);
20     // 獲取連接狀態
21     GLint linkStatus;
22     glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
23     if (linkStatus == GL_FALSE) {// 鏈接出錯
24         // 獲取錯誤信息 log
25         GLchar message[512];
26         glGetProgramInfoLog(self.myProgram, sizeof(message), 0, &message[0]);
27         NSString *messageString = [NSString stringWithUTF8String:message];
28         NSLog(@"Program Link Error:%@",messageString);
29         return;
30     }
31     
32     /// 5. 使用 program
33     glUseProgram(self.myProgram);
34  }

加載編譯着色器:

 1 // 加載着色器
 2 // 頂點着色器 和 片元着色器 的代碼傳進來(.vsh  .fsh)
 3 -(GLuint)loadShadersWithVertex:(NSString *)vert Withfrag:(NSString *)frag {
 4     
 5     // 1.定義 着色器
 6     GLuint verShader, fragShader;
 7     
 8     // 2.創建程序 program
 9     GLint program = glCreateProgram();// 創建一個空的程序對象
10     
11     // 3.編譯着色器 --> 封裝一個方法 compileShaderWithShader:
12     [self compileShaderWithShader:&verShader shaderType:GL_VERTEX_SHADER filePath:vert];
13     [self compileShaderWithShader:&fragShader shaderType:GL_FRAGMENT_SHADER filePath:frag];
14     
15     // 4.attach shader, 將shader附着到 程序
16     glAttachShader(program, verShader);
17     glAttachShader(program, fragShader);
18     
19     //5.已附着好的 shader 刪掉,避免不必要的內存占用
20     glDeleteShader(verShader);
21     glDeleteShader(fragShader);
22     
23     return program;// 返回編譯好的程序
24 }
25 // 編譯着色器
26 /*
27  shader: 着色器 ID
28  type: 着色器類型
29  path: 着色器代碼文件路徑
30  */
31 - (void)compileShaderWithShader:(GLuint *)shader shaderType:(GLenum)type filePath:(NSString *)path {
32     
33     // 1.讀取文件路徑
34     NSString *file = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
35     // NSString 轉 C 的 char
36     const GLchar *source = (GLchar *)[file UTF8String];
37     
38     // 2.創建對應類型的shader
39     *shader = glCreateShader(type);
40     
41     // 3.讀取着色器源碼 將其附着到着色器對象上面
42     /* params:
43      shader: 要編譯的着色器對象 *shader
44      numOfStrings: 傳遞的源碼字符串數量 1個
45      參數3:strings: 着色器程序的源碼(真正的着色器程序源碼)
46      參數4:lenOfStrings: 長度,具有每個字符串長度的數組,或NULL,這意味着字符串是NULL終止的
47      */
48     //    glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length)
49     glShaderSource(*shader, 1, &source,NULL);
50     
51     // 4. 編譯
52     glCompileShader(*shader);
53 }

2、繪制型關信息的設置,過程:

  a2、設置坐標

  b2、加載紋理

  c2、draw 繪制

a2、設置坐標信息:

    /// 6. 設置頂點、紋理坐標
    // 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,
    };
    
    /// 7. copy 到頂點緩沖區
    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    // 頂點數據 copy 到緩沖區
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

    /// 8. 打開通道 傳遞數據
    // 8.1 頂點數據
    // 獲取通道 ID
    /*
     glGetAttribLocation(GLuint program, const GLchar *name)
     program:
     name: 給誰傳 --> .vsh 的 position
     */
    GLuint position = glGetAttribLocation(self.myProgram, "position");
    // 打開通道
    glEnableVertexAttribArray(position);
    // 讀數據
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);

    // 8.2 紋理數據 /*傳給誰 --> .vsh 的 textCoordinate */
    GLuint texture = glGetAttribLocation(self.myProgram, "textCoordinate");
    glEnableVertexAttribArray(texture);
    glVertexAttribPointer(texture, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float *)NULL + 3);
    
    
    /// 9. 加載紋理
    [self loadTexture];

b2、加載紋理

// 加載紋理
- (void)loadTexture {
    
    // 9.0 image 轉為 CGImageRef
    CGImageRef spriteImage = [UIImage imageNamed:@"0002"].CGImage;
    // 圖片是否獲取成功
    if (!spriteImage) {
        NSLog(@"Failed to load image ");
        exit(1);// 退出程序
    }
    // 獲取圖片寬高
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    // 獲取圖片字節數 寬*高*4(RGBA)
    GLubyte *spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    
    // 創建上下文
    /*
     data:指向要渲染的繪制圖像的內存地址
     width:bitmap 的寬度,單位為像素
     height:bitmap 的高度,單位為像素
     bitPerComponent:內存中像素的每個組件的位數,比如 32 位 RGBA,就設置為 8
     bytesPerRow:bitmap 的沒一行的內存所占的比特數
     colorSpace:bitmap 上使用的顏色空間  kCGImageAlphaPremultipliedLast:RGBA
     */
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);

    // 在 CGContextRef 上 --> 將圖片繪制出來
    /*
     CGContextDrawImage 使用的 Core Graphics 框架,坐標系與 UIKit 不一樣。UIKit 框架的原點在屏幕的左上角,Core Graphics 框架的原點在屏幕的左下角。
     CGContextDrawImage(CGContextRef  _Nullable c, CGRect rect, CGImageRef  _Nullable image)
     c:繪圖上下文
     rect:rect坐標
     image:繪制的圖片
     */
    CGRect rect = CGRectMake(0, 0, width, height);
    CGContextDrawImage(spriteContext, rect, spriteImage);

    // 繪完 釋放上下文
    CGContextRelease(spriteContext);
    
    // 9.1. 綁定紋理到默認的紋理ID
    glBindTexture(GL_TEXTURE_2D, 0);
    
    // 9.2. 設置紋理屬性
    /*
     glTexParameteri(GLenum target, GLenum pname, GLint param)
     target:紋理維度
     pname:線性過濾; 為s,t坐標設置模式
     param:wrapMode; 環繞模式
     */
    // 過濾方式
    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);
        
    // 9.3 載入紋理
    /* 載入紋理 glTexImage2D
    參數1:紋理維度,GL_TEXTURE_2D
    參數2:mip貼圖層次
    參數3:紋理單元存儲的顏色成分(從讀取像素圖中獲得)
    參數4:加載紋理寬度
    參數5:加載紋理的高度
    參數6:為紋理貼圖指定一個邊界寬度 0
    參數7、8:像素數據的數據類型, GL_UNSIGNED_BYTE無符號整型
    參數9:指向紋理圖像數據的指針
    */
    float fw = width, fh = height;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);

    // 9.4 釋放 sprite
    free(spriteData);
}

c2、繪制

    /// 10. 設置紋理采樣器
    glUniform1i(glGetUniformLocation(self.myProgram, "colorMap"), 0);
    
    /// 11.  繪制
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
    // 12. 從渲染緩沖區顯示到屏幕
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];

運行結果如下圖:

圖片是倒立的?如何解決呢?

UIKit 框架的原點是屏幕的左上角,Core Graphics 框架的原點是屏幕的左下角。

三、解決圖片倒立問題

1、加載紋理的繪制圖片過程中,將圖片通過轉換矩陣旋轉

   // 矩陣轉換 - 翻轉圖片
    // x、y 軸平移
    CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
    // y 平移
    CGContextTranslateCTM(spriteContext, 0, rect.size.height);
    // Y 軸方向 Scale -1 翻轉
    CGContextScaleCTM(spriteContext, 1.0, -1.0);
    // 平移回原點位置處
    CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
    // 重繪
    CGContextDrawImage(spriteContext, rect, spriteImage);

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,
//    };

// 紋理坐標反向對應
GLfloat attrArr[] = {

        0.5f, -0.5f, -1.0f,     1.0f, 1.0f,
        -0.5f, 0.5f, -1.0f,     0.0f, 0.0f,
        -0.5f, -0.5f, -1.0f,    0.0f, 1.0f,
        
        0.5f, 0.5f, -1.0f,      1.0f, 0.0f,
        -0.5f, 0.5f, -1.0f,     0.0f, 0.0f,
        0.5f, -0.5f, -1.0f,     1.0f, 1.0f,
    };

3、修改着色器代碼進行翻轉

3.1、紋理着色器中 對紋理 y 坐標進行翻轉

 

紋理着色器代碼:

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));
} 

3.2、頂點着色器中 對紋理坐標轉換

原理同 3.1

代碼:

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

void main() {

    //varyTextCoord = textCoordinate;
    varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
    gl_Position = position;
}

3.3、頂點着色器 傳入旋轉矩陣對頂點進行旋轉

頂點着色器代碼:

attribute vec4 position;
attribute vec2 textCoordinate;
uniform mat4 rotateMatrix; varying lowp vec2 varyTextCoord;
void main() { varyTextCoord = textCoordinate; vec4 vPos = position; vPos = vPos * rotateMatrix; gl_Position = vPos; }

繪制前  旋轉矩陣 --> 矩陣使用 uniform 修飾傳遞

    // 1. rotate等於shaderv.vsh中的 uniform 屬性,rotateMatrix
    GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
    
    // 2.獲取渲旋轉的弧度
    float radians = 180 * 3.14159f / 180.0f;
    // 3.求得弧度對於的sin\cos值
    float s = sin(radians);
    float c = cos(radians);
    
    // 4.因為在3D課程中用的是橫向量,在OpenGL ES用的是列向量
    // 參考Z軸旋轉矩陣
    GLfloat zRotation[16] = {
        c,-s,0,0,
        s,c,0,0,
        0,0,1,0,
        0,0,0,1
    };
    
    // 5.設置旋轉矩陣
    /*
     glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
     location : 對於shader 中的ID
     count : 個數
     transpose : 是否轉置矩陣
     value : 指針
     */
    glUniformMatrix4fv(rotate, 1, GL_FALSE, zRotation);

一般使用第一種方式,修改着色器代碼 每一個像素都要運行一次,成本太高。

 

 


免責聲明!

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



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