上文中我们已经实现了将OpenGL
和相机结合到一起,本文就在上文的基础上,添加滤镜。
添加滤镜后的整体流程.png
上编文章,我们是直接绘制OES的纹理。这里,因为要添加滤镜的效果。所以我们需要将纹理进行处理。
离屏绘制.png
先将OES
纹理,绑定到FrameBuffer
上。同时会在FrameBuffer
上绑定一个新的textureId
(这里命名为OffscreenTextureId
)。然后调用绘制OES
纹理的方法,数据就会传递到FBO
上。而我们可以通过绑定在其上的OffscreenTextureId
得到其数据。通常情况下,我们把绑定FrameBuffer
和绘制这个新的OffscreenTextureId
代表的纹理的过程,称为离屏绘制。
FrameBuffer
的时机创建FrameBuffer
。因为RenderBuffer的存储大小要和当前的显示的宽和高相关。所以会在onSurfaceChanged
生命周期方法时候调用。
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//在这里监听到尺寸的改变。做出对应的变化
prepareFramebuffer(width, height);
//...
}
//生成frameBuffer的时机
private void prepareFramebuffer(int width, int height) {
int[] values = new int[1];
//申请一个与FrameBuffer绑定的textureId
GLES20.glGenTextures(1, values, 0);
mOffscreenTextureId = values[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTextureId);
// Create texture storage.
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
// Set parameters. We're probably using non-power-of-two dimensions, so
// some values may not be available for use.
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
//创建FrameBuffer Object并且绑定它
GLES20.glGenFramebuffers(1, values, 0);
mFrameBuffer = values[0];
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);
// 创建RenderBuffer Object并且绑定它
GLES20.glGenRenderbuffers(1, values, 0);
mRenderBuffer = values[0];
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mRenderBuffer);
//为我们的RenderBuffer申请存储空间
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height);
// 将renderBuffer挂载到frameBuffer的depth attachment 上。就上面申请了OffScreenId和FrameBuffer相关联
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, mRenderBuffer);
// 将text2d挂载到frameBuffer的color attachment上
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mOffscreenTextureId, 0);
// See if GLES is happy with all this.
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
throw new RuntimeException("Framebuffer not complete, status=" + status);
}
// 先不使用FrameBuffer,将其切换掉。到开始绘制的时候,在绑定回来
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
//在onDrawFrame中添加代码
@Override
public void onDrawFrame(GL10 gl) {
//...省略
//重新切换到FrameBuffer上。
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);
//这里的绘制,就会将数据挂载到FrameBuffer上了。
mOesFilter.draw();
//解除绑定,结束FrameBuffer部分的数据写入
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
//....省略
}
FrameBuffer
帧缓冲对象
openGL绘制流程.png
我们自己创建的FrameBuffer其实只是一个容器。所以我们要将数据挂载上去,它才算是完整。
FrameBuffer.png
所以,我们可以看到申请FrameBuffer需要进行下面的三步
GL_DEPTH_ATTACHMENT
上。
RenderBuffer
也是一个渲染缓冲区对象。RenderBuffer
对象是新引入的用于离屏渲染。它允许将场景直接渲染到Renderbuffer
对象,而不是渲染到纹理对象。Renderbuffe
r只是一个包含可渲染内部格式的单个映像的数据存储对象。它用于存储没有相应纹理格式的OpenGL
逻辑缓冲区,如模板或深度缓冲区。
textureId
,挂载到GL_COLOR_ATTACHMENT0
上。我们就可以通过这个纹理,得到保存在FBO
上的数据了
添加滤镜.png
我们可以通过FBO
,进行滤镜处理。我们将得到的数据,再次进行绘制,在这次的绘制中,我们就可以添加上我们想要的滤镜处理了。
但是这里不仅仅是要绘制到屏幕上,同时要在开启录制的时候,输入给Encoder进行视频的编码和封装。 所以我们需要将数据再写写入一个新的FrameBuffer中,然后再其输出的outputTexture中,就可以得到应用了纹理的数据了。
@Override
public void onDrawFrame(GL10 gl) {
//...省略
//经过路径处理
mColorFilter.setTextureId(mOffscreenTextureId);
mColorFilter.onDrawFrame();
int outputTextureId = mColorFilter.getOutputTextureId();
//...省略
}
同时滤镜内,也按照上面的FrameBuffer
的处理流程。将数据挂载到FrameBuffer
上。得到挂载在FrameBuffer
上的outputTextureId
代码同上,省略
GLView
和Encoder
当中image.png
@Override
public void onDrawFrame(GL10 gl) {
//省略...
//经过滤镜处理
mColorFilter.setTextureId(mOffscreenTextureId);
mColorFilter.onDrawFrame();
int outputTextureId = mColorFilter.getOutputTextureId();
//将得到的outputTextureId,输入encoder,进行编码
mVideoEncoder.setTextureId(outputTextureId);
mVideoEncoder.frameAvailable(mSurfaceTexture);
//将得到的outputTextureId,再次Draw,因为没有FrameBuffer,所以这次Draw的数据,就直接到了Surface上了。
mShowFilter.setTextureId(outputTextureId);
mShowFilter.onDrawFrame();
}
outputTextureId
,输入Encoder
的InputSurface
中,通知内部进行draw
和进行编码。outputTextureId
,再次Draw,因为没有FrameBuffer
,所以这次draw
的数据,就直接到了Surface
上了。也就是直接绘制到了我们的GLSurfaceView
上了。GLView
上显示,而这里是将纹理绘制到绑定的FrameBuffer
中,而且
绘制的结果不直接显示出来。所以可以形象的理解离屏绘制,就是将绘制的结果保存在与FrameBuffer
绑定的一个新的textureId
(OffscreenTextureId
)中,不直接绘制到屏幕上。