本文正在参加「金石计划」
FBO离屏渲染
到目前为止,我们已经使用过了几个buffer:可写入颜色值的颜色缓冲区,可以写入和测试深度的深度缓冲区以及模板缓冲区。这些缓冲区被存放在GPU的某内存中,这块内存就是一个帧缓冲。
之前我们使用的都是屏幕提供的默认缓冲区,默认缓冲区绘制后的颜色和深度等都会呈现在屏幕上。
OpenGL还提供了一个更加灵活的API,可以让开发者自定义帧缓冲区,然后写入到对应的纹理或者Render缓冲区对象中(RBO):这就是我们要说的离屏渲染。
顾名思义,离屏渲染就是不会绘制在屏幕上的绘制操作,而是在GPU后台进行绘制,并写入到纹理或者RBO中。
绘制后的纹理或者RBO又可以用来绘制到屏幕上,相当于做了一个中间层的转换。
什么是RBO?
RBO(Render Buffer Object)即渲染缓冲区对象,是一个由应用程序分配的 2D 图像缓冲区。渲染缓冲区可以用于分配和存储深度或者模板值,可以用作 FBO 中的深度或者模板附着。
为什么要使用FBO?
首先,在默认情况下,OpenGL通过绘制到窗口的默认帧缓冲区,实现纹理的渲染,但是这种情况下只能适配纹理尺寸小余帧缓冲区的情况,如果纹理太大就没法正常绘制。
其次:我们知道GPU的图像处理能力是很强大的,在绘制的过程中,也不是所有的场景都需要绘制到屏幕上的,如图像的前期处理,缩放,滤镜等耗时操作都可以使用FBO来做预处理,这样可以大大提高GPU在后台的利用率。
FBO如何使用?
FBO的使用过程一般有以下几个:
-
1.创建并初始化FBO:
- 1.1 : 创建2D纹理用于fbo的颜色依附:glGenTextures->glBindTexture
- 1.2:创建FBO:glGenFramebuffers
- 1.3:绑定FBO:glBindFramebuffer(GL_FRAMEBUFFER,fbo_id);
- 1.4 : 绑定FBO纹理:glBindTexture->glFramebufferTexture2D->glTexImage2D
- 1.5 : 检测FBO的完整性:glCheckFramebufferStatus
- 1.6:解绑纹理:glBindTexture,解绑FBO:glBindFramebuffer
//1.1:创建2D纹理用于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); // 1.2:创建FBO:glGenFramebuffers glGenFramebuffers(1,&m_FboId); // 1.3:绑定FBO:glBindFramebuffer(GL_FRAMEBUFFER,fbo_id); glBindFramebuffer(GL_FRAMEBUFFER,m_FboId); // 1.4: 绑定FBO纹理:glBindTexture->glFramebufferTexture2D->glTexImage2D 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,imageWidth,imageHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr); // 1.5: 检测FBO的完整性:glCheckFramebufferStatus if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!= GL_FRAMEBUFFER_COMPLETE) { LOGCATE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE"); return ; } // 1.6:解绑纹理:glBindTexture,解绑FBO:glBindFramebuffer glBindTexture(GL_TEXTURE_2D,GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
-
2.使用FBO对象创建的纹理或者RBO对象进行渲染:
- 2.1:离屏渲染
- 2.1.1:绑定FBO并使用离屏渲染的着色器对象:glBindFramebuffer->glUseProgram(fboShaderObj);
- 2.1.2:绑定VAO并设置纹理对象:glBindVertexArray->glUniform1i
- 2.1.3:开始离屏渲染纹理:glDrawElements
- 2.1.4:解绑VAO:glBindVertexArray->glBindFramebuffer
- 2.1.5:解绑FBO:glBindFramebuffer(GL_FRAMEBUFFER, 0);
//2.1:离屏渲染 { glPixelStorei(GL_UNPACK_ALIGNMENT,1); glViewport(0,0,imageWidth,imageHeight); //2.1.1:绑定FBO并使用离屏渲染的着色器对象:glBindFramebuffer,glUseProgram(fboShaderObj); glBindFramebuffer(GL_FRAMEBUFFER,m_FboId); glUseProgram(fbo_ProgramObj); //2.1.2:绑定VAO并设置纹理对象:glBindVertexArray->glUniform1i glBindVertexArray(m_fboVAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,textureID); glUniform1i(glGetUniformLocation(fbo_ProgramObj,"s_TextureMap"),0); //2.1.3:开始离屏渲染纹理:glDrawElements glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (const void *)0); //2.1.4:解绑VAO:glBindVertexArray->glBindFramebuffer glBindTexture(GL_TEXTURE_2D,GL_NONE); glBindVertexArray(GL_NONE); //2.1.5:解绑FBO:glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER,0); }
离屏渲染会得到一个新的纹理对象,可以提供给普通渲染使用。
注意:在离屏渲染的时候我们将渲染的视口(glViewport)设置为了原纹理贴图的宽高,然后将离屏渲染顶点数据设置为1为单位,这样就可以渲染出一个和原纹理贴图一样尺寸的纹理对象。
- 2.2:普通渲染
- 2.2.1:使用普通着色器对象:glUseProgram(commonShaderObj);
- 2.2.2:绑定VAO并设置纹理。使用2.1中离屏渲染的FboTextureId纹理id
- 2.2.3:开始绘制:glDrawElements
- 2.2.4:解绑VAO
//2.2:普通渲染 { glViewport(0, 0, mScreenWidth, mScreenHeight); //2.2.1:使用普通着色器对象:glUseProgram(commonShaderObj); glUseProgram(programObj); //2.2.2:绑定VAO并设置纹理。使用2.1中离屏渲染的FboTextureId纹理id glBindVertexArray(VAO); glUniform1i(glGetUniformLocation(programObj, "s_TextureMap"), 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_FboTextureId); //2.2.3:开始绘制:glDrawElements glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0); // 2.2.4:解绑VAO glBindTexture(GL_TEXTURE_2D,GL_NONE); glBindVertexArray(GL_NONE); }
-
顶点数据:
//0.普通渲染顶点数据
GLfloat vertices[] = {
//顶点 //纹理坐标
-0.5f, -0.5f, 0.0f,0.0f, 0.0f,//V0
0.5f,-0.5f, 0.0f,1.0f,0.0f,//V1
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,//V2
-0.5f, 0.5f, 0.0f,0.0f, 1.0f//V3
};
//0.离屏渲染顶点数据:设置以1位单位距离
GLfloat fbo_vertices[] = {
//顶点 //纹理坐标
-1.0f, -1.0f, 0.0f,0.0f, 0.0f,//V0
1.0f,-1.0f, 0.0f,1.0f,0.0f,//V1
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,//V2
-1.0f, 1.0f, 0.0f,0.0f, 1.0f//V3
};
//确定三角形索引
GLint indices[] = {
0,1,2,
0,2,3
};
这里小余对离屏渲染使用不同的顶点数据,配合前面说的视口变化操作,得到一个新的和原纹理贴图尺寸一样的纹理对象。
- 着色器程序:
//1.创建着色器程序,此处将着色器程序创建封装到一个工具类中
char vShaderStr[] =
"#version 300 es \n"
"layout(location = 0) in vec4 vPosition; \n"
"layout(location = 1) in vec2 texCords; \n"
"out vec2 v_texCoord; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
" v_texCoord = texCords; \n"
"} \n";
char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"in vec2 v_texCoord; \n"
"out vec4 fragColor; \n"
"uniform sampler2D s_TextureMap; \n"
"void main() \n"
"{ \n"
" fragColor = texture(s_TextureMap,v_texCoord); \n"
"} \n";
// 用于离屏渲染的片段着色器脚本,取每个像素的灰度值
char fbo_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"
" vec4 tempColor = texture(s_TextureMap, v_texCoord);\n"
" // RGB转YUV:\n"
" // Y = 0.299 * R + 0.587 * G + 0.114*B\n"
" // U = - 0.1687 * R - 0.3313 * G + 0.5* B + 128 注: +128 的含义是让UV的范围处于整数区间(0-255)\n"
" // V = 0.5 * R - 0.4187 * G - 0.0813 * B + 128 注: +128 的含义是让UV的范围处于整数区间(0-255)\n"
" // 下面的luminance 只取了YUV的Y,所以是黑白的照片"
" float luminance = tempColor.r * 0.299 + tempColor.g * 0.587 + tempColor.b * 0.114;\n"
" outColor = vec4(vec3(luminance), tempColor.a);\n"
"}"; // 输出灰度图
这里要显示灰度图,涉及到RGB和YUV的转换:
RGB转YUV:
Y = 0.299 * R + 0.587 * G + 0.114*B"
U = - 0.1687 * R - 0.3313 * G + 0.5* B + 128 注: +128 的含义是让UV的范围处于整数区间(0-255)
V = 0.5 * R - 0.4187 * G - 0.0813 * B + 128 注: +128 的含义是让UV的范围处于整数区间(0-255)
效果图:
可以看到正确显示出了灰度值,说明我们离屏渲染操作是成功了的。
总结
离屏渲染这块可能大家第一次学的话比较难看懂,只要理解:离屏渲染可以在GPU后台对纹理做一些预处理操作,记住是预处理,且这中间可以经过多次离屏渲染,然后最终发送到进行屏幕绘制的默认帧缓冲中。
离屏渲染可以做的东西很多:只要是对图片进行处理的,比如图片缩放,裁剪,旋转,模糊,滤镜等等:
以后工作中做的大部分是对图片的处理操作,也就是FBO。所以要入门图像处理,FBO也是一个必会技能。