本文主要是介绍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实现滤镜效果的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!