OpenGL ES實踐教程多重紋理實現圖像混合(圖像合成)

教程

OpenGL ES實踐教程1-Demo01-AVPlayer
OpenGL ES實踐教程2-Demo02-攝像頭採集數據和渲染
OpenGL ES實踐教程3-Demo03-Mirror
OpenGL ES實踐教程4-Demo04-VR全景視頻播放
其他教程請移步OpenGL ES文集

有簡書的開發者問我如何使用在一張大圖上貼一張小圖,原始的需求是在檢測人臉,在返回的範圍(矩形)內貼上一張圖片。
有幾點前提:

  • 儘量少消耗CPU;
  • 合成的數據是用於推流;
  • 圖片大小不一致;

說說如果沒有上述幾點前提下,可能的方案:

  • 1、使用UIKit,新建一個透明的View,大小和原圖像一致,在View上面對應的位置添加圖像;
  • 2、使用GPUImage,選擇一個filter,添加兩個原圖像作爲輸入;
  • 3、使用OpenGL ES,多重紋理;

因爲數據要用於推流,故而最簡單的方案1不行;
方案2可行,但是需要對GPUImage較爲熟悉;
方案3相對方案2簡單,同時對性能的要求最低,最爲符合。

本文探究如何使用OpenGL ES實現兩個圖片的混合。

核心思路

自定義shader,傳入兩個紋理和對應矩形的座標;
在像素着色器內判斷當前點的範圍,如果處於對應矩形內,則進行混合操作;

效果展示

具體細節

1、編譯鏈接GLProgram

爲了更方便開發,特引入Jeff LaMarche's GLProgram頭文件

This is Jeff LaMarche's GLProgram OpenGL shader wrapper class from his OpenGL ES 2.0 book.
A description of this can be found at his page on the topic:
http://iphonedevelopment.blogspot.com/2010/11/opengl-es-20-for-ios-chapter-4.html
I've extended this to be able to take programs as NSStrings in addition to files, for baked-in shaders

2、上傳頂點數據以及矩形座標

通過GLProgram-uniformIndex:-attributeIndex:方法,可以便捷的取到對應屬性的索引,再通過glUniform1iglUniform2f方法可以上次數據到OpenGL ES。

    GLuint texture0Uniform = [self.mProgram uniformIndex:@"myTexture0"];
    GLuint texture1Uniform = [self.mProgram uniformIndex:@"myTexture1"];
    GLuint leftBottomUniform = [self.mProgram uniformIndex:@"leftBottom"];
    GLuint rightTopUniform = [self.mProgram uniformIndex:@"rightTop"];
    GLuint displayPositionAttribute = [self.mProgram attributeIndex:@"position"];
    GLuint displayTextureCoordinateAttribute = [self.mProgram attributeIndex:@"textCoordinate"];

注意,shader裏面的attribute變量用-attributeIndex,uniform變量用-uniformIndex,紋理是uniform變量;
從頂點shader傳值到像素shader需要用varing變量。

3、上傳紋理數據

這是本文的重點之一。

  • 1、首先通過UIKit的方法,拿到圖像的UIImage對象;

  • 2、將UIImage轉換成CGImage,通過CoreGraphics取到二進制數據;

// 1獲取圖片的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);
    
    GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte)); //rgba共4個byte
    
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,
                                                       CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    
    // 3在CGContextRef上繪圖
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
    
    CGContextRelease(spriteContext);
  • 3、選擇對應的紋理單元,創建紋理對象並綁定;
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &_myTexture0);
    glBindTexture(GL_TEXTURE_2D, self.myTexture0);
  • 4、上傳紋理數據,並釋放原來申請的內存;
    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;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    //    glBindTexture(GL_TEXTURE_2D, 0);
    free(spriteData);

這裏需要理解兩個概念,紋理單元紋理對象
紋理單元我沒有找到很好的中文描述,講下我自己的理解。
紋理單元對應GPU支持的紋理數量,在shader的表現是以uniform變量的形式表現

