什么是 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某一帧的流程图,

图中的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)。
-
默认帧缓冲 • 默认帧缓冲由 OpenGL 创建,并与窗口系统关联。 • 默认帧缓冲的内容直接显示在屏幕上。 • 调用 glBindFramebuffer 将目标设置为 0 时: • GL_FRAMEBUFFER 绑定到默认的屏幕渲染目标。
-
切换渲染目标
在 OpenGL 中,你可以使用自定义的帧缓冲对象(FBO)来进行离屏渲染,例如将渲染结果存储到纹理或处理后的数据中。当你完成对自定义 FBO 的渲染任务后,通常需要将绑定切换回默认帧缓冲,这样后续的渲染操作会直接绘制到屏幕。
- 操作流程
通常操作流程如下: 1. 创建并绑定 FBO,进行离屏渲染。 2. 渲染完成后,解绑 FBO,将绘图切换回屏幕。 3. 渲染其他内容或将离屏渲染的结果作为纹理在屏幕上显示。
示例代码:
// 绑定自定义 FBO,进行离屏渲染
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, customFbo);
renderToTexture();
// 解绑 FBO,切换回屏幕渲染
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
renderToScreen();
解除FBO绑定,将窗口大小、纹理坐标、矩阵都恢复回原来的配置。 将渲染重新切换到原来的系统窗口上,画面将重新显示到系统窗口上。
作用场景
- 屏幕显示
完成离屏渲染后,使用默认帧缓冲可以将最终结果显示到屏幕上。
- 渲染链切换
当使用多个渲染目标(如多个 FBO)时,通过解绑 FBO,可以轻松地切换回屏幕渲染或默认的绘图目标。
- 调试和测试
解绑 FBO 也方便直接在屏幕上观察渲染结果,便于调试。
首先,Fbo 的概念性的东西,大家可以上网查查,这里就直接说说Fbo的作用
Oes纹理转换2D纹理
预览相机、播放视频等这些通过SurfaceTexture方式渲染的,一般都是使用Oes纹理,而当需要在相机预览或者播放视频中添加水印/贴纸,则需要先将Oes纹理转化成2D纹理,因为Oes纹理和2D纹理是不能同时使用
保留帧
让当前渲染的纹理保留在一个帧缓存里,而不显示在屏幕上
- 邮箱 :charon.chui@gmail.com
- Good Luck!