离屏渲染(FrameBufferObject)
今天为大家介绍离屏渲染的概念。在OpenGL中,GPU屏幕渲染有以下两种方式:
1.On-Screen Rendering
意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。(例如我们显示/录制的Surface)
2.Off-Screen Rendering
意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。(区别于Surface的另外一种渲染区)
我们用于预览/录制的Surface都是由Android系统提供给OpenGL渲染的帧缓冲区
,OpenGL的所有渲染结果都是直接到达到帧缓冲区,这是on-screen的渲染方式;
除此方式以外,OpenGL还扩展提供了一种方式来创建额外的帧缓冲区对象(FBO)。使用帧缓冲区对象,OpenGL可以将原先绘制到窗口提供的帧缓冲区重定向到FBO之中。和窗口提供的帧缓冲区类似,FBO提供了一系列的缓冲区,包括颜色缓冲区、深度缓冲区和模板缓冲区。这些逻辑的缓冲区在FBO中被称为 framebuffer-attachable说明它们是可以绑定到FBO的对象数组上。
FBO中有两类绑定的对象:纹理图像(texture images)和渲染图像(renderbuffer images)。如果纹理对象绑定到FBO,那么OpenGL就会执行渲染到纹理(render to texture)的操作,如果渲染对象绑定到FBO,那么OpenGL会执行离屏渲染(offscreen rendering)
FBO可以理解为包含了许多挂接点的一个对象,它自身并不存储图像相关的数据,他提供了一种可以快速切换外部纹理对象和渲染对象挂接点的方式,在FBO中必然包含一个深度缓冲区挂接点和一个模板缓冲区挂接点,同时还包含许多颜色缓冲区挂节点(具体多少个受OpenGL实现的影响,可以通过GL_MAX_COLOR_ATTACHMENTS使用glGet查询),FBO的这些挂接点用来挂接纹理对象和渲染对象,这两类对象中才真正存储了需要被显示的数据。FBO提供了一种快速有效的方法挂接或者解绑这些外部的对象,对于纹理对象使用 glFramebufferTexture2D,对于渲染对象使用glFramebufferRenderbuffer 。具体描述参考下图:
上面是知识概念,而且已经画出了重点。白话文解释:FBO是一个挂接器,类似画家画画用的托架;其中FBO只能挂接两种对象,纹理图像 和 渲染图像,这个理解就是画家准备创作作品前,在托架上放的是油画纸还是水墨纸(纹理),或者根本不是放画纸,放的是木板雕刻(渲染模板)。最后我们等画家创作出他的艺术品后,直接搬到到展示区,呈现給大家。(鼓掌散花)
3、创建FBO
既然我们已经认识了什么是FBO,接下来我们就认识认识关于FBO的OpenGL.API
和其他VAO,VBO,IBO一样,通过调用Gen****,创建FBO,代码如下:
final int frameBuffers[] = new int[1];
GLES20.glGenFramebuffers(1, frameBuffers, 0);
if (frameBuffers[0] == 0) {
int i = GLES20.glGetError();
throw new RuntimeException("Could not create a new frame buffer object, glErrorString : "+ GLES20.glGetString(i));
}
int frameBufferId = frameBuffers[0];
接着,我们开始使用FBO的时候,需要通过绑定纹理对象来锁定挂接渲染区。
int textureId = createFBOTexture(width, height, format); //创建指定format的纹理对象
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId); //绑定fbo进行操作
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, //说明FBO挂接操作
GLES20.GL_COLOR_ATTACHMENT0, //指定挂接区是单元0
GLES20.GL_TEXTURE_2D, //说明挂接的是纹理对象
textureId, //具体挂接的纹理对象
0);
接着我们就可以渲染了(draw),当渲染结束后,我们就需要解绑FBO,告诉OpenGL我们已经不需要在FBO上创作了。
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId);
最后,展览结束关门了,我们需要收拾东西。
GLES20.glDeleteFramebuffers(1, new int[]{frameBufferId}, 0);
GLES20.glDeleteTextures(1, new int[]{textureId}, 0);
4、使用FBO
接下来我给自己的FBO封装使用。
public interface IFboRender {
void onBindFbo();
void onUnbindFbo();
void onSurfaceCreated(int viewWidth, int viewHeight);
void onSurfaceChanged(int width, int height);
void onDrawFrame();
}
public class FboRender implements IFboRender {
private static final String TAG = "FboRender";
private int mFobWidth = 0;
private int mFboHeight = 0;
public final String mVertexShaderStr = "attribute vec4 v_Position;\n" +
" attribute vec2 f_Position;\n" +
" varying vec2 ft_Position;\n" +
" void main() {\n" +
" ft_Position = f_Position;\n" +
" gl_Position = v_Position;\n" +
" }";
public final String mFragmentShaderStr = "precision mediump float;\n" +
"varying vec2 ft_Position;\n" +
"uniform sampler2D sTexture;\n" +
"void main() {\n" +
" gl_FragColor=texture2D(sTexture, ft_Position);\n" +
"}";
/**
* 顶点坐标
*/
private float[] mVertexCoordinate = new float[]{
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
};
private FloatBuffer mVertexBuffer;
/**
* 纹理坐标
*/
private float[] mFragmentCoordinate = new float[]{
0f, 1f,
1f, 1f,
0f, 0f,
1f, 0f
};
private FloatBuffer mFragmentBuffer;
private int mProgram;
private int vPosition;
private int fPosition;
private int uMatrix;
private int mVboId;
private int mFboId;
private int mTextureId;
private float[] matrix = new float[16];
private Context mContext;
public FboRender(Context context) {
mVertexBuffer = ByteBuffer.allocateDirect(mVertexCoordinate.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(mVertexCoordinate);
mVertexBuffer.position(0);
mFragmentBuffer = ByteBuffer.allocateDirect(mFragmentCoordinate.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(mFragmentCoordinate);
mFragmentBuffer.position(0);
this.mContext = context;
mFobWidth = Utils.getScreenWidth(context);
mFboHeight = Utils.getScreenHeight(context);
}
@Override
public void onSurfaceCreated(int viewWidth, int viewHeight) {
mProgram = Utils.createProgram(mVertexShaderStr, mFragmentShaderStr);
// 获取坐标
vPosition = GLES20.glGetAttribLocation(mProgram, "v_Position");
fPosition = GLES20.glGetAttribLocation(mProgram, "f_Position");
int sTexture = GLES20.glGetUniformLocation(mProgram, "sTexture");
uMatrix = GLES20.glGetUniformLocation(mProgram, "u_Matrix");
// 创建 vbos,存储位置信息的缓冲区
int[] vBos = new int[1];
GLES20.glGenBuffers(1, vBos, 0);
// 绑定 vbos
mVboId = vBos[0];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId);
// 开辟 vbos
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, (mVertexCoordinate.length + mFragmentCoordinate.length) * 4,
null, GLES20.GL_STATIC_DRAW);
// 赋值 vbos
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, mVertexCoordinate.length * 4, mVertexBuffer);
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, mVertexCoordinate.length * 4,
mFragmentCoordinate.length * 4, mFragmentBuffer);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
// 创建纹理,用于和frameBuffer绑定
int[] textureIds = new int[1];
GLES20.glGenTextures(1, textureIds, 0);
mTextureId = textureIds[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glUniform1i(sTexture, 0);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
// 创建 fbo 并把纹理绑定到 fbo
int[] fBoIds = new int[1];
GLES20.glGenFramebuffers(1, fBoIds, 0);
mFboId = fBoIds[0];
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFboId);
if (viewWidth > 0) {
mFobWidth = viewWidth;
}
if (viewHeight > 0) {
mFboHeight = viewHeight;
}
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, mFobWidth, mFboHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, mTextureId, 0);
if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
Log.e(TAG, "fbo bind failure");
} else {
Log.e(TAG, "fbo bind success");
}
//清除绑定
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
@Override
public void onSurfaceChanged(int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame() {
GLES20.glUseProgram(mProgram);
// 绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
// 设置正交矩阵的值
GLES20.glUniformMatrix4fv(uMatrix, 1, false, matrix, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboId);
/**
* 设置坐标
* 2:2个为一个点
* GLES20.GL_FLOAT:float 类型
* false:不做归一化
* 8:步长是 8
*/
GLES20.glEnableVertexAttribArray(vPosition);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
GLES20.glEnableVertexAttribArray(fPosition);
GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, mVertexCoordinate.length * 4);
// 绘制到屏幕
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// 解绑
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
@Override
public void onBindFbo() {
//激活frameBuffer,然后下文绘制的数据,会保存到frameBuffer中
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFboId);
}
@Override
public void onUnbindFbo() {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
public int getTextureId() {
return mTextureId;
}
}
只需要fbo的生命周期和render的保持一致就好
public final class CameraRender extends GLSurfaceView.Renderer{
public CameraRender(Context context) {
//...CameraRender的初始化
mFboRender = new FboRender(mContext);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mFboRender.onSurfaceCreated(mViewWidth, mViewHeight);
//...正常的生成纹理,绑定顶点数据等
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mFboRender.onSurfaceChanged(width, height);
GLES20.glViewport(0, 0, width, height);
Matrix.rotateM(matrix, 0, 180, 1, 0, 0);
}
@Override
public void onDrawFrame(GL10 gl) {
// 绑定 fbo
mFboRender.onBindFbo();
//...省略的绘制纹理操作
mFboRender.onUnbindFbo();
// 再把 fbo 绘制到屏幕
mFboRender.onDrawFrame();
}
}
当我们向fbo挂载一个纹理对象后,在onDrawFrame启用了fbo,在当前环境所渲染的任何指令,都是输出到这个fbo挂载的纹理对象当中。当每结束绘制一帧的时候,我们再拿和fbo绑定的mTextureId做渲染,如果注释掉GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4),取消绘制,则会发现什么都没有显示。