The original article was first published on the wechat official account Byteflow

What is a FBO

FBO (Frame Buffer Object) is actually a container for adding buffers to which you can add textures or render Buffer objects (RBO).

FBO itself can not be used for rendering, only after adding texture or rendering buffer can be used as a rendering target, it only provides three attachments, respectively color Attachment, depth Attachment and template Attachment.

Render Buffer Object (RBO) is a 2D image Buffer allocated by the application. The render buffer can be used to allocate and store color, depth, or template values and can be used as a color, depth, or template attachment in the FBO.

When using an FBO as a rendering target, you first need to add a connection object for the attachment of the FBO, such as a color buffer that needs to be attached to a texture or render buffer object.

Why FBO

By default, OpenGL ES renders to a texture by drawing to the frame buffer provided by the window system and then copying the corresponding region of the frame buffer into the texture, but this method only works if the texture size is less than or equal to the frame buffer size.

Another approach is to implement rendering to texture by using pBuffers attached to textures, but switching with the drawable surfaces provided by context and windowing systems is also expensive. Therefore, the frame buffer object FBO was introduced to solve this problem.

In the development of NDK OpenGLES, GLSurfaceView is generally used to display the drawing results on the screen. However, in practical application, many scenes do not need to be rendered on the screen. For example, GPU is used to complete some time-consuming operations such as image conversion and zooming in the background. At this time, using FBO can facilitate the implementation of similar requirements.

FBO enables rendering operations to be rendered to off-screen Buffer instead of on screen, and then glReadPixels or HardwareBuffer can be used to read the rendered image data, so as to realize image processing in the background with GPU.

How to use FBO

Steps to create and initialize an FBO:

// Create a 2D texture for the color attachment of the FBO
glGenTextures(1, &m_FboTextureId);
glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, GL_NONE);

/ / create FBO
glGenFramebuffers(1, &m_FboId);
/ / bind FBO
glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
// Bind the FBO texture
glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
// Attach the texture to the FBO attachment
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_FboTextureId, 0);
// Allocate memory size
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_RenderImage.width, m_RenderImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// Check the FBO integrity status
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)! = GL_FRAMEBUFFER_COMPLETE) { LOGCATE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status ! = GL_FRAMEBUFFER_COMPLETE");
	return false;
}
// Unbind textures
glBindTexture(GL_TEXTURE_2D, GL_NONE);
/ / unbundling FBO
glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);

Copy the code

General steps to use FBO:

/ / bind FBO
glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);

// Select Program for off-screen rendering, bind VAO to image texture, and render (off-screen rendering)
// m_ImageTextureId is another image texture used for texture mapping
glUseProgram(m_FboProgramObj);
glBindVertexArray(m_VaoIds[1]);
glActiveTexture(GL_TEXTURE0);
// Bind the image texture
glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
glUniform1i(m_FboSamplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);

/ / unbundling FBO
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// After the off-screen rendering is complete, the resulting graph data is stored in the texture m_FboTextureId that we connected to FBO earlier.
// We will do a normal render with the FBO texture m_FboTextureId to draw the results of the previous off-screen rendering onto the screen.
M_FboProgramObj is used for off-screen rendering and m_ProgramObj is used for normal rendering

// Select another shader program for normal rendering with m_FboTextureId texture as input
glUseProgram(m_ProgramObj);
glBindVertexArray(m_VaoIds[0]);
glActiveTexture(GL_TEXTURE0);
// Bind the FBO texture
glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glBindVertexArray(GL_NONE);
Copy the code

Example:

  1. Create and initialize the FBO
bool FBOSample::CreateFrameBufferObj()
{
	// Create and initialize the FBO texture
	glGenTextures(1, &m_FboTextureId);
	glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture(GL_TEXTURE_2D, GL_NONE);

	// Create and initialize the FBO
	glGenFramebuffers(1, &m_FboId);
	glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
	glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_FboTextureId, 0);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_RenderImage.width, m_RenderImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	if(glCheckFramebufferStatus(GL_FRAMEBUFFER)! = GL_FRAMEBUFFER_COMPLETE) { LOGCATE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status ! = GL_FRAMEBUFFER_COMPLETE");
		return false;
	}
	glBindTexture(GL_TEXTURE_2D, GL_NONE);
	glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
	return true;

}
Copy the code
  1. Compiler links 2 shader programs to create VAO, VBO and image textures
