OpenGL.ES在Android上的简单实践:20-水印录制(预览 gl_blend)

1、继续画出预览帧

紧接着上篇文章,既然是要画出预览帧,按照之前其他项目的架构组成。我们是通过模型FrameRect.draw的方法画出预览帧,在定义这个draw方法之前我们从着色器出发,看看需要什么。

    private static final String VERTEX_SHADER ="uniform mat4 uMVPMatrix;\n" +"attribute vec4 aPosition;\n" +"uniform mat4 uTexMatrix;\n" +"attribute vec4 aTextureCoord;\n" +"varying vec2 vTextureCoord;\n" +"void main() {\n" +"    gl_Position = uMVPMatrix * aPosition;\n" +"    vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" +"}\n";private static final String FRAGMENT_SHADER_EXT ="#extension GL_OES_EGL_image_external : require\n" +"precision mediump float;\n" +"varying vec2 vTextureCoord;\n" +"uniform samplerExternalOES sTextureOES;\n" +"void main() {\n" +"    gl_FragColor = texture2D(sTextureOES, vTextureCoord);\n" +"}\n";

从顶点着色器入手,大部分属性都已经介绍,那么这个uTexMatrix从哪里来?这个又要说到Camera.SurfaceTexture的getTransformMatrix了,从方法名理解就是获取变换矩阵?怎么个变换法啊,我们可以这里理解这个变换矩阵,其实就是我们之前介绍OpenGL的三大矩阵MVP的核矩阵。Camera通过SurfaceTexture反馈一个纹理帧对象,而且附带了当前摄像头设置变换的矩阵。譬如我们在打开摄像头openCamera的时候就设置了显示角度,如下