uniform sampler2D myTexture0;
uniform sampler2D myTexture1;

iOS對紋理單元的數量限制如下


紋理對象指的是紋理的索引,通常是用glGenTextures生成,如下是生成一個紋理對象。

 

    glGenTextures(1, &_myTexture0);

一個紋理單元上有1D、2D、3D、CUBE等幾個目標,即是你可以在同一個紋理單元bind不同的紋理對象,但是不推薦剛開始就這麼做。

    glActiveTexture(GL_TEXTURE1);
    glEnable(GL_TEXTURE_2D);
    glGenTextures(1, &(_myTexture1));
    glBindTexture(GL_TEXTURE_2D, self.myTexture1);
    ```
如上,這是一段常用的使用紋理單元1的代碼。
先選擇(你也可以按照詞面意思理解爲激活)紋理單元1,同時開啓2D的紋理目標;
然後生成一個紋理對象,把紋理對象綁定到紋理單元1的2D紋理上;
接下來所有的操作都是針對紋理單元1上的紋理對象,直到你再次通過`glActiveTexture`選擇其他紋理單元。

####4、實現着色器
頂點着色器較爲簡單,只需把頂點數據轉成varying變量,傳給像素着色器即可;
像素着色器,收到頂點着色器傳過來的varyOtherPostion頂點數據,判斷當前點是否在leftBottom變量和rightTop變量形成的矩形內。
如果在矩形內,則通過自定義的操作來混合顏色,通常是使用alpha值,一個變量 \* alpha,一個變量 \* (1-alpha)。

varying lowp vec2 varyTextCoord;
varying lowp vec2 varyOtherPostion;

uniform lowp vec2 leftBottom;
uniform lowp vec2 rightTop;

uniform sampler2D myTexture0;
uniform sampler2D myTexture1;

void main()
{
if (varyOtherPostion.x >= leftBottom.x && varyOtherPostion.y >= leftBottom.y && varyOtherPostion.x <= rightTop.x && varyOtherPostion.y <= rightTop.y) {

    lowp vec2 test = vec2((varyOtherPostion.x - leftBottom.x) / (rightTop.x - leftBottom.x), 1.0 -  (varyOtherPostion.y - leftBottom.y) / (rightTop.y - leftBottom.y));
    lowp vec4 otherColor = texture2D(myTexture1, test);
 //   otherColor.a = 0.8;
    gl_FragColor = otherColor * otherColor.a + texture2D(myTexture0, 1.0 - varyTextCoord) * (1.0 - otherColor.a);
}
else {
    gl_FragColor = texture2D(myTexture0, 1.0 - varyTextCoord);
}

}

>0.8是爲了測試,效果展示的圖片就是alpha=0.8的效果圖。


###總結
最近幾周都忙着[直播系列的補齊](http://www.jianshu.com/notebooks/5037333/latest),OpenGL ES的上一篇[OpenGL ES實踐教程(四)VR全景視頻播放](http://www.jianshu.com/p/0c8d080bb375)已經是一個月之前。
接下來的文章主要還是以直播相關內容爲主,圖形圖像的等簡書的書友問道了再補上。


####附1
CaptureGPUFrame突然不好用,查了下文檔,發現可能是以下原因
>Note: Some features of the FPS gauge and GPU report rely on a display link timer. If you do not use the CADisplayLink or GLKViewController classes to animate your OpenGL ES displays, the gauge and report cannot show performance relative to a target frame rate or provide accurate CPU frame time information.

####附2
之前有書友問到,如何添加以下形狀的圖像。
![](http://upload-images.jianshu.io/upload_images/1049769-f492641d933b30a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這種格式不太試用本文的方法,需要引入一個新的shader變量`gl_LastFragData `。先繪製原來的圖像,再繪製新的圖像,通過`gl_LastFragData `來混合。
***有興趣的來一份數據,弄個demo玩玩!!***

產出不易,如果文章對你有所幫助,不如請我喝個可樂。



作者:落影loyinglin
鏈接:https://www.jianshu.com/p/f5c6593e1a44
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章