void FBOSample::Init()
{
	// Vertex coordinates
	GLfloat vVertices[] = {
			1.0 f.1.0 f.0.0 f.1.0 f.1.0 f.0.0 f.1.0 f.1.0 f.0.0 f.1.0 f.1.0 f.0.0 f};// Normal texture coordinates
	GLfloat vTexCoors[] = {
            0.0 f.1.0 f.1.0 f.1.0 f.0.0 f.0.0 f.1.0 f.0.0 f};// FBO texture coordinates are different from the normal texture direction, the origin is in the lower left corner
	GLfloat vFboTexCoors[] = {
			0.0 f.0.0 f.1.0 f.0.0 f.0.0 f.1.0 f.1.0 f.1.0 f}; GLushort indices[] = {0.1.2.1.3.2 };

	char vShaderStr[] =
			"#version 300 es \n"
			"layout(location = 0) in vec4 a_position; \n"
			"layout(location = 1) in vec2 a_texCoord; \n"
			"out vec2 v_texCoord; \n"
			"void main() \n"
			"{ \n"
			" gl_Position = a_position; \n"
			" v_texCoord = a_texCoord; \n"
			"} \n";

	// Fragment shader script for normal rendering, simple texture mapping
	char fShaderStr[] =
			"#version 300 es\n"
			"precision mediump float; \n"
			"in vec2 v_texCoord; \n"
			"layout(location = 0) out vec4 outColor; \n"
			"uniform sampler2D s_TextureMap; \n"
			"void main()\n"
			"{\n"
			" outColor = texture(s_TextureMap, v_texCoord); \n"
			"}";

	// A fragment shader script for off-screen rendering that takes the gray value of each pixel
	char fFboShaderStr[] =
			"#version 300 es\n"
			"precision mediump float; \n"
			"in vec2 v_texCoord; \n"
			"layout(location = 0) out vec4 outColor; \n"
			"uniform sampler2D s_TextureMap; \n"
			"void main()\n"
			"{\n"
			" vec4 tempColor = texture(s_TextureMap, v_texCoord); \n"
			"Float luminance = tempcolor.r * 0.299 + tempcolor.g * 0.587 + tempcolor.b * 0.114; \n"
			" outColor = vec4(vec3(luminance), tempColor.a); \n"
			"}"; // Output grayscale image

	// Build a shader program linked to normal rendering
	m_ProgramObj = GLUtils::CreateProgram(vShaderStr, fShaderStr, m_VertexShader, m_FragmentShader);

	// Build the shader program linked to the off-screen rendering
	m_FboProgramObj = GLUtils::CreateProgram(vShaderStr, fFboShaderStr, m_FboVertexShader, m_FboFragmentShader);

	if (m_ProgramObj == GL_NONE || m_FboProgramObj == GL_NONE)
	{
		LOGCATE("FBOSample::Init m_ProgramObj == GL_NONE");
		return;
	}
	m_SamplerLoc = glGetUniformLocation(m_ProgramObj, "s_TextureMap");
	m_FboSamplerLoc = glGetUniformLocation(m_FboProgramObj, "s_TextureMap");

	// Generate VBO, load vertex data and index data
	// Generate VBO Ids and load the VBOs with data
	glGenBuffers(4, m_VboIds);
	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices), vVertices, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vTexCoors), vTexCoors, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[2]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vFboTexCoors), vFboTexCoors, GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[3]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	GO_CHECK_GL_ERROR();

	// Generate two VAO, one for normal rendering and one for off-screen rendering
	// Generate VAO Ids
	glGenVertexArrays(2, m_VaoIds);
    // Initialize the VAO for normal rendering
	// Normal rendering VAO
	glBindVertexArray(m_VaoIds[0]);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
	glEnableVertexAttribArray(VERTEX_POS_INDX);
	glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (const void *)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
	glEnableVertexAttribArray(TEXTURE_POS_INDX);
	glVertexAttribPointer(TEXTURE_POS_INDX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (const void *)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[3]);
	GO_CHECK_GL_ERROR();
	glBindVertexArray(GL_NONE);


	// Initialize the VAO used for off-screen rendering
	// FBO off screen rendering VAO
	glBindVertexArray(m_VaoIds[1]);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
	glEnableVertexAttribArray(VERTEX_POS_INDX);
	glVertexAttribPointer(VERTEX_POS_INDX, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (const void *)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[2]);
	glEnableVertexAttribArray(TEXTURE_POS_INDX);
	glVertexAttribPointer(TEXTURE_POS_INDX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (const void *)0);
	glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[3]);
	GO_CHECK_GL_ERROR();
	glBindVertexArray(GL_NONE);

	// Create and initialize the image texture
	glGenTextures(1, &m_ImageTextureId);
	glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_RenderImage.width, m_RenderImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[0]);
	glBindTexture(GL_TEXTURE_2D, GL_NONE);
	GO_CHECK_GL_ERROR();

	if(! CreateFrameBufferObj()) { LOGCATE("FBOSample::Init CreateFrameBufferObj fail");
		return; }}Copy the code
  1. Off-screen rendering and normal rendering
void FBOSample::Draw(int screenW, int screenH)
{
	// Render off-screen
	glPixelStorei(GL_UNPACK_ALIGNMENT,1);
	glViewport(0.0, m_RenderImage.width, m_RenderImage.height);

	// Do FBO off screen rendering
	glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
	glUseProgram(m_FboProgramObj);
	glBindVertexArray(m_VaoIds[1]);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
	glUniform1i(m_FboSamplerLoc, 0);
	GO_CHECK_GL_ERROR();
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
	GO_CHECK_GL_ERROR();
	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	// Normal render
	// Do normal rendering
	glViewport(0.0, screenW, screenH);
	glUseProgram(m_ProgramObj);
	GO_CHECK_GL_ERROR();
	glBindVertexArray(m_VaoIds[0]);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
	glUniform1i(m_SamplerLoc, 0);
	GO_CHECK_GL_ERROR();
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
	GO_CHECK_GL_ERROR();
	glBindTexture(GL_TEXTURE_2D, GL_NONE);
	glBindVertexArray(GL_NONE);

}
Copy the code

Render result:

Implementation code path: github.com/githubhaoha…

Contact and exchange