前言
FBO(Frame Buffer Object)在 OpenGL ES 中是一个用于自定义渲染目标的对象。简单来说,它允许开发者创建一个不在屏幕上显示的缓冲区,来存储渲染的结果。这意味着可以灵活地控制渲染的输出位置和方式,而不仅仅局限于将图形直接渲染到屏幕的默认帧缓冲区。
FBO 的主要特点和作用包括:
- 离屏渲染:能够在内存中进行渲染,而不立即显示在屏幕上。这对于一些需要预计算或中间处理的图形操作非常有用。
- 多渲染目标:可以同时将渲染结果输出到多个缓冲区,例如同时生成颜色、深度和模板信息。
- 提高效率:避免了不必要的屏幕重绘,节省了系统资源和提高了渲染性能。
今天我们尝试使用fbo渲染一张纹理图片,然后将此图片再渲染到屏幕上。
1.创建render
代码实现基于GlSurfaceView,为它创建一个Render对象,核心逻辑都在render中。
public class MyFboRender implements GLSurfaceView.Renderer {
public static int sScreenWidth;
public static int sScreenHeight;
private final Context mContext;
private MyFboShape myFboShape;
public MyFboRender(@NonNull Context context) {
this.mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1);
myFboShape = new MyFboShape(mContext);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
sScreenWidth = width;
sScreenHeight = height;
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
myFboShape.draw();
}
}
2.创建shape对象,用于封装所有opengl操作
在shape的构造方法中创建直接内存用于存储顶点数据和纹理坐标。
public MyFboShape(Context mContext) {
this.mContext = mContext;
mSqureBuffer = ByteBuffer.allocateDirect(bgVertex.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mSqureBuffer.put(bgVertex);
mSqureBuffer.position(0);
mSqureBufferfbo = ByteBuffer.allocateDirect(fboVertex.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mSqureBufferfbo.put(fboVertex);
mSqureBufferfbo.position(0);
mLoadedTextureId = initTexture(R.mipmap.bg);
}
同时会加载一张纹理图片,并返回其句柄mLoadedTextureId备用。
private int initTexture(int res) {
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), res);
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
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_T, GLES20.GL_MIRRORED_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_MIRRORED_REPEAT);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textures[0];
}
3.draw开始绘制
创建帧缓冲区
int[] frameBuffers = new int[1];
GLES20.glGenFramebuffers(1, frameBuffers, 0);
创建纹理对象并设置格式
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
int colorTextureId = textures[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorTextureId);
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_RGBA, MyFboRender.sScreenWidth, MyFboRender.sScreenHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
为帧缓冲区设置附加纹理
int frameBufferId = frameBuffers[0];
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, colorTextureId, 0);
if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
Log.e("rzm", "glFramebufferTexture2D error");
}
创建顶点着色器和片段着色器
int frameBufferVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int frameBufferFagmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
加载shader的工具方法
private int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
int[] params = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, params, 0);
if (params[0] == 0) {
Log.e("rzm", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = -1;
}
return shader;
}
创建program并将shader附加到program
mFrameBufferProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mFrameBufferProgram, frameBufferVertexShader);
GLES20.glAttachShader(mFrameBufferProgram, frameBufferFagmentShader);
GLES20.glLinkProgram(mFrameBufferProgram);
int[] params = new int[1];
GLES20.glGetProgramiv(mFrameBufferProgram, GLES20.GL_LINK_STATUS, params, 0);
if (params[0] != 0) {
Log.e("rzm", GLES20.glGetProgramInfoLog(mFrameBufferProgram));
}
GLES20.glUseProgram(mFrameBufferProgram);
着色器参数配置
int aPositionLocation = GLES20.glGetAttribLocation(mFrameBufferProgram, "aPosition");
int aTextureCoordPosition = GLES20.glGetAttribLocation(mFrameBufferProgram, "aTextureCoord");
int uTextureLocation = GLES20.glGetUniformLocation(mFrameBufferProgram, "uTexture");
mSqureBufferfbo.position(0);
GLES20.glVertexAttribPointer(aPositionLocation, 2, GLES20.GL_FLOAT, false, 4 * (2 + 2), mSqureBufferfbo);
mSqureBufferfbo.position(2);
GLES20.glVertexAttribPointer(aTextureCoordPosition, 2, GLES20.GL_FLOAT, false, 4 * (2 + 2), mSqureBufferfbo);
GLES20.glEnableVertexAttribArray(aPositionLocation);
GLES20.glEnableVertexAttribArray(aTextureCoordPosition);
激活纹理并将加载了一张图片的纹理对象传递到shader
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mLoadedTextureId);
GLES20.glUniform1i(uTextureLocation, 0);
绘制到fbo
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
注意,从fbo绑定,到fbo解绑中间的一些列操作都是针对帧缓冲区的。
接下来开始将fbo中的图片会知道屏幕
创建另一个program用于绘制
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, windowVertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, windowFragmentShaderCode);
mWindowProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mWindowProgram, vertexShader);
GLES20.glAttachShader(mWindowProgram, fragmentShader);
GLES20.glLinkProgram(mWindowProgram);
int[] params2 = new int[1];
GLES20.glGetProgramiv(mFrameBufferProgram, GLES20.GL_LINK_STATUS, params2, 0);
if (params2[0] != 0) {
Log.e("rzm", GLES20.glGetProgramInfoLog(mWindowProgram));
}
GLES20.glUseProgram(mWindowProgram);
为着色器配置参数
int positionHandle = GLES20.glGetAttribLocation(mWindowProgram, "aPosition");
int textureCoordHandle = GLES20.glGetAttribLocation(mWindowProgram, "aTextureCoord");
int textureHandle = GLES20.glGetUniformLocation(mWindowProgram, "uTexture");
mSqureBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 4 * (2 + 2), mSqureBuffer);
mSqureBuffer.position(2);
GLES20.glVertexAttribPointer(textureCoordHandle, 2, GLES20.GL_FLOAT, false, 4 * (2 + 2), mSqureBuffer);
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(textureCoordHandle);
激活纹理并绘制到屏幕
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorTextureId);
GLES20.glUniform1i(textureHandle, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
总结
- 整个过程共创建了两个纹理对象,第一个是加载了一张图片的mLoadedTextureId,第二个是创建后设置给fbo的colorTextureId。
- 这两个纹理对象是从fbo绘制到屏幕的关键。
- 第一步我们先绑定fbo,然后将加载了图片的mLoadedTextureId绘制到fbo中。
- 第二部通过fbo附加的colorTextureId将图片绘制到屏幕上。
- 所以图像其实就是在两张纹理之间的传递。