美文网首页iOS精学选辑OpenGL|ESiOS 开发
OpenGL ES实践教程(五)多重纹理实现图像混合

OpenGL ES实践教程(五)多重纹理实现图像混合

作者: 落影loyinglin | 来源:发表于2016-09-26 10:42 被阅读5394次

    教程

    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:https://img.haomeiwen.com/i1049769/f492641d933b30a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    这种格式不太试用本文的方法,需要引入一个新的shader变量`gl_LastFragData `。先绘制原来的图像,再绘制新的图像,通过`gl_LastFragData `来混合。
    ***有兴趣的来一份数据,弄个demo玩玩!!***

    相关文章

      网友评论

      • 奔跑的时间:大神 如何将两张图片合成一张图片,并且把这两张图片重合的部分去除。有什么好的思路?
        落影loyinglin:@奔跑的时间 你可以把demo写好 我补个shader给你
        奔跑的时间:@落影loyinglin 有没有demo之类的教程?谢谢
        落影loyinglin:@奔跑的时间 用shader处理
      • 言溪Lee:你好 ,请问多重纹理混合(都是全屏),其中一个纹理动态刷新的话,怎么实现混合呢,我现在只能显示其中一个纹理,另外一个被覆盖,有时候还会出现纹理1绘制到了纹理2的位置上?这种情况要怎么处理呢,难道要每次刷新纹理重新active,bind么
        落影loyinglin:@ChristineH demo发我一份吧 loying#foxmail.com
        言溪Lee:@落影loyinglin 是,已经上传的贴图纹理glActiveTexture(GL_TEXTURE1);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, self.radarTexture)只在初次绘制贴图时绑定一次,后面没再调用;摄像头渲染的帧刷新时重新绑定glActiveTexture(GL_TEXTURE0);
        glBindTexture(CVOpenGLESTextureGetTarget(*texture), CVOpenGLESTextureGetName(*texture));但是屏幕显示只有贴图,背景一片空白,摄像头始终显示不出来
        落影loyinglin:@ChristineH 已经上传的纹理不用动,有刷新的纹理用替换的方法上传。
        绘制用原来方式。
      • 双湾:准备研究人脸识别AR的技术,看了影神这篇文章很受启发。在对应的demo中,update方法做的事情有清理buffer、创建buffer和清理画布、绘制顶点、应用context,这里我有两个问题请楼主帮忙解答一下:1.是不是每次更新都要重新创建buffer?2.如果我要在每次更新的时候切换纹理,该如何处理比较合适?比如更新摄像头采集的buffer
      • c1e0f39af20a:楼主能给个demo地址看下吗,,yuv420p图片。
        c1e0f39af20a:@落影loyinglin 摄像头的是yuv420sp 我需要做转换是吗
        落影loyinglin:@feng5f 你看文集里面摄像机的
        落影loyinglin:@feng5f 摄像机照出来的就可以是yuv
      • c1e0f39af20a:请问yuv420p图片或者rgb565图片如何在ios中显示呢
        c1e0f39af20a:@feng5f 能给个demo地址给我看下吗,,做了几天了,找不到合适的demo
        落影loyinglin:@feng5f 有yuv的
        demo
      • 孙健会员:如何直接渲染rgb数据
        落影loyinglin:@孙健会员 创建纹理,上传数据即可。
      • 孙健会员:影大神,如何自己制作一个滤镜添加到 GPUImage的响应链中?
        落影loyinglin:@孙健会员 你可以继承GPUImageFilter,然后实现自己的shader,接着按照普通的GPUImageFilter使用方式即可。

      本文标题:OpenGL ES实践教程(五)多重纹理实现图像混合

      本文链接:https://www.haomeiwen.com/subject/kdfqyttx.html