上一章探讨了如何采用SurfaceTexture+GLSurfaceView显示YUV数据,减少了片段着色器的工作量和代码量,但是采用GLSL采用的外部纹理真正的内容是在物理内存中,GPU只负责维护元数据,这样就增加了GPU取数据的时间,若进行一些运算密集的算法例如高斯滤波,每次都会到外部纹理取数据,这样则会造成明显的卡顿,所以仅采用外部纹理实现实时滤镜行不通。同时采用JNI格式转换随着预览分辨率提高时间也达不到要求,采用Shader转换则每次加权计算都需要进行格式转换,也增加了运算量。

好在OpenGL允许用户创建帧缓存对象,可以将数据渲染到纹理。因此我们可以将对应相机预览图像的外部纹理先渲染到内部纹理中,剩下的滤镜再绑定到该纹理,对其进行操作,这样就增加渲染速度,经实验帧数达到要求。

以下代码和思路参考GPUImage中的GPUImageFilterGroup,因为Android平台采用的是OpenGL ES,与OpenGL略有不同。

一.分配帧缓冲对象:

1)创建帧缓冲对象:

有N+1个滤镜(其中第一个从外部纹理接收的无滤镜效果),就需要分配N个帧缓冲对象,首先创建大小为N的两个数组mFrameBuffers和mFrameBufferTextures,分别用来存储缓冲区id和纹理id,通过GLES20.glGenFramebuffers(1, mFrameBuffers, i)来创建帧缓冲对象

2)创建纹理:

创建输出纹理,方法基本相同,不同之处在于glTexImage2D最后一个参数为null,不指定数据指针。

3)绑定帧缓冲区:

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]),第一个参数是target,指的是你要把FBO与哪种帧缓冲区进行绑定。

4)绑定纹理:

GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i], 0)

glFramebufferTexture2D()把一幅纹理图像关联到一个FBO。第一个参数一定是GL_FRAMEBUFFER_,第二个参数是关联纹理图像的关联点。一个帧缓冲区对象可以有多个颜色关联点(GL_COLOR_ATTACHMENT0, ..., GL_COLOR_ATTACHMENTn),L_DEPTH_ATTACHMENT, 和GL_STENCIL_ATTACHMENT。第三个参数textureTarget在多数情况下是GL_TEXTURE_2D。第四个参数是纹理对象的ID号。最后一个参数是要被关联的纹理的mipmap等级 如果参数textureId被设置为0,那么纹理图像将会被从FBO分离。如果纹理对象在依然关联在FBO上时被删除,那么纹理对象将会自动从当前帮的FBO上分离。然而,如果它被关联到多个FBO上然后被删除,那么它将只被从绑定的FBO上分离,而不会被从其他非绑定的FBO上分离。

5)解绑默认帧缓存和纹理:

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

二.依次绘制:

首先第一个一定是绘制与SurfaceTexture绑定的外部纹理处理后的无滤镜效果,之后的操作与第一个一样,都是绘制到纹理。首先与之前相同传入纹理id,并重新绑定到对应的缓冲区对象GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]),之后draw对应的纹理id。若不是最后一个滤镜,需要解绑缓冲区,下一个滤镜的新的纹理id即上一个滤镜的缓冲区对象所对应的纹理id,同样执行上述步骤,直到最后一个滤镜。

三.关键代码:

1)创建部分:

for (int i = 0; i < size - 1; i++) {

GLES20.glGenFramebuffers(1, mFrameBuffers, i);

GLES20.glGenTextures(1, mFrameBufferTextures, i);

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i]);

GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 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, mFrameBuffers[i]);

GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,

GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i], 0);

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

}

绘制部分:

for (int i = 0; i < size; i++) {

GPUImageFilter filter = mMergedFilters.get(i);

boolean isNotLast = i < size - 1;

if (isNotLast) {

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]);

GLES20.glClearColor(0, 0, 0, 0);

}

if (i == 0) {

filter.onDraw(previousTexture, cubeBuffer, textureBuffer);

} else if (i == size - 1) {

filter.onDraw(previousTexture, mGLCubeBuffer, (size % 2 == 0) ? mGLTextureFlipBuffer : mGLTextureBuffer);

} else {

filter.onDraw(previousTexture, mGLCubeBuffer, mGLTextureBuffer);

}

if (isNotLast) {

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

previousTexture = mFrameBufferTextures[i];

}

根据上述思路,我们可以对GPUImage进行修改,首先是取消JNI部分转换,将SurfaceTexure与GLSurfaceView绑定(GPUImage中虽然使用了SurfaceTexture,但明显没有用到它的作用,并且addPreviewCallback也使用错误,毕竟2年没有修改过了)。然后选取滤镜时,将Camera输出到缓冲区对象,选取的滤镜对该纹理进行渲染即可。可以重构GPUImage中的滤镜基类,将所有滤镜的基类都归为Group滤镜,然后修改Group滤镜的一些方法。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