Opengl入门系列- FBO的渲染到纹理的用法

时间:2022-06-07 19:11:39

前半部分引用的是这位大侠的博客,目的说明渲染到纹理的过程。后面例子是我自己写的。

原文地址:http://www.cppblog.com/init/archive/2012/02/16/165778.aspx

帧缓冲区对象呢又称为FBO,它允许我们把渲染从窗口的帧缓冲区转移到我们所创建的一个或者多个离屏帧缓冲区。被推荐用于数据渲染到纹理对象,相对于其他同类技术,如数据拷贝或者交换缓冲区等等,使用FBO技术会更高效且易于实现。此buffer包含了color buffer,depth buffer,stencil buffer.渲染到纹理这个技术在游戏中经常用来模拟电视机或者监视器等等的效果。

1.FBO并不受窗口大小的限制。
2.纹理可以连接到FBO,允许直接渲染到纹理,不需要显示glCopyTexImage。
3.FBO可以包含许多颜色缓冲区,可以同时从一个片段着色器写入

FBO为OpenGL core API的一部分,使用它之前要检查GL_EXT_frmaebuffer_object扩展


Opengl入门系列- FBO的渲染到纹理的用法



FBO是一个图像容器,空的FBO容器里面存储的是texture(纹理)和renderbuffer(渲染缓冲区),纹理和渲染缓冲区都可以作为渲染的目标。一般使用的步骤:
设定好OpenGL基本环境 ->  建立FBO ->  启动FBO ->  对FBO绘图 ->  将FBO当成贴图  -> 启动原来的Frame Buffer -> 对Frame Buffer 画图 ->解除贴图的连接 -> 删除FBO

创建FBO:

      生成一个对象,并取得一个有效的对象标识
        GLuint fboname;
        glGenFrameBuffersEXT(1,&fboname);

对FBO进行任何操作都需要首先绑定它:

      把FBO与目标绑定,整型变量fboname用来保存FBO对象标识
      glBindFrameBufferEXT(GL_FRAMEBUFFER_EXT,fboname);
      要想关闭FBO,只要是fboname给0就可以:glBindFrameBufferEXT(GL_FRAMEBUFFER_EXT,0);即:除了创建新的FBO外,glBindFrameBufferEXT()也用于在FBO之间进行切换,绑定到名称0将会解除当前绑定的FBO,并把渲染重新定向到窗口的帧缓冲区。

加入一个深度缓存
        一个FBO本身并没有多大用处,要想让他能被更有效的使用,我们需要把它与一些可被渲染的缓冲区绑定在一起,这样的缓冲区可以是纹理texture,也可以是渲染缓冲区renderbuffer,其实它就是一个用来支持离屏渲染的缓冲区,通常是帧缓冲区的一部分,一般不具有纹理格式,常见的模板缓冲和深度缓冲就是这样一类对象,我们要为FBO指定一个dephtbuffer:
        GLuint dbname;
       glGenRenderBuffersEXT(1,&dbname);

绑定该缓冲区,让它成为当前渲染缓冲:
        glBindRenderBufferEXT(GL_RENDERBUFFER_EXT,dbname);

生成一个renderbuffer后,它本身并不会自动的分配内存空间,我们需要调用API来分配指定的内存空间:
         glRenderBufferStorageEXT(GL_RENDERBUFFER_EXT,GL_DEPTH_COMPONENT,width,height);

这样就分配了一个w*h的深度缓冲区,这里使用了GL_DEPTH_COMPONENT,是指我们的空间用来保存深度值,除此之外还可以用来保存普通的GL_RGB/GL_RFBA格式的数据或者模板缓冲的信息。

接下来把这个深度缓存与准备好的FBO对象绑定在一起:
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,GL_DEPTH_ATTACHMENT_EXT,GL_RENDERBUFFER_EXT,dbname);

         一个FBO可以有多个不同的绑定点,这里是绑定在FBO的深度缓冲绑定点上,如GL_COLOR_ATTACHMENTi_EXT,GL_DEPTH_ATTACHMENT_EXT等等

