Skip to content

Files

Latest commit

 

History

History
228 lines (91 loc) · 10.1 KB

12.FBO.md

File metadata and controls

228 lines (91 loc) · 10.1 KB

12.FBO

什么是 EGL

EGL 是 OpenGL ES 和本地窗口系统(Native Window System)之间的通信接口,它的主要作用: 与设备的原生窗口系统通信;

查询绘图表面的可用类型和配置;

创建绘图表面;

在OpenGL ES 和其他图形渲染API之间同步渲染;

管理纹理贴图等渲染资源。

OpenGL ES 的平台无关性正是借助 EGL 实现的,EGL 屏蔽了不同平台的差异(Apple 提供了自己的 EGL API 的 iOS 实现,自称 EAGL)。

本地窗口相关的 API 提供了访问本地窗口系统的接口,而 EGL 可以创建渲染表面 EGLSurface ,同时提供了图形渲染上下文 EGLContext,用来进行状态管理

当前屏幕渲染(onscreen rendering)和离屏渲染(offscreen rendering) 默认的测试其实就是onscreen rendering,顾名思义,渲染的场景是需要呈现在屏幕上眼睛可见的,而offscreen则是不需要呈现的。 当前屏幕渲染绘制流程是: eglCreateXXXSurface eglCreateContext Draw eglSwapbuffers 离屏渲染流程则是: ``` glGenFramebuffers glBindFramebuffer glFramebufferTexture2D/glFramebufferRenderbuffer

Draw

glFlush (or internal flush)



我们知道eglSwapbuffer是为了把EGL当前渲染的surface给呈现到屏幕上,glFlush是为了把GLES当前渲染的结果绘制到和FBO绑定的Texture或者Render Buffer里面。所以简单的说,onscreen rendering是EGL surface的渲染,而offscreen rendering是GLES FBO的渲染。



当然并不是所有的EGL surface都是onscreen的,一个例外就是EGL pbuffer surface,这是EGL为了offscreen rendering专门定制的。



### Onscreen和Offscreen的关系



那么问题来了,这两者到底有什么关系呢?

下图是一个benchmark某一帧的流程图,

![Image](https://raw.githubusercontent.com/CharonChui/Pictures/master/onscreen_offscreen.webp?raw=true)



图中的FBO0是最终的屏幕渲染输出,而其他用于离屏渲染的FBO都是为了叠加最后的Blur效果和提供背景模版。通常对于复杂的场景,最终的onscreen rendering是由众多的offscreen rendering的结果组合而成的。

Offscreen有什么好处吗



Onscreen rendering的一个限制因素是当前屏幕的分辨率,比如显示器最多只支持1080p,那么EGL surface大小最大只能是1080p。而offscreen rendering则宽松多了,它只受GPU硬件本身的限制,通常可以支持4K分辨率。所以如果需要不同于当前屏幕分辨率的渲染,反锯齿或者添加新的renderpass,通常会先渲染到offscreen来获取更好的渲染效果。



因为offscreen是不可见的,GPU可能会有一定的优化。举个例子,原点(0, 0)对于DirectX来讲,通常在左上角,而对于OpenGLES则在左下角。如果GPU是为DirectX设计的,GPU默认的(0, 0)当然是左上角,那么对于OpenGLES的onscreen rendering,就需要做一个Y坐标的翻转,让原点从左下角映射到左上角。然而对于offscreen rendering,GPU就可以默认其原点在左上角,从而避免坐标转换的损耗。





FBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO)。


顾名思义,它就是能缓存一帧的这么个东西,它有什么用呢?     

大家回想我们之前的教程,我们都是通过一次渲染把内容渲染到屏幕(严格来说是渲染到GLSurfaceview上)。      
如果我们的渲染由多个步骤组成,而每个步骤的渲染结果会给到下一个步骤作为输入,那么就要用到frame buffer。      

比如一个效果:先把图片的蓝色通道全都设置为0.5,得到的结果再去做一个水平方向的模糊,这时渲染过程就由2步组成,第一步的操作不应该显示到屏幕上,应该有个地方存着它的结果,作为第二步的输入,然后第二步的渲染结果才直接显示到屏幕上。实际上这两步可以合成一步,大家可以思考一下如何用一步实现,这里分成两步主要是为了展示如果使用frame buffer。


FBO从名字上看,往往很容易让人误解这是一个缓存空间,但实际上,FBO很重要的在后面的Object上。    

这是一个缓存对象,包含了多个缓冲索引,分别是颜色缓冲(Color buffers)、深度缓冲(Depth buffer)、模板缓冲(Stencil buffer)。之所以说是缓冲索引,是因为FBO并不包含这些缓冲数据,仅仅保存了缓冲数据的索引地址。    

FBO和这些缓冲区则通过附着点进行连接。     

frame buffer本身其实并不会存储数据,都是通过attachment去绑定别的东西来存储相应的数据,我们今天要讲的就是color attachment,我们会将frame buffer中的一个attachment绑定到一个texture上,然后先将第一步的效果渲染到这个frame buffer上作为中间结果,然后将这个texture作为第二步的输入。


FBO本身不能用于渲染,只有添加了纹理或者渲染缓冲区之后才能作为渲染目标,它仅且提供了3种附着(Attachment),分别是颜色附着、深度附着和模板附着。

只有color buffer用于最后的像素显示,其他的都是用来辅助fragment的处理。


RBO(Render Buffer Object)即渲染缓冲区对象,是一个由应用程序分配的2D图像缓冲区。渲染缓冲区可以用于分配和存储颜色、深度或者模板值,可以用作FBO中的颜色、深度或者模板附着。

使用FBO作为渲染目标时,首先需要为FBO的附着添加连接对象,如颜色附着需要连接纹理或者渲染缓冲区对象的颜色缓冲区。


FBO可以绑定到纹理对象或者RenderBuffer对象,RenderBuffer是以内部格式存储的经过渲染优化的对象,它的渲染速度更快,缺点是无法对渲染进果进行重采样。如果不需要对FBO的输出再做下一步采样处理,就可以用RenderBuffer。在我们的例子中,因为我们要暂存相机流处理着色器的渲染结果,并作为灰度黑着色器程序的输入,即要对此输出结果进行采样,所以我们必须要用FBO绑定纹理对象的方式。


GLSurfaceView的onDrawFrame回调中,默认是绑定了window系统生成的FBO的,这个FBO对应屏幕显示,即0号FBO。只要我们中间不切换FBO,所有的glDrawArray或glDrawElements指令调用都是将目标渲染到这个0号FBO的。




#### 为什么用 FBO

默认情况下,OpenGL ES通过绘制到窗口系统提供的帧缓冲区,然后将帧缓冲区的对应区域复制到纹理来实现渲染到纹理,但是此方法只有在纹理尺寸小于或等于帧缓冲区尺寸才有效。       

另一种方式是通过使用连接到纹理的pbuffer来实现渲染到纹理,但是与上下文和窗口系统提供的可绘制表面切换开销也很大。因此,引入了帧缓冲区对象FBO来解决这个问题。

Android OpenGL ES开发中,一般使用GLSurfaceView将绘制结果显示到屏幕上,然而在实际应用中,也有许多场景不需要渲染到屏幕上,如利用GPU在后台完成一些图像转换、缩放等耗时操作,这个时候利用FBO可以方便实现类似需求。

使用FBO可以让渲染操作不用再渲染到屏幕上,而是渲染到离屏Buffer中,然后可以使用glReadPixels或者HardwareBuffer将渲染后的图像数据读出来,从而实现在后台利用GPU完成对图像的处理。


在OpenGL ES中,调用: 
```java
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