private void openCamera(int desiredWidth, int desiredHeight, int desiredFps) {... ...// System API Open CameraCamera.Parameters parms = mCamera.getParameters();CameraUtils.choosePreviewSize(parms, desiredWidth, desiredHeight);//设置合适的长宽mCameraPreviewThousandFps = CameraUtils.chooseFixedPreviewFps(parms, desiredFps * 1000);//设置合适的帧率parms.setRecordingHint(true);//设置可录制的索引mCamera.setParameters(parms);//生成设置的参数Camera.Size cameraPreviewSize = parms.getPreviewSize();// 调整界面的长宽比例AspectFrameLayout layout = (AspectFrameLayout) findViewById(R.id.continuousRecord_afl);//layout.setAspectRatio((double) cameraPreviewSize.width / cameraPreviewSize.height);// Portraitlayout.setAspectRatio((double) cameraPreviewSize.height / cameraPreviewSize.width);mCamera.setDisplayOrientation(90); //设置显示角度,这里会影响作用纹理帧的变换矩阵}

所以,此时ContinuousRecordActivity的drawFrame方法修改成如下:

    private final float[] mTmpMatrix = new float[16];private void drawFrame() {if (mEglCore == null) {Log.d(TAG, "Skipping drawFrame after shutdown");return;}Log.d(TAG, " MSG_FRAME_AVAILABLE");mDisplaySurface.makeCurrent();GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);mCameraTexture.updateTexImage(); // 获取预览帧mCameraTexture.getTransformMatrix(mTmpMatrix); // 获取预览帧的变换矩阵int viewWidth = sv.getWidth();int viewHeight = sv.getHeight();GLES20.glViewport(0, 0, viewWidth, viewHeight); //设置视口为整个surface大小mFrameRect.drawFrame(mTextureId, mTmpMatrix); // 画图mDisplaySurface.swapBuffers();}

剩下的就是去完成这个FrameRect.drawFrame的方法实现了。我们继续show代码

    public void drawFrame(int mTextureId, float[] texMatrix) {GLES20.glUseProgram(mProgram.getShaderProgramId());// 设置纹理GLES20.glActiveTexture(GLES20.GL_TEXTURE0);GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);GLES20.glUniform1i(mProgram.sTextureOESLoc, 0);GlUtil.checkGlError("TEXTURE_EXTERNAL_OES sTextureOES");// 设置 model / view / projection 矩阵GLES20.glUniformMatrix4fv(mProgram.uMVPMatrixLoc, 1, false, getFinalMatrix(), 0);GlUtil.checkGlError("glUniformMatrix4fv uMVPMatrixLoc");// 设置 纹理变换矩阵GLES20.glUniformMatrix4fv(mProgram.uTexMatrixLoc, 1, false, texMatrix, 0);GlUtil.checkGlError("glUniformMatrix4fv uTexMatrixLoc");// 使用简单的VAO 设置顶点坐标数据GLES20.glEnableVertexAttribArray(mProgram.aPositionLoc);GLES20.glVertexAttribPointer(mProgram.aPositionLoc, mCoordsPerVertex,GLES20.GL_FLOAT, false, mVertexStride, mVertexArray);GlUtil.checkGlError("VAO aPositionLoc");// 使用简单的VAO 设置纹理坐标数据GLES20.glEnableVertexAttribArray(mProgram.aTextureCoordLoc);GLES20.glVertexAttribPointer(mProgram.aTextureCoordLoc, mCoordsPerTexture,GLES20.GL_FLOAT, false, mTexCoordStride, mTexCoordArray);GlUtil.checkGlError("VAO aTextureCoordLoc");// GL_TRIANGLE_STRIP三角形带,这就为啥只需要指出4个坐标点,就能画出两个三角形了。GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mVertexCount);// Done -- 解绑everythingGLES20.glDisableVertexAttribArray(mProgram.aPositionLoc);GLES20.glDisableVertexAttribArray(mProgram.aTextureCoordLoc);GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);GLES20.glUseProgram(0);}

额 ... 感觉没啥好说的啊~注释都有了。坐标顶点和纹理顶点用了VAO的加载方法;第二个注意点就是glDrawArrays的绘制类型是GL_TRIANGLE_STRIP三角带,所以只需要四个顶点就可以画出两个连续的三角形了;其余那些m的变量都在FrameRect构造函数的时候准备好了;绑定的纹理对象就是Camera.SurfaceTexture的纹理对象,我们记住这是EXTERNAL_OES的类型,剩下的我们按照以前纹理的知识按部就班的去使用就没问题了。

现在我们运行demo看看情况如何?

2、添加水印

现在我们已经正常的在EGL环境下预览摄像头了!下一步就是添加水印签名。根据我们的直观认识,其实水印签名也就是一个透明背景的图而已嘛?!我们大胆猜测是否能这样?在片段着色器中增加一个普通的纹理对象sampler2D,然后在叠加显示它们? 事不宜迟,我们赶紧跳坑吧~(奸笑.jpg)

    private static final String VERTEX_SHADER = "... ... ...";private static final String FRAGMENT_SHADER_EXT ="#extension GL_OES_EGL_image_external : require\n" +"precision mediump float;\n" +"varying vec2 vTextureCoord;\n" +"uniform samplerExternalOES sTextureOES;\n" +"uniform sampler2D sTexture;\n" +"void main() {\n" +"    //gl_FragColor = texture2D(sTextureOES, vTextureCoord);\n" +"    vec4 texOES = texture2D(sTextureOES, vTextureCoord);\n"+"    vec4 tex = texture2D(sTexture, vTextureCoord);\n"+"    gl_FragColor = mix(texOES, tex, 0.5);\n"+"}\n";

巴拉巴拉的,我们在片段着色器中增加多一个sampler2D普通纹理对象sTexture。然后我们先简单的运用GLSL自带的mix混合叠加两张纹理看看效果,关于更多的GLSL内部自带的函数变量请参阅这里,搜索内置函数。mix这个函数是GLSL中一个特殊的线性插值函数,他将前两个参数的值基于第三个参数按照以下公式进行插值:

genType mix (genType x, genType y, float a)
返回线性混合的x和y,如:x⋅(1−a)+y⋅a

重载我们FrameRect.drawFrame方法如下

    public void drawFrame(int mTextureId, float[] texMatrix, int mTextureSign) {GLES20.glUseProgram(mProgram.getShaderProgramId());// 设置预览纹理 使用单元0GLES20.glActiveTexture(GLES20.GL_TEXTURE0);GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);GLES20.glUniform1i(mProgram.sTextureOESLoc, 0);GlUtil.checkGlError("TEXTURE_EXTERNAL_OES sTextureOES");// 设置水印签名纹理 使用单元1GLES20.glActiveTexture(GLES20.GL_TEXTURE1);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureSign);GLES20.glUniform1i(mProgram.sTextureLoc, 1);GlUtil.checkGlError("GL_TEXTURE_2D sTexture");... ... ... //以前的代码GLES20.glUseProgram(0);}

然后使用之前的模板工具代码,从mipmap加载我们的签名图片,并在传入新改造的drawFrame方法中。

    @Overridepublic void surfaceCreated(SurfaceHolder surfaceHolder) {... ... ...mTextureId = GlUtil.createExternalTextureObject();... ... ...mSignTexId = TextureHelper.loadTexture(ContinuousRecordActivity.this, R.mipmap.name);... ... ...}

运行demo看看效果如何?

因为我们使用的是同一组纹理坐标,所以占满了整个屏幕了。感觉还行吧,类似那种防盗版的视频不都是加了一个整屏幕的水印吗? (笑哭.jpg)好吧,在顶点着色器重新定义一个签名纹理的坐标attribute变量,然后视频纹理和签名纹理各自对应自己的纹理坐标。不过又有同学了,这太麻烦了不够灵活啊?!

3、更学科的添加水印

之前在介绍GL_TEXTURE_EXTERNAL_OES这个Android特有的纹理类型的时候,我就说到尽量把这个类型的纹理和普通纹理的分开在不同的GLSL渲染管线。这是更为科学优雅的做法。

我们依据FrameRect+FrameRectSProgram的模板代码,复制一份WaterSignature+WaterSignSProgram,然后我们需要注意以下几点的区别。

public class WaterSignSProgram extends ShaderProgram {private static final String VERTEX_SHADER ="uniform mat4 uMVPMatrix;\n" +"attribute vec4 aPosition;\n" +"attribute vec4 aTextureCoord;\n" +"varying vec2 vTextureCoord;\n" +"void main() {\n" +"    gl_Position = uMVPMatrix * aPosition;\n" +"    vTextureCoord = aTextureCoord.xy;\n" +"}\n";private static final String FRAGMENT_SHADER ="precision mediump float;\n" +"varying vec2 vTextureCoord;\n" +"uniform sampler2D sTexture;\n" +"void main() {\n" +"    gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +"}\n";public WaterSignSProgram() {... ... ...//着色器程序的编译。属性、变量的获取等模板代码//详情follow github}
}

首先我们看着色器程序,普通纹理sampler2D,还有从顶点着色器传递过来的纹理坐标vTextureCoord是vec2二维的。而我们之前定义是vec4四维,我们只需要用前两个xy值就可以了。

public class WaterSignature {private static final float FULL_RECTANGLE_COORDS[] = {-1.0f, -1.0f,   // 0 bottom left1.0f, -1.0f,   // 1 bottom right-1.0f,  1.0f,   // 2 top left1.0f,  1.0f,   // 3 top right};private static final float FULL_RECTANGLE_TEX_COORDS[] = {0.0f, 1.0f,     //0 bottom left     //0.0f, 0.0f, // 0 bottom left1.0f, 1.0f,     //1 bottom right    //1.0f, 0.0f, // 1 bottom right0.0f, 0.0f,     //2 top left        //0.0f, 1.0f, // 2 top left1.0f, 0.0f,     //3 top right       //1.0f, 1.0f, // 3 top right};... ... ...//构造函数初始化顶点坐标数据 纹理坐标数据 MVP三大矩阵//详情follow githubpublic void drawFrame(int mTextureId) {GLES20.glUseProgram(mProgram.getShaderProgramId());// 设置纹理GLES20.glActiveTexture(GLES20.GL_TEXTURE0);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);GLES20.glUniform1i(mProgram.sTextureLoc, 0);GlUtil.checkGlError("GL_TEXTURE_2D sTexture");// 设置 model / view / projection 矩阵// 使用简单的VAO 设置顶点坐标数据// 使用简单的VAO 设置纹理坐标数据// Done -- 解绑~}}

再到模型,因为这次是加载普通的纹理,我们的纹理坐标需要水平颠倒。参照之前SurfaceTexture的坐标就很好的转换过来了。drawFrame方法记得加载的目标纹理是GL_TEXTURE_2D。   东风准备好了,接着下来看看要怎么吹吧。

转到测试页面ContinuousRecordActivity,还是按照FrameRect的套路,在onCraete创建WaterSignature实例,在surfaceCreated的EGL环境下设置着色器程序。

下面我们来说说重点疑点。

    @Overridepublic void surfaceCreated(SurfaceHolder surfaceHolder) {... ... ...mFrameRect.setShaderProgram(new FrameRectSProgram());mWaterSign.setShaderProgram(new WaterSignSProgram());mSignTexId = TextureHelper.loadTexture(ContinuousRecordActivity.this, R.mipmap.name);... ... ...}private void drawFrame() {... ... mDisplaySurface.makeCurrent();... ...GLES20.glViewport(0, 0, viewWidth, viewHeight);mFrameRect.drawFrame(mTextureId, mTmpMatrix);GLES20.glViewport(0, 0, 288, 144); // x, y, width, height. 设置绘制的视口位置/大小mWaterSign.drawFrame(mSignTexId);mDisplaySurface.swapBuffers();}

我们使用以前的工具代码,从res.mipmap加载透明背景的水印签名图片。然后就到drawFrame,我们在画出水印签名图前先更改视口的位置/大小,改为图尺寸大小or整数倍。这样我们的纹理坐标是填充整个视口的,不需要做很精密的计算,我们通过改动视口以达到更改签名的绘制的位置大小,就非常符合编程的思维。  现在我们跑起demo看看效果。(奸笑.jpg)

What the fxxk?这叫毛水印啊,透明效果呢?为啥会出现这样?首先要确保你的签名图是真的透明哦。好了,还是来说重点。其实这个是Surface的颜色域 和 GL的混合模式所造成的问题。有些低版本的Android的Surface默认颜色域是RGB_565的,所以我们要先申明其支持透明的RGBA_8888。申明方法如下:

    @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.continuous_record);sv = (SurfaceView) findViewById(R.id.continuousRecord_surfaceView);SurfaceHolder sh = sv.getHolder();sh.setFormat(PixelFormat.RGBA_8888); // 申明其surface的颜色是RGBA_8888sh.addCallback(this);mHandler = new MainHandler(this);mFrameRect = new FrameRect();mWaterSign = new WaterSignature();}

然后就到GL的混合模式了,啥玩意?不知道同学们对Android.Paint的xfermode有无印象,从认知的角度来说其实是同一回事。在很多情况下,我们都需要在一个画布(Canvas/Surface)画一个以上的对象,多个对象间难免有重合叠加的情况,现实的艺术家通过颜色的混合来达到想要的效果,在程序上我们也是可以借用混合这个概念。  OpenGL打开混合模式的指令是如下代码:

private void drawFrame() {... ... ...mDisplaySurface.makeCurrent();GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);GLES20.glEnable(GLES20.GL_BLEND); //打开混合功能GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); //指定混合模式GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);... ... ...GLES20.glViewport(0, 0, viewWidth, viewHeight);mFrameRect.drawFrame(mTextureId, mTmpMatrix);GLES20.glViewport(0, 0, 288, 144);mWaterSign.drawFrame(mSignTexId);mDisplaySurface.swapBuffers();}

OpenGL的混合知识不少,前辈巨人们已经在这里作出了很详尽的介绍了。其中划重点:

1、混合就是在绘制时,不是直接把新的颜色覆盖在原来旧的颜色上,而是将新的颜色与旧的颜色经过一定的运算,从而产生新的颜色。新的颜色称为源颜色,原来旧的颜色称为目标颜色。传统意义上的混合,是将源颜色乘以源因子,目标颜色乘以目标因子,然后相加。
2、源因子和目标因子是可以设置的。源因子和目标因子设置的不同直接导致混合结果的不同。将源颜色的alpha值作为源因子,用1.0减去源颜色alpha值作 为目标因子,是一种常用的方式。这时候,源颜色的alpha值相当于“不透明度”的作用。利用这一特点可以绘制出一些半透明的物体。
3、在进行混合时,绘制的顺序十分重要。因为在绘制时,正要绘制上去的是源颜色,原来存在的是目标颜色,因此先绘制的物体就成为目标颜色,后来绘制的则成为源颜色。绘制的顺序要考虑清楚,将目标颜色和设置的目标因子相对应,源颜色和设置的源因子相对应。

前辈巨人们总结的OpenGL混合知识请一定要查阅一番(在这里点进去),一次读不懂不重要,到现在我也没完全熟练明白。(尴尬.jpg)从运用中理解会更能容易快速掌握。

总结:摄像头预览添加水印效果我们基本上已经完成了。通过这章我们复习了旧有的知识的同时学到了

1、同一渲染管线下,GL_TEXTURE_EXTERNAL_OES和GL_TEXTURE_2D的GLSL.mix线性混合(不推荐)

2、GL_BLEND的OpenGL混合模式(重点)

3、Android的surface颜色域(隐藏坑)

题外话:我们的水印签名图每帧都是固定位置的,能不能做成类似弹幕的平衡移动?嘿嘿嘿get到干货知识点了吧?

github项目地址:https://github.com/MrZhaozhirong/BlogApp

OpenGL.ES在Android上的简单实践:20-水印录制(预览+透明水印 表情 弹幕 gl_blend)相关推荐

  1. OpenGL.ES在Android上的简单实践:11-全景(索引-深度测试)

    OpenGL.ES在Android上的简单实践:11-全景(正方体-索引-深度测试) 0.全景图要怎么看? What is 全景?可能很多人单看这名字不太清楚.但看到下面的图的时候就噢的一声~瞬间廓然 ...

  2. OpenGL.ES在Android上的简单实践:10-曲棍球(拖动物体、碰撞测试)

    OpenGL.ES在Android上的简单实践:10-曲棍球(拖动物体.碰撞测试) 1.让木槌跟随手指移动 继续上一篇文章9的内容.既然可以测试木槌是否被触碰了,我们将继续努力下去:当我们来回拖动木槌 ...

  3. OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏渲染,解决透明冲突,画中画)

    OpenGL.ES在Android上的简单实践:23-水印录制(FBO离屏录制,解决透明冲突) 1.水印签名罢工了? 不知道大家有没注意到,之前我们使用MediaCodec录制的视频,水印签名那部分区 ...

  4. OpenGL.ES在Android上的简单实践:21-水印录制(MediaCodec输出h264+MediaMuxer合成mp4 上)

    OpenGL.ES在Android上的简单实践:21-水印录制(MediaCodec输出h264+MediaMuxer合成mp4 上) 1.录制视频需要什么? 在上篇文章,我们已经成功的满足了需求,在 ...

  5. SDL2源码分析之OpenGL ES在windows上的渲染过程

    SDL2源码分析之OpenGL ES在windows上的渲染过程 更新于2018年11月4日. 更新于2018年11月21日. ffmpeg + SDL2实现的简易播放器 ffmpeg和SDL非常强大 ...

  6. 在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping)

    在 OpenGL ES 2.0 上实现视差贴图(Parallax Mapping) 视差贴图 最近一直在研究如何在我的 iPad 2(只支持 OpenGL ES 2.0, 不支持 3.0) 上实现 视 ...

  7. OpenGL ES for Android 绘制旋转的地球

    No 图 No Code,我们先来欣赏下旋转的地球: 是不是很酷炫,要想绘制出上面酷炫的效果需要3个步骤: 计算球体顶点数据 地球纹理贴图 通过MVP矩阵旋转地球 计算球体顶点数据 我们知道OpenG ...

  8. android opengl流程,【Android OpenGL ES】Android Opengl ES创建流程

    在android 1.0rc2 sdk中,提供了以下包支持Opengl ES 编程: 一.openglES包 android.opengl Class: GLDebugHelper:用于调试OpenG ...

  9. OpenGL ES for Android 绘制立方体

    立方体有6个面,8个顶点,因此绘制立方体其实就是绘制6个面. 顶点shader attribute vec4 a_Position; attribute vec4 a_color; varying v ...

最新文章

  1. xubuntu18.04安装Google拼音输入法
  2. 刚刚!2020世界大学学术排名正式发布:中国内地144所大学上榜!
  3. 人生第一个快速幂的题(HDU - 1097--A hard puzzle )
  4. Device Tree(三):代码分析【转】
  5. 剑指 Offer 面试题45:把数组排成最小的数——Python内置函数 map()、__lt__()、join()、sorted()
  6. JavaScript 全局对象
  7. 北上广深的程序员,房子在向你们招手了!
  8. 三部门部署开展非学科类校外培训收费专项整治工作
  9. 怎样输出矩阵乘积C语言,c语言矩阵相乘
  10. redis开发与运维笔记
  11. 电子邮件工作原理及主要协议
  12. jsPlumb点到点连线偏移
  13. 边际成本,机会成本,沉默成本
  14. DeprecationWarning: The binary mode of fromstring is deprecated, as it behaves surprisingly on unico
  15. webrtc QOS方法四.2(拥塞算法学习)
  16. Java代码实现excel的导入和导出
  17. HyperLynx(二)LineSim的基本操作
  18. 窗口看门狗和独立看门狗区别
  19. 初面蚂蚁金服,三面腾讯,已拿offer!绝对干货分享
  20. 个人发展分析:SWOT

热门文章

  1. 完美解决google关闭历史栏搜索的问题
  2. 汽车之家前端面试经历
  3. Laya Sprite
  4. 手把手教你,用Auto-GPT自动写个网站(保姆级)
  5. 在B端供应链上,看见企业增长的「密码」
  6. oracle ebs的权限设计,oracle EBS 系统管理员-手册.doc
  7. cad批量打印_CAD基础知识之打印,CAD图纸如何批量打印?这样设置,轻松搞定
  8. 如何快速简单的获取WIFI的加密方式
  9. 2005本年度最具投资价值的九大行业分析
  10. Linux自学之旅-基础命令(压缩解压缩命令之bzip2命令)