帧缓冲对象FBO
渲染管线的最后一个阶段是到帧缓冲区。前面学习的好多知识所做的渲染操作都是在默认的帧缓冲中进行的,这个默认的帧缓冲是我们创建一个Surface时自动创建和配置好的,这篇博客就创建我们自己的缓冲区而不使用系统提供的缓冲区,这样就有了另一种渲染方式,默认情况下,我们使用OpenGL ES使用的窗口系统提供的帧缓冲区,这样绘制的结果是显示到屏幕上,然而实际中有很多情况并不需要渲染到屏幕上,那么使用窗口系统提供的帧缓冲区就不太好了,这个时候使用FBO(Frame Buffer Object)就可以很方便的实现这类需求。
我们知道显示到屏幕上的每一帧数据其实对应的就是内存中的数据,在内存中对应分配着存储帧数据的缓冲区,包括写入颜色的颜色缓冲,写入深度值的深度缓冲,以及基于一些条件丢弃片元的模板缓冲,这几种缓冲一起称之为帧缓冲。
帧缓冲区对象(FBO)是一组颜色、深度、模板附着点,纹理对象可以连接到帧缓冲区对象的颜色附着点,同时也可以连接到FBO的深度附着点,另外一种可以连接到深度附着点和模板附着点的一类对象叫做渲染缓冲区对象(RBO)
创建帧缓冲对象
和创建纹理类似,可以使用glGenFramebuffers
函数创建帧缓冲。
public static native void glGenFramebuffers(int n, int[] framebuffers, int offset);
返回的framebuffers中包含了生成的帧缓冲对象,该帧缓冲对象是不为0的整数,0用来表示窗口系统生成的帧缓冲区,创建完帧缓冲对象后,接下来要将其绑定到当前帧缓冲。
使用glBindFramebuffer
函数用于设置当前帧缓冲区。
public static native void glBindFramebuffer(
int target, // 一般设置为GL_FRAME_BUFFER
int framebuffer // 帧缓冲区对象
);
绑定到GL_FRAMEBUFFER
目标后,接下来所有的读、写帧缓冲的操作都会影响到当前绑定的帧缓冲。也可以把帧缓冲分开绑定到读或写目标上,分别使用GL_READ_FRAMEBUFFER
或GL_DRAW_FRAMEBUFFER
来做这件事。如果绑定到了GL_READ_FRAMEBUFFER
,就能执行所有读取操作,像glReadPixels这样的函数使用了;绑定到GL_DRAW_FRAMEBUFFER
上,就允许进行渲染、清空和其他的写入操作。大多数时候不必分开用,通常把两个都绑定到GL_FRAMEBUFFER
上就行。
此时创建的帧缓冲对象其实只是一个“空壳”,它上面还包含一些附着,因此接下来还必须往它里面添加至少一个附着才可以使用。创建的帧缓冲必须至少添加一个附着点(颜色、深度、模板缓冲)并且至少有一个颜色附着点。
一个缓冲区就是一个内存空间,可以添加的附着点可以是纹理对象或者渲染缓冲对象
纹理附着
当把一个纹理扶着到FBO上后,所有的渲染操作就会写入到该纹理上,意味着所有的渲染操作会被存储到纹理图像上,这样做的好处是显而易见的,我们可以在着色器中使用这个纹理。
int [] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
int textureId = textures[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
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.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_MIRRORED_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_MIRRORED_REPEAT);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, ShapeView.sScreenWidth, ShapeView.sScreenHeight, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, null);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureId, 0);
创建纹理的方式和前面学的一样,区别是使用了glTexImage2D函数,前面使用GLUtils#texImage2D函数加载一幅2D图像作为纹理对象,这里的glTexImage2D稍显复杂,这里重要的是最后一个参数,如果为null就会自动分配可以容纳相应宽高的纹理,然后后续的渲染操作就会存储到这个纹理上了。
就下来需要将这个纹理附着到帧缓冲中去
public static native void glFramebufferTexture2D(
int target, // 创建的帧缓冲类型的目标,一般为GL_FRAMEBUFFER
int attachment, // 附着点,这里附着的事一个纹理,需要传入参数为一个颜色附着点
int textarget, // 希望附着的纹理类型
int texture, // 附加的纹理对象ID
int level // Mipmap level 一般设置为0
);
attachment可以为一下几个枚举值: GL_COLOR_ATTACHMENT0
、GL_DEPTH_ATTACHMENT
、GL_STENCIL_ATTACHMENT
分别对应颜色缓冲、深度缓冲和模板缓冲。
除了颜色附着,还可以附加深度和模板附着到帧缓冲对象上。附着深度缓冲可以使用GL_DEPTH_ATTACHMENT
作为附着类型,此时纹理的内部类型为GL_DEPTH_COMPONENT
(32位深),附着模板缓冲使用GL_STENCIL_ATTACHMENT
附着点,对应文理类型为GL_STENCIL_INDEX
。
最后使用glFramebufferTexture2D
函数将2D纹理附着到帧缓冲对象。
public static native void glFramebufferTexture2D(
int target, // GL_FRAMEBUFFER
int attachment, // GL_COLOR_ATTACHMENT、GL_DEPTH_ATTACHMENT或GL_STENCIL_ATTACHMENT
int textarget, // 纹理目标,和glTeXImag