表示将当前的帧缓冲绑定切换回默认帧缓冲,即屏幕帧缓冲。这意味着后续的绘图操作将直接渲染到屏幕,而不是一个自定义的 FrameBuffer Object (FBO)。

  1. 默认帧缓冲 • 默认帧缓冲由 OpenGL 创建,并与窗口系统关联。 • 默认帧缓冲的内容直接显示在屏幕上。 • 调用 glBindFramebuffer 将目标设置为 0 时: • GL_FRAMEBUFFER 绑定到默认的屏幕渲染目标。

  2. 切换渲染目标

在 OpenGL 中,你可以使用自定义的帧缓冲对象(FBO)来进行离屏渲染,例如将渲染结果存储到纹理或处理后的数据中。当你完成对自定义 FBO 的渲染任务后,通常需要将绑定切换回默认帧缓冲,这样后续的渲染操作会直接绘制到屏幕。

  1. 操作流程

通常操作流程如下: 1. 创建并绑定 FBO,进行离屏渲染。 2. 渲染完成后,解绑 FBO,将绘图切换回屏幕。 3. 渲染其他内容或将离屏渲染的结果作为纹理在屏幕上显示。

示例代码:

// 绑定自定义 FBO,进行离屏渲染
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, customFbo);
renderToTexture();

// 解绑 FBO,切换回屏幕渲染
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
renderToScreen();

解除FBO绑定,将窗口大小、纹理坐标、矩阵都恢复回原来的配置。 将渲染重新切换到原来的系统窗口上,画面将重新显示到系统窗口上。

作用场景

  1. 屏幕显示

完成离屏渲染后,使用默认帧缓冲可以将最终结果显示到屏幕上。

  1. 渲染链切换

当使用多个渲染目标(如多个 FBO)时,通过解绑 FBO,可以轻松地切换回屏幕渲染或默认的绘图目标。

  1. 调试和测试

解绑 FBO 也方便直接在屏幕上观察渲染结果,便于调试。

首先,Fbo 的概念性的东西,大家可以上网查查,这里就直接说说Fbo的作用

Oes纹理转换2D纹理

预览相机、播放视频等这些通过SurfaceTexture方式渲染的,一般都是使用Oes纹理,而当需要在相机预览或者播放视频中添加水印/贴纸,则需要先将Oes纹理转化成2D纹理,因为Oes纹理和2D纹理是不能同时使用

保留帧

让当前渲染的纹理保留在一个帧缓存里,而不显示在屏幕上