0%

如何使用FBO实现离屏渲染

上一篇文章中我已经向你介绍了二次渲染和BlitFramebuffer两种离屏渲染技术,今天我们来看一下如何通过FBO实现离屏渲染。

什么是FBO

FBO(Framebuffer Object),它是一个对象而不是一个缓冲区,不能直接用来存放视频帧。其示意图如下所示:

从图中我们可以看到,FBO包含了三种附着:第一种颜色附着,用来存储渲染结果的颜色信息,可以有多个,每个对应一个颜色缓冲区。颜色附着可以是纹理或渲染缓冲对象,可以用来做一些后处理效果,如光照、阴影、模糊等。第二种深度附着,用来存储渲染结果的深度信息,只能有一个,对应一个深度缓冲区。深度附着可以是纹理或渲染缓冲对象,可以用来实现一些特殊的渲染效果,如遮罩、轮廓、镜面反射等。第三种模板附着,用来存储渲染结果的模板信息,只能有一个,对应一个模板缓冲区。模板附着可以是纹理或渲染缓冲对象,可以用来实现一些特殊的渲染效果,如遮罩、轮廓、镜面反射等。

一般情况下,我们只使用颜色附着和深度附着,颜色附着使用纹理作为缓冲区,而深度附着使用Renderbuffer作为缓冲区。

此外我们还要知道,FBO是用来代替EGL中的Pbuffer的,因为FBO要比Pbuffer高效得多。

接下来我们来看一下如何使用FBO。

创建FBO

使用FBO的第一步是创建FBO对象,而创建FBO对象的时机非常关键,在什么地方创建FBO比较合适呢?

一个比较好的时机是在收到SurfaceView的surfaceChanged事件时创建FBO,这样就可以根据Surface的变化来随时变更FBO了。其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void surfaceChanged(...){
int[] values = new int[1];

//创建纹理ID
GLES20.glGenTextures(1, values, 0);
mOffscreenTexture = values[0]; // expected > 0
//绑定纹理ID
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTexture);

//为纹理分配空间
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_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);

//创建FBO ID
GLES20.glGenFramebuffers(1, values, 0);
mFramebuffer = values[0]; // expected > 0
//绑定FBO ID
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebuffer);

//创建Renderbuffer ID
GLES20.glGenRenderbuffers(1, values, 0);
mDepthBuffer = values[0]; // expected > 0
//绑定Renderbuffer ID
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);

//为Renderbuffer分配空间
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
width, height);

//为FBO添加深度附着
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
GLES20.GL_RENDERBUFFER, mDepthBuffer);
//为FBO添加颜色附着
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);

//解绑FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

}

在上述代码中,首先创建了一个纹理ID,之后将该纹理ID与GPU进行了绑定。紧接着为纹理分配了空间,并设置了相关的参数,这些内容我们在前面的课程中都做过详细讲解,这里就不赘述了。

之后代码中调用**glGenFramebuffers( )**方法生成了FBO ID,并将FBO ID与GPU进行了绑定。再下来开始创建Renderbuffer。

调用**glGenRenderbuffers( )方法生成Renderbuffer ID,然后让它与GPU进行绑定,同时使用glRenderbufferStorage( )**为Renderbuffer分配空间。

最后调用**glFramebufferRenderbuffer( )glFramebufferTexture2D( )**方法分别为FBO设置深度附着和颜色附着。

创建好FBO后,接下来咱们看看如何使用FBO实现离屏渲染。

使用FBO实现离屏渲染

当FBO创建好后,我们如何通过FBO实现离屏渲染呢?其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void doFrame(...){
...
//重新绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebuffer);
//使用OpenGL渲染模型,并输出到新绑定的FBO中
draw();

//重新将FBO恢复
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
//将OffscreenTexture纹理中的内容通过OpenGL渲染到窗口Surface
mFullScreen.drawFrame(mOffscreenTexture, mIdentityMatrix);
//将窗口Surface的内容输出到屏幕上
swapResult = mWindowSurface.swapBuffers();

//切到编码器Surface
mInputWindowSurface.makeCurrent();
...
//再次将OffscreenTexture纹理中的内容时行渲染,不过此次渲染到编码器Surface中
mFullScreen.drawFrame(mOffscreenTexture, mIdentityMatrix);
//将编码器Surface中的内容输出给编码器进行编码
mInputWindowSurface.swapBuffers();

//将当前Surface恢复为窗口Surface
mWindowSurface.makeCurrent();
...
}

在上述代码中,首先重新将FBO ID绑定到GPU上,之后调用draw( )方法通过OpenGL渲染模型,而渲染后的结果就会默认输出到FBO上。

之后,将FBO与GPU解绑,那么此后输出的内容又会回到之前的逻辑,输出给窗口Surface。因此,接下来调用**mFullScreen.drawFrame(…)方法时,它会从FBO的颜色附着纹理中读取数据,再通过OpenGL将其渲染到窗口Surface中,最后通过mWindowSurface.swapBuffers( )**方法将其展示到屏幕上。

再下来,与二次渲染一样,切换到编码器Surface,并再次调用drawFrame( )方法,让OpenGL将OffscreenTexture纹理中的内容渲染到编码器Surface中,然后通过swapBuffers( )方法将渲染内容送编码器编码。

最后,再将当前Surface恢复为窗口Surface。至此就完成了通过FBO实现离屏渲染的工作。

小结

本文我向你介绍了什么是FBO,如何创建FBO以及如何使用FBO。这里有两个知识点我们需要弄清楚,第一个是当我们绑定FBO后,OpenGL渲染的结果就会被自动写入到FBO中。第二个,当需要将FBO中的内容渲染到屏幕时,首先要解绑FBO,之后把FBO的颜色纹理当作输入,再利用OpenGL就可以将FBO中的内容输出到窗口Surface中了。

参考资料

系统玩转OpenGL+AI,实现各种酷炫视频特效

欢迎关注我的其它发布渠道