上一篇文章中我已经向你介绍了二次渲染和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];
GLES20.glGenTextures(1, values, 0); mOffscreenTexture = values[0]; 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);
GLES20.glGenFramebuffers(1, values, 0); mFramebuffer = values[0]; GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebuffer);
GLES20.glGenRenderbuffers(1, values, 0); mDepthBuffer = values[0]; GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height);
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, mDepthBuffer); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);
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(...){ ... GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebuffer); draw();
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); mFullScreen.drawFrame(mOffscreenTexture, mIdentityMatrix); swapResult = mWindowSurface.swapBuffers();
mInputWindowSurface.makeCurrent(); ... mFullScreen.drawFrame(mOffscreenTexture, mIdentityMatrix); mInputWindowSurface.swapBuffers();
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,实现各种酷炫视频特效