说到基于GPU的图像处理和实时滤镜,大家肯定会想到鼎鼎大名的GPUImage,这个项目确实为后续开发提供了很多方便,基本的图像处理工具一应俱全。为我们提供不小的帮助。
** GPUImage项目结构**
GPUImage的项目结构其实很简单,Android版本就更是简陋,结构如下:
一堆滤镜(着色器以及配套设置参数的代码)-FilterGroup(利用FBO进行同一副图像的多次处理)-EGL管理类(主要用于做离屏渲染)
虽然GPUImage的主要价值在那堆滤镜上,但是我们主要来分析后面两个,这是GPUImage的框架,而滤镜就像插件一样,想插就插:D,我们也可以依葫芦画瓢定制自己的滤镜。
为什么要离屏渲染
离屏渲染的主要目的是在后台处理数据,做过Camera应用的都知道,如果用SurfaceView进行预览,那么就不得不把相机数据显示出来,为了不显示,就要把SurfaceView缩小到很小,麻烦Android 3.0后有了SurfaceTexture和GLSurfaceView,之后又有了TextureView,可以自由处理相机数据不显示出来了,但是依然有一个显示和替换的过程。换句话说,TextureView和GLSurfaceView还不够听话,不能完成我们的所有要求。
如果我们只是想要利用GPU处理一张图片,但是不把他显示出来呢?
举个栗子我们来看一下Camera360 Lite版的界面:![](http://img4.sycdn.imooc.com/5e81e8630001190f07910165.jpg )这些图片都是打开以后选择滤镜可以看到的,不用联网也可以,他们是APK自带的吗?为什么都是同一个人呢?却找了一圈以后,我只能在APK中找到这些:![](http://img1.sycdn.imooc.com/ 5e81e864000172a103420292 .jpg)不同颜色的大姐姐去哪了?这说明,这些不同的滤镜效果,其实是APK在第一次运行时,在用户手机上生成的。(可以自行查看Camera360的数据文件夹)这样有很多好处呀,例如说大大增加了APK体积,相同套代码还可以进行完成不同的功能等。当然,这只是离屏渲染的一个优点。
之前使用GLSurfaceView时,GLSurfaceView帮我们完成了EGL的环境配置,现在不使用GLSurfaceView,我们就要自行管理了,看看GPUImage是怎么做的吧:![](http://img1.sycdn.imooc。 com / 5e81e865000145a106990529.jpg)GPUImage参考了GLSurfaceView,自己进行了OpenGL的环境配置(好像什么都没说啊,逃脱…
后面我们在分析GLSurfaceView的代码时,会再来说离屏渲染应该怎么做(毕竟环境配置什么的都是套路)
滤镜组与帧缓存对象(FBO)
帧FrameObject(FBO,帧缓存),我们可以在帧幅图像上使用不同的滤镜组合来得到想要的结果。GPUFrame的滤镜组可以说是对这些滤镜的最好替换。
再举个栗子:我写了一个灰度滤镜,可以把图片转成黑白效果,代码如下:
精密中型浮子;
变化的VEC2 vTextureCoord;
统一采样器的2D结构;
void main(){
vec3 centralColor = texture2D(sTexture,vTextureCoord).rgb;
gl_FragColor = vec4(0.299 * centralColor.r + 0.587 * centralColor.g + 0.114 * centralColor.b);
} ```有一天我闲的没事干,又写了一个反色滤镜:```
精密中型浮子; 变化的vec2 vTextureCoord; 统一采样器2D结构; void main(){ vec4 centralColor = texture2D(sTexture,vTextureCoord); gl_FragColor = vec4((1.0-centralColor.rgb),centralColor.w);} ` ` `
现在Boss要求我对视频流先进行黑白处理,再进行反色。这点小事怎么难得到我呢,然后我花了10分钟写出了下面的代码:
精密中型浮子;
变化的vec2 vTextureCoord;
统一采样器2D结构;
void main(){
vec4 centralColor = texture2D(sTexture,vTextureCoord);
gl_FragColor = vec4(0.299 * centralColor.r + 0.587 * centralColor.g + 0.114 * centralColor.b);
gl_FragColor = vec4((1.0-gl_FragColor.rgb),gl_FragColor.w);
} ```
这两个滤镜比较简单(只有一行),如果每个滤镜都很复杂呢?如果组合很多呢?
我们将两个功能写到了一个同一个滤镜里面,这样每次都要修改shader,一点都不优雅,一点都没有体现大学老师辛辛苦苦灌输的OO理念。
在GPUImage中,帧缓存对象就是解决了这个问题的,之前我们都是一次性处理完就放置到屏幕上了,现在不,我们可以将结果保存在帧缓存当中,然后再拿到放置结果作为下一个一次的输入数据来进行处理,于是我的代码就变成了:
filterGroup.addFilter(新GrayScaleShaderFilter(上下文));
filterGroup.addFilter(new InvertColorFilter(context)); ```
如果还要有第三步处理怎么办?再new一个呀!是不是很方便?
** FBO的创建与重组流程**
首先我们需要两个数组,以保存FBO的ID和替换结果的纹理ID。
protected int [] frameBuffers = null;
protected int [] frameBufferTextures = null; ```
没错,FBO也像纹理一样,用一个数字表示。
if (frameBuffers == null) {
frameBuffers = new int[size-1];
frameBufferTextures = new int[size-1];
for (int i = 0; i < size-1; i++) {
GLES20.glGenFramebuffers(1, frameBuffers, i);
GLES20.glGenTextures(1, frameBufferTextures, i);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frameBufferTextures[i]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
filters.get(i).surfaceWidth, filters.get(i).surfaceHeight, 0,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[i]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, frameBufferTextures[i], 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
}
这里的代码比较长,但是和我们之前生成纹理的代码很相似(没有OpenGL ES基础的同学可以看这个)
GLES20.glGenFramebuffers用来生成帧缓存对象- 下面的一大段其实就是生成一个纹理并且用我们当前要绘制的长和宽对其进行配置,并且指定边界的处理情况,放大缩小的策略等- 关键来了:我们用GLES20.glFramebufferTexture2D来把一幅纹理图像关联到一个帧缓存对象,告诉OpenGL这个FBO是关联一个2D纹理的,frameBufferTextures [i]就是和这个FBO关联的纹理- 为什么是size- 1呢,因为我们最后一个纹理是直接替换到屏幕上的呀〜
预测
生成了FBO以后,我们就可以这样改写我们的替换代码
如果(ⅰ
GLES20.glViewport(0,0,filter.surfaceWidth,filter.surfaceHeight);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,frameBuffers [i]);
GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);
filter.onDrawFrame(previousTexture);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
previousTexture = frameBufferTextures [i];
} else {
GLES20.glViewport(0,0,filter.surfaceWidth,filter.surfaceHeight);
filter.onDrawFrame(previousTexture);
} ```
- 每次使用之前先使用glBindFramebuffer绑定FBO,然后这次我们放置的结果就不会显示在屏幕上,而是变成了一个刚才和FBO绑定的纹理对象,然后再用这个纹理给下一个过滤器镜作为输入- 第一个滤镜的输入就是我们的相机或播放器对应的纹理- 最后一个滤镜不需要再输出到FBO了,因此直接替换出来就好。
**滤镜组完整代码**
` ` ` 包com.martin.ads.omoshiroilib.filter.base; 导入android.opengl.GLES20; 导入android.util.Log; 导入java.util.ArrayList; 导入java.util.List; / ** *由Ads于2016/11/19创建。* / 公共类FilterGroup扩展了AbsFilter { private static final String TAG =“ FilterGroup”; protected int [] frameBuffers = null; protected int [] frameBufferTextures = null; 受保护的List < AbsFilter >过滤器; 受保护的boolean isRunning; public FilterGroup(){ super(“ FilterGroup”);
filters=new ArrayList();
}
@Override
public void init() {
for (AbsFilter filter : filters) {
filter.init();
}
isRunning=true;
}
@Override
public void onPreDrawElements() {
}
@Override
public void destroy() {
destroyFrameBuffers();
for (AbsFilter filter : filters) {
filter.destroy();
}
isRunning=false;
}
@Override
public void onDrawFrame(int textureId) {
runPreDrawTasks();
if (frameBuffers == null || frameBufferTextures == null) {
return ;
}
int size = filters.size();
int previousTexture = textureId;
for (int i = 0; i < size; i++) {
AbsFilter filter = filters.get(i);
Log.d(TAG, "onDrawFrame: "+i+" / "+size +" "+filter.getClass().getSimpleName()+" "+
filter.surfaceWidth+" "+filter.surfaceHeight);
if (i < size - 1) {
GLES20.glViewport(0, 0, filter.surfaceWidth, filter.surfaceHeight);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[i]);
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
filter.onDrawFrame(previousTexture);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
previousTexture = frameBufferTextures[i];
}else{
GLES20.glViewport(0,0,filter.surfaceWidth,filter.surfaceHeight);
filter.onDrawFrame(previousTexture);
}
}
}
@覆盖
公共无效onFilterChanged(INT surfaceWidth,INT surfaceHeight){
super.onFilterChanged(surfaceWidth,surfaceHeight);
int size = filter.size();
for(int i = 0; i
filter.get(i).onFilterChanged(surfaceWidth,surfaceHeight);
}
if(frameBuffers!= null){destroyFrameBuffers
();
}
if(frameBuffers == null){
frameBuffers = new int [size-1];
frameBufferTextures = new int [size-1];
为(int i = 0; 我
GLES20.glGenFramebuffers(1,frameBuffers,i);
GLES20.glGenTextures(1,frameBufferTextures,i);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,frameBufferTextures [i]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_RGBA,filters.get
(i).surfaceWidth,filters.get(i).surfaceHeight,0,
GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE,null);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,frameBuffers [i]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER,GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D,frameBufferTextures [i],0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
}
}
}
私有无效destroyFrameBuffers
(){如果(frameBufferTextures!= null){
GLES20.glDeleteTextures(frameBufferTextures.length,frameBufferTextures,0);
frameBufferTextures = null;
}
if(frameBuffers!= null){
GLES20.glDeleteFramebuffers(frameBuffers.length,frameBuffers,0);
frameBuffers = null;
}
}
public void addFilter(最终AbsFilter过滤器){
if(filter == null)返回;
//如果多次添加一个过滤器,
//执行相同的时间
顺便说一句:
((isisRunning){filter.add
(过滤器);
}
其他
addPreDrawTask(新的Runnable(){
@覆盖
公共无效的运行(){
filter.init();
filter.add(过滤器);
onFilterChanged(surfaceWidth, surfaceHeight);
}
});
}
public void addFilterList(最终列表 filterList){
如果(filterList == null)返回;
//如果多次添加一个过滤器,
//将执行相同的时间
// BTW:请注意执行顺序是否
(!){
对于(AbsFilter filter:filterList){
filter.add(filter);
}
}
其他
addPreDrawTask(新的Runnable(){
@覆盖
public void run(){
for(AbsFilter filter:filterList){
filter.init(); filter.add(
过滤器);
)
onFilterChanged(surfaceWidth,surfaceHeight);}} ;}}} ` `作家:马丁,美国国务院链接:HTTPS:[](// blog.csdn.net/Martin20150405/article/details/55520358