本文正在参与「金石计划」

FBO离屏烘托

到目前为止,咱们现已运用过了几个buffer:可写入色彩值的色彩缓冲区,能够写入和测验深度的深度缓冲区以及模板缓冲区。这些缓冲区被存放在GPU的某内存中,这块内存便是一个帧缓冲

之前咱们运用的都是屏幕供给的默许缓冲区,默许缓冲区制作后的色彩和深度等都会呈现在屏幕上

OpenGL还供给了一个更加灵敏的API,能够让开发者自定义帧缓冲区,然后写入到对应的纹路或者Render缓冲区目标中(RBO):这便是咱们要说的离屏烘托

顾名思义,离屏烘托便是不会制作在屏幕上的制作操作,而是在GPU后台进行制作,并写入到纹路或者RBO中

制作后的纹路或者RBO又能够用来制作到屏幕上,相当于做了一个中间层的转化

什么是RBO?

RBO(Render Buffer Object)即烘托缓冲区目标,是一个由应用程序分配的 2D 图画缓冲区。烘托缓冲区能够用于分配和存储深度或者模板值,能够用作 FBO 中的深度或者模板附着。

【安卓OpenGLES】 开发入门(九):离屏渲染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的转化:

RGBYUV:
Y = 0.299 * R + 0.587 * G + 0.114*B"
U = - 0.1687 * R - 0.3313 * G + 0.5* B + 128   注: +128 的意义是让UV的规模处于整数区间(0-255V = 0.5 * R - 0.4187 * G - 0.0813 * B + 128   注: +128 的意义是让UV的规模处于整数区间(0-255

效果图:

【安卓OpenGLES】 开发入门(九):离屏渲染FBO

能够看到正确显示出了灰度值,阐明咱们离屏烘托操作是成功了的。

总结

离屏烘托这块可能我们第一次学的话比较难看懂,只要了解:离屏烘托能够在GPU后台对纹路做一些预处理操作,记住是预处理,且这中间能够通过屡次离屏烘托,然后最终发送到进行屏幕制作的默许帧缓冲中

离屏烘托能够做的东西很多:只要是对图片进行处理的,比如图片缩放,裁剪,旋转,模糊,滤镜等等

以后工作中做的大部分是对图片的处理操作,也便是FBO。所以要入门图画处理,FBO也是一个必会技能。