加入用于渲染的纹理:
         到现在为止,还没有办法往FBO写入颜色信息,有两种方法实现:
       把一个颜色渲染缓冲与FBO绑定或者把一个纹理与FBO绑定,要想把纹理与FBO绑定,我们首先要生成这个纹理:
        GLuint img;
        glGenTexture(1,&img);
        glBindTexture(GL_TEXTURE_2D,img);
        glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,wight,height,0,GL_RGBA,GL_UNSIGEND_BYTE,NULL);
          生成一个普通的RGBA图像,大小是w*h, 与前面生成的渲染缓冲区的大小是一样,FBO中要求所绑定的对象有相同的高度与宽度,这时候没有数据
        生成纹理之后,把这个纹理与FBO绑定在一起,以便把数据渲染到纹理空间中去
          glFramebufferTexture2Dext(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENTO_EXT,GL_TEXTURE_2D,img,0);
         参数GL_COLOR_ATTACHMENTO_EXT是告诉opengl把纹理对象绑定到FBO的0号绑定点,GL_TEXTURE_2D是纹理的格式,img是保存的纹理标识,指向之前就准备好的纹理对象,纹理可以使多重映射的图像,最后一个参数指定级为0,标识使用原图像
        接下来测试FBO准备工作是否完,返回一个当前绑定FBO是否正确的状态信息,返回 GL_FRAMEBUFFER_COMPLETE_EXT  就是指FBO准备好了 :
        GLenum status=glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
 渲染到纹理:
        当我们要把数据渲染并输出到FBO时,我们就调用glBindFrameBufferEXT();当我们要停止输出FBO,把参数设置成0即可,当然,停止FBO输出很重要,我们完成FBO的工作就要停止FBO,让图像可以再屏幕上正确输出,
glBindFrameBufferEXT(GL_FRAMEBUFFER_EXT,fbname);
glPushAttrib(GL_VIEWPORT_BIT);
glViewPort(0,0,,wight,height);
//render as normal here
//output goes to the FBO and it's attached buffers
glPopAttrib();
glBindFrameBufferEXT(GL_FRAMEBUFFER_EXT,0);
面另外三行代码glPushAttrib/glPopAttrib 及 glViewport,是用来确保在你跳出FBO渲染的时候可以返回原正常的渲染路径。glViewport在这里的调用是十分必要的,我们不要常试把数据渲染到一个大于或小于FBO大小的区域。 函数glPushAtrrib 和 glPopAttrib 是用来快速保存视口信息。这一步也是必要的,因为FBO会共享主上下文的所有信息。任何的变动,都会同时影响到FBO及主上下文,当然也就会直接影响到你的正常屏幕渲染。

这里一个重要信息,你可能也注意到了,我们只是在绘制的时候绑定或解除FBO,但是我们没有重新绑定纹理或渲染缓冲区,这里因为在FBO中会一直保存了这种绑定关系,除非你要把它们分开或FBO对像被销毁了。

使用已渲染出来的纹理:

来到这里,我们已经把屏幕的数据渲染到了一个图像纹理上。现在我们来看一看如何来使用这张已经渲染好了的图像纹理。这个操作的本身其实是很简单的,我们只要把这张图像纹理当作普通纹理一样,绑定为当前纹理就可以了。

glBindTexture(GL_TEXTURE_2D, img);

以上这一函数调用完成之后,这张图像纹理就成了一个在绘图的时候用于被读取的普通纹理。

根据你在初始化时所指定的不同纹理滤波方式,你也许会希望为该纹理生成多重映像(mipmap)信息。如果要建立多重映像信息,多数的人都是在上传纹理数据的时候,通过调用函数gluBuild2DMipmaps()来实现,当然有些朋友可能会知道如何使用自动生成多重映像的扩展,但是在FBO扩展中,我们增加了第三种生成映像的方法,也就是使用GenerateMipmapEXT()函数。

这个函数的作用就是让OpenGL帮你自动创建多重映像信息。中间实现的过程,根据不同的显卡会有所不同,我们只关心它们最终的结果是一样就行了。值得注意的是:对于这种通过FBO渲染出来的纹理,要实现多重映像的话,只有这一种方法是正确的,这里你不可以使用自动生成函数来生成多重映像,这其中的原因有很多,如果你想深入了解的话,可以查看一下技术文档。

使用这一函数使方便,你所要做的就是先把该纹理对像绑定为当前纹理,然后调用一次该函数就可以了。

glGenerateMipmapEXT(GL_TEXTURE_2D);

OpenGL将会自动为我们生成所需要的全部信息,到现在我们的纹理便可以正常使用了。

一个重点要注意的地方:如果你打算使用多重映像(如 GL_LINEAR_MIPMAP_LINEAR),该函数glGenerateMipmapEXT()必须要在执行渲染到纹理之前调用。

在创建纹理的时候,我们可以按以下代码来做。

glGenTextures(1, &img);
glBindTexture(GL_TEXTURE_2D, img);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
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_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmapEXT(GL_TEXTURE_2D);

到现在,这张纹理和普通纹理没什么区别,我们就按处理普通纹理的方法来使用就可以了。


删除FBO:
glDeleteFrameBufferEXT(1,&fboname);

同样的,你如果分配了渲染缓冲对像,也别忘了要把它清理掉。本实例中我们分配的是深度缓存渲染对像,我们用以下函数来清除它:

glDeleteRenderbuffersEXT(1, &depthbuffer);

FBO的完整性

