Opengl ES系列学习--FBO拷贝texture实现滤镜效果

2023-12-19 20:32

本文主要是介绍Opengl ES系列学习--FBO拷贝texture实现滤镜效果,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

     本节我们来学习一下如何使用FBO,FBO的一些基本内容网上有很多的介绍资料,我们这里就不多说了。把使用方法总结出来,方便大家学习。我们也使用FBO实现了一些最简单的滤镜效果,录屏如下。

     本节的内容是实时的相机预览,有部分核心的代码不方便发出来,滤镜的效果很简单,都是直接在片段着色器中修改RGB值完成的。网上也有使用Lut表查表实现的,大家可以参考:滤镜Shader--LookUpTable,好像github也需要翻墙了,在家里都无法打开了。

     本节实现的相机预览效果,上面的大图是一个GLSurfaceView,下面的一排小框是一个GLSurfaceView,两个GLSurfaceView对应各自的Opengl环境,上面的大框我们就不讲了,就是打开相机,拿到实时的预览数据,然后在Render类中的onDrawFrame方法中渲染出来。

     我们来看下面一排小框,它的Render类是FilterRender,完整源码如下:

package com.hong.camera;import android.content.Context;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.util.Log;import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;import static android.opengl.GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
import static android.opengl.GLES20.GL_CLAMP_TO_EDGE;
import static android.opengl.GLES20.GL_COLOR_ATTACHMENT0;
import static android.opengl.GLES20.GL_FRAMEBUFFER;
import static android.opengl.GLES20.GL_FRAMEBUFFER_COMPLETE;
import static android.opengl.GLES20.GL_LINEAR;
import static android.opengl.GLES20.GL_RGBA;
import static android.opengl.GLES20.GL_TEXTURE1;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_S;
import static android.opengl.GLES20.GL_TEXTURE_WRAP_T;
import static android.opengl.GLES20.GL_UNSIGNED_BYTE;
import static android.opengl.GLES20.glBindFramebuffer;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glCheckFramebufferStatus;
import static android.opengl.GLES20.glFramebufferTexture2D;
import static android.opengl.GLES20.glGenFramebuffers;
import static android.opengl.GLES20.glGenTextures;
import static android.opengl.GLES20.glTexImage2D;
import static android.opengl.GLES20.glTexParameterf;
import static android.opengl.GLES20.glTexParameteri;
import static android.opengl.GLES20.glUniform1f;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.GLES30.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES30.GL_TEXTURE0;
import static android.opengl.GLES30.glActiveTexture;
import static android.opengl.GLES30.glClear;
import static android.opengl.GLES30.glDisableVertexAttribArray;
import static android.opengl.GLES30.glEnableVertexAttribArray;
import static android.opengl.GLES30.glGetUniformLocation;
import static android.opengl.GLES30.glUniform1i;
import static android.opengl.GLES30.glUseProgram;
import static android.opengl.GLES30.glViewport;
import static android.opengl.Matrix.rotateM;
import static android.opengl.Matrix.setIdentityM;public class FilterRender implements GLSurfaceView.Renderer {private static final String TAG = FilterRender.class.getSimpleName();private static final int BYTES_PER_FLOAT = 4;private static final int BYTES_PER_SHORT = 2;private static final int POSITION_COMPONENT_SIZE = 3;private static final int COLOR_COMPONENT_SIZE = 4;private final float[] mVerticesData ={-1.0f, 1f, 0.0f, // v0-1.0f, -1f, 0.0f, // v11.0f, -1f, 0.0f,  // v2-1.0f, 1f, 0.0f, // v01.0f, -1f, 0.0f,  // v21.0f, 1f, 0.0f,  // v3};private final float[] mTexturePosiontData ={0.0f, 0.0f,0.0f, 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f, 1.0f,1.0f, 0.0f,};private Context mContext;private int mProgramObject, mFilterProgramObject;private int vPosition, aTexturePosition, mOESTextureUniform, uMatrixUniform;private int vFilterPosition, aFilterTexturePosition, uTexture, uFilterMatrixUniform, uIndex, uTime;private int mWidth, mHeight;private int mTextureId;private FloatBuffer mVertices, mTextureBuffer;private final float[] viewMatrix = new float[16];private final float[] filterMatrix = new float[16];int[] mFBO = new int[1];int[] mFBOTexture = new int[1];private int itemWidth, itemHeight, itemGap;private long lStartTime = 0;public FilterRender(Context context) {mContext = context;mVertices = ByteBuffer.allocateDirect(mVerticesData.length * BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();mVertices.put(mVerticesData).position(0);mTextureBuffer = ByteBuffer.allocateDirect(mTexturePosiontData.length * BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();mTextureBuffer.put(mTexturePosiontData).position(0);}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {createOriginProgram();createFilterProgram();}private void createOriginProgram() {String vShaderStr = ESShader.readShader(mContext, "vertexShader.glsl");String fShaderStr = ESShader.readShader(mContext, "fragmentShader.glsl");int vertexShader;int fragmentShader;int[] linked = new int[1];// Load the vertex/fragment shadersvertexShader = ESShader.loadShader(GLES30.GL_VERTEX_SHADER, vShaderStr);fragmentShader = ESShader.loadShader(GLES30.GL_FRAGMENT_SHADER, fShaderStr);// Create the program objectmProgramObject = GLES30.glCreateProgram();if (mProgramObject == 0) {return;}GLES30.glAttachShader(mProgramObject, vertexShader);GLES30.glAttachShader(mProgramObject, fragmentShader);// Link the programGLES30.glLinkProgram(mProgramObject);// Check the link statusGLES30.glGetProgramiv(mProgramObject, GLES30.GL_LINK_STATUS, linked, 0);if (linked[0] == 0) {Log.e(TAG, "Error linking program:");Log.e(TAG, GLES30.glGetProgramInfoLog(mProgramObject));GLES30.glDeleteProgram(mProgramObject);return;}// Store the program objectvPosition = GLES30.glGetAttribLocation(mProgramObject, "vPosition");aTexturePosition = GLES30.glGetAttribLocation(mProgramObject, "aTexturePosition");mOESTextureUniform = glGetUniformLocation(mProgramObject, "mOESTextureUniform");uMatrixUniform = glGetUniformLocation(mProgramObject, "uMatrix");Log.e(TAG, "get vPosition: " + vPosition + ", mOESTexture: " + mOESTextureUniform+ ", aTexturePosition: " + aTexturePosition);int[] textureId = new int[1];glGenTextures(1, textureId, 0);mTextureId = textureId[0];setIdentityM(viewMatrix, 0);}private void createFilterProgram() {mFilterProgramObject = OpenGLUtils.loadProgram(mContext, R.raw.filter_vertex, R.raw.filter_fragment);if (mFilterProgramObject == 0) {return;}// Store the program objectvFilterPosition = GLES30.glGetAttribLocation(mFilterProgramObject, "vPosition");aFilterTexturePosition = GLES30.glGetAttribLocation(mFilterProgramObject, "aTexturePosition");uTexture = glGetUniformLocation(mFilterProgramObject, "uTexture");uFilterMatrixUniform = glGetUniformLocation(mFilterProgramObject, "uMatrix");uIndex = glGetUniformLocation(mFilterProgramObject, "uIndex");uTime = glGetUniformLocation(mFilterProgramObject, "uTime");Log.e(TAG, "get vPosition: " + vPosition + ", mOESTexture: " + mOESTextureUniform+ ", aTexturePosition: " + aTexturePosition);glGenFramebuffers(1, mFBO, 0);glGenTextures(1, mFBOTexture, 0);setIdentityM(filterMatrix, 0);rotateM(filterMatrix, 0, -90, 0f, 0f, 1f);}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {mWidth = width;mHeight = height;itemWidth = 144;itemHeight = 192;itemGap = 30;Log.e(TAG, "onSurfaceChanged, " + mWidth + ", " + mHeight);}@Overridepublic void onDrawFrame(GL10 gl) {if (lStartTime == 0) {lStartTime = System.currentTimeMillis();}glClear(GL_COLOR_BUFFER_BIT);GLES30.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);drawFBO();drawFilter();}private void drawFBO() {glBindFramebuffer(GL_FRAMEBUFFER, mFBO[0]);glBindTexture(GL_TEXTURE_2D, mFBOTexture[0]);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, itemWidth, itemHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);glBindTexture(GL_TEXTURE_2D, mFBOTexture[0]);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFBOTexture[0], 0);drawOES();glBindFramebuffer(GL_FRAMEBUFFER, 0);}private void drawOES() {...}private void drawFilter() {glUseProgram(mFilterProgramObject);glUniformMatrix4fv(uFilterMatrixUniform, 1, false, filterMatrix, 0);GLES30.glVertexAttribPointer(vFilterPosition, 3, GLES30.GL_FLOAT, false, 0, mVertices);glEnableVertexAttribArray(vFilterPosition);GLES30.glVertexAttribPointer(aFilterTexturePosition, 2, GLES30.GL_FLOAT, false, 0, mTextureBuffer);glEnableVertexAttribArray(aFilterTexturePosition);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, mFBOTexture[0]);glUniform1i(uTexture, 1);glUniform1f(uTime, System.currentTimeMillis() - lStartTime);for (int i = 0; i < 9; i++) {glViewport((itemWidth + itemGap) * i, 0, itemWidth, itemHeight);glUniform1i(uIndex, i);GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 6);}glDisableVertexAttribArray(vFilterPosition);glDisableVertexAttribArray(aFilterTexturePosition);}
}

     该类中也包含了FBO的使用方法,也就是drawFBO方法的逻辑,和普通的texture一样,我们在使用之前必须先调用glBindFramebuffer把生成的目标fbo对象绑定到GL_FRAMEBUFFER  target上,当然target类型还有GL_READ_FRAMEBUFFER、GL_DRAW_FRAMEBUFFER两种,不过大部分场景下我们没有必须区分,直接使用GL_FRAMEBUFFER就可以了;接下来的几步我们需要声明一张texture 2d,并调用glTexImage2D将它的内存置空,这张2d纹理就是用来向fbo拷贝数据的,fbo本身是没有存储空间的,它是需要关联一张颜色附件才能正常使用,我们使用FBO执行纹理拷贝时,其实就是把源纹理中的数据拷贝到这张和FBO关联的2d纹理附件中,最后要使用的也是这张关联的2d纹理附件,这才是FBO的正确使用方法,大家必须把这点搞清楚,否则还是没有掌握FBO的使用方法;设定好2d texture的纹理参数后,调用glFramebufferTexture2D将该2d纹理附着到FBO上,到这里FBO的使用环境就算是准备好,我们也可以调用glCheckFramebufferStatus对FBO的状态进行验证,状态正常时返回GL_FRAMEBUFFER_COMPLETE(36053),否则我们就需要检查FBO哪些条件没有满足了。

     上面的环境准备好之后,我们就可以执行正常的绘制了,也就是drawOES方法的逻辑,drawOES方法中是和普通绘制完全相同的逻辑,使用OES纹理绘制我们相机的预览数据,绘制完成后调用glBindFramebuffer(GL_FRAMEBUFFER, 0)把FBO解绑,解绑的意思其实就是把和FBO关联的纹理id恢复成默认的0。

     这里是不是觉得有点奇怪了,先准备了FBO的环境,然后正常绘制,最后解绑,好像第二步我们的正常绘制和FBO没有进行任何拷贝相关的动作,那么预览数据是如何到FBO对象中的?这也就是FBO的奥秘了。FBO就是Opengl给我们准备好的一张画布,默认的FBO纹理id是0,也就是显示屏,如果我们调用glBindFramebuffer把默认的0替换成其他值,那么这时候就不会显示到屏幕上了,这就是离屏渲染的叫法,大家可以试下,在正常绘制的开始调用glBindFramebuffer把FBO的默认id改掉,屏幕就会黑了,什么东西都绘制不出来,就是这个原因。这是我们本节讲FBO的第二点重要内容,上面绑定一张颜色附件是第一点重要内容。

     理解了这两点,我们也就掌握了FBO的使用原则了,再回头看我们的三个方法:drawFBO是准备FBO的使用环境,环境准备好之后,调用drawOES;因为上一步一开始就调用glBindFramebuffer,改变了默认的FBO的纹理id,所以此时的drawOES都是在往FBO绑定的那张颜色附件的2d纹理中绘制的,也就是说这一步执行完,那张关联在FBO上的2d纹理附件就有数据了!!!第三步drawFilter也就是最简单的Opengl绘制了,因为纹理已经有数据了,我们就只需要调用正常的Opengl流程,把这张2d纹理绘制出来就可以了。要实现我们前面的那些滤镜效果,因我们已经把相机的预览原数据拷贝过来了,剩下的只需要for循环绘制多次,然后针对每一次的绘制改变fragmentshader中的颜色采样就可以了,这块的道理应该都很简单,我们通过阅读片段着色器的源码来结束本节的内容,谢谢大家!!

#version 300 esprecision mediump float;uniform sampler2D uTexture;
in vec2 vTexturePosition;
out vec4 o_fragColor;
uniform int uIndex;
uniform float uTime;vec4 filterProcess(vec4 color) {vec4 outColor = vec4(0.0);switch (uIndex) {case 0:float c = (color.r + color.g + color.b) / 3.0;outColor = vec4(c, c, c, color.a);break;case 1:outColor = color + vec4(0.2, 0.2, 0.0, 0.0);break;case 2:outColor = color + vec4(0.0, 0.0, 0.3, 0.0);break;case 3:float dis = 0.01;color += texture(uTexture, vec2(vTexturePosition.x-dis, vTexturePosition.y-dis));color += texture(uTexture, vec2(vTexturePosition.x-dis, vTexturePosition.y+dis));color += texture(uTexture, vec2(vTexturePosition.x+dis, vTexturePosition.y-dis));color += texture(uTexture, vec2(vTexturePosition.x+dis, vTexturePosition.y+dis));color += texture(uTexture, vec2(vTexturePosition.x-dis, vTexturePosition.y-dis));color += texture(uTexture, vec2(vTexturePosition.x-dis, vTexturePosition.y+dis));color += texture(uTexture, vec2(vTexturePosition.x+dis, vTexturePosition.y-dis));color += texture(uTexture, vec2(vTexturePosition.x+dis, vTexturePosition.y+dis));color += texture(uTexture, vec2(vTexturePosition.x-dis, vTexturePosition.y-dis));color += texture(uTexture, vec2(vTexturePosition.x-dis, vTexturePosition.y+dis));color += texture(uTexture, vec2(vTexturePosition.x+dis, vTexturePosition.y-dis));color += texture(uTexture, vec2(vTexturePosition.x+dis, vTexturePosition.y+dis));color /= 13.0;outColor = color;break;case 4:float lightUpValue = abs(sin(uTime / 1000.0)) / 4.0;vec4 addColor = vec4(lightUpValue, lightUpValue, lightUpValue, 1.0);outColor = color + addColor;break;default :outColor = color;break;}return outColor;
}void main() {vec4 color = texture(uTexture, vTexturePosition);o_fragColor = filterProcess(color);
}

 

这篇关于Opengl ES系列学习--FBO拷贝texture实现滤镜效果的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/513606

相关文章

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java easyExcel实现导入多sheet的Excel

《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

浏览器插件cursor实现自动注册、续杯的详细过程

《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

Golang如何用gorm实现分页的功能

《Golang如何用gorm实现分页的功能》:本文主要介绍Golang如何用gorm实现分页的功能方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录背景go库下载初始化数据【1】建表【2】插入数据【3】查看数据4、代码示例【1】gorm结构体定义【2】分页结构体

在Golang中实现定时任务的几种高效方法

《在Golang中实现定时任务的几种高效方法》本文将详细介绍在Golang中实现定时任务的几种高效方法,包括time包中的Ticker和Timer、第三方库cron的使用,以及基于channel和go... 目录背景介绍目的和范围预期读者文档结构概述术语表核心概念与联系故事引入核心概念解释核心概念之间的关系

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C