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玩玩!!***
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容