在向FBO输出渲染结果之前,需要测试FBO的完整性。如果FBO不完整,任何渲染操作都会失败。我们可以使用glCheckFramebufferStatusEXT()函数来测试FBO的完整性(此函数不能在glBegin()和glEnd()函数之间调用)。FBO完整性的判别法则如下:

  • 与FBO挂接的二维数组对象的长度和宽度必须不能为。
  • 如果一个二维数组对象被挂接到FBO的颜色缓冲区挂接点时,二维数组必须具有内部颜色格式(GL_RGBA, GL_DEPTH_COMPONENT, GL_LUMINANCE等)。
  • 如果一个二维数组对象被挂接到FBO的深度缓冲区挂接点时,二维数组必须具有内部深度格式(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT24_EXT等)。
  • 如果一个二维数组对象被挂接到FBO的模板缓冲区挂接点时,二维数组必须具有内部模板格式(GL_STENCIL_INDEX, GL_STENCIL_INDEX8_EXT等)。
  • FBO至少挂接有一个二维数组缓冲区对象。
  • 同一个FBO上挂接的二维数组对象必须拥有相同的长度和宽度。
  • 所有的颜色缓冲区挂接点上挂接的二维数组对象必须具有相同的内部格式。

-----------------------------------------

#include <iostream>
using namespace std;

#include "GL/glew.h"
#include "GL/gl.h"
#include "GL/glu.h"
#include "GL/glut.h"
/****************************************************************************************************
* 全局变量定义
*****************************************************************************************************/
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
const int TEXTURE_WIDTH = 512;
const int TEXTURE_HEIGHT = 512;
const double NEAR_PLANE = 1.0f;
const double FAR_PLANE = 1000.0f;

GLuint fbo = 0;        // FBO对象的句柄
GLuint depthbuffer = 0;
GLuint rendertarget = 0;        // 纹理对象的句柄


/****************************************************************************************************
* 全局函数定义
*****************************************************************************************************/
void SetupWindow(void)
{
    int argc = 0; char* argv[] = {0};
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH|GLUT_SINGLE|GLUT_RGBA);
    //glutInitWindowPosition(100, 100);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Framebuffer Sample");

    GLenum err = glewInit(); // GLEW的初始化必须在OpenGL上下文被创建之后调用
}

// 初始化摄像机
void SetupCamera(void)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, (double)SCREEN_WIDTH/(double)SCREEN_HEIGHT, NEAR_PLANE, FAR_PLANE);
    gluLookAt(5, 5, 5, 0, 0, 0, 0, 1, 0);

    // 各种变换应该在GL_MODELVIEW模式下进行
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Z-buffer
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    // 启用2D贴图
    glEnable(GL_TEXTURE_2D);
}

// 初始化几何形体
void SetupResource(void)
{
    // 创建纹理
    glGenTextures(1, &rendertarget);
    glBindTexture(GL_TEXTURE_2D, rendertarget);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glBindTexture(GL_TEXTURE_2D, 0);

    /*
    // 创建深度缓冲区
    glGenRenderbuffersEXT(1, &depthbuffer);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
    */

    // 创建FBO对象
    glGenFramebuffersEXT(1, &fbo);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, rendertarget, 0);
    //glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

    GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
    {

    }
}

// 渲染到窗体
void Render(void)
{
    // 绑定默认FBO(窗体帧缓冲区的ID是0)
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    glBindTexture(GL_TEXTURE_2D, rendertarget);
    glViewport(0,0,SCREEN_WIDTH, SCREEN_HEIGHT);


    // 渲染
    glClearColor( 0, 0, 1, 0 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glBegin(GL_POLYGON);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glColor3f(1, 1, 1);

    glTexCoord2f(1, 1);
    glVertex3d( 1,  1, 0);

    glTexCoord2f(0, 1);
    glVertex3d(-1,  1, 0);

    glTexCoord2f(0, 0);
    glVertex3d(-1, -1, 0);

    glTexCoord2f(1, 0);
    glVertex3d( 1, -1, 0);

    glEnd();

    glutSwapBuffers();
}


// 渲染到纹理
void RenderToTarget(void)
{
    glBindTexture(GL_TEXTURE_2D, 0); // 取消绑定,因为如果不取消,渲染到纹理的时候会使用纹理本身

    // 绑定渲染目标
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
    glViewport(0,0,TEXTURE_WIDTH, TEXTURE_HEIGHT);

    // 渲染
    glClearColor( 1, 1, 0, 1 );
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glBegin(GL_POLYGON);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glColor4f(1, 0, 0, 1);
    glVertex3d( 0,  1, 0);
    glVertex3d(-1, -1, 0);
    glVertex3d( 1, -1, 0);

    glEnd();

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

void Clear(void)
{

}

void renderScene()
{
   RenderToTarget();
    Render();
    //Render1();
}

/****************************************************************************************************
* 主程序入口
*****************************************************************************************************/
int main(int argc, char* argv[])
{
    SetupWindow();
    SetupCamera();
    SetupResource();

    glutDisplayFunc(renderScene);
    glutMainLoop();
    return 0;
}
-----------

Opengl入门系列- FBO的渲染到纹理的用法