1. 程式人生 > >OpenGL緩衝區物件之FBO

OpenGL緩衝區物件之FBO

1. 概述

在OpenGL渲染管線中幾何資料和紋理經過變換和一些測試處理,最終會被展示到螢幕上。OpenGL渲染管線的最終位置是在幀緩衝區中。幀緩衝區是一系列二維的畫素儲存陣列,包括了顏色緩衝區、深度緩衝區、模板緩衝區以及累積緩衝區。預設情況下OpenGL使用的是視窗系統提供的幀緩衝區。

OpenGL的GL_ARB_framebuffer_object這個擴充套件提供了一種方式來建立額外的幀緩衝區物件(FBO)。使用幀緩衝區物件,OpenGL可以將原先繪製到視窗提供的幀緩衝區重定向到FBO之中。

和視窗提供的幀緩衝區類似,FBO提供了一系列的緩衝區,包括顏色緩衝區、深度緩衝區和模板緩衝區(需要注意的是FBO中並沒有提供累積緩衝區

)這些邏輯的緩衝區在FBO中被稱為 framebuffer-attachable images說明它們是可以繫結到FBO的二維畫素陣列。

FBO中有兩類繫結的物件:紋理影象(texture images)和渲染影象(renderbuffer images)。如果紋理物件繫結到FBO,那麼OpenGL就會執行渲染到紋理(render to texture)的操作,如果渲染物件繫結到FBO,那麼OpenGL會執行離屏渲染(offscreen rendering)

FBO可以理解為包含了許多掛接點的一個物件,它自身並不儲存影象相關的資料,他提供了一種可以快速切換外部紋理物件和渲染物件掛接點的方式,在FBO中必然包含一個深度緩衝區掛接點和一個模板緩衝區掛接點,同時還包含許多顏色緩衝區掛節點(具體多少個受OpenGL實現的影響,可以通過GL_MAX_COLOR_ATTACHMENTS使用glGet查詢),FBO的這些掛接點用來掛接紋理物件和渲染物件,這兩類物件中才真正儲存了需要被顯示的資料。FBO提供了一種快速有效的方法掛接或者解綁這些外部的物件,對於紋理物件使用 glFramebufferTexture2D

,對於渲染物件使用glFramebufferRenderbuffer
具體描述參考下圖:
FBO

2. 使用方法

2.1 建立FBO

建立FBO的方式類似於建立VBO,使用glGenFramebuffers

void glGenFramebuffers( 
    GLsizei n,
    GLuint *ids);

n:建立的幀緩衝區物件的數量
ids:儲存建立幀緩衝區物件ID的陣列或者變數
其中,ID為0有特殊的含義,表示視窗系統提供的幀緩衝區(預設)
FBO不在使用之後使用glDeleteFramebuffers刪除該FBO

建立FBO之後,在使用之前需要繫結它,使用glBindFramebuffers

void glBindFramebuffer(GLenum target, GLuint id)

target:繫結的目標,該引數必須設定為 GL_FRAMEBUFFER
id:由glGenFramebuffers建立的id

2.2 渲染物件

渲染物件是用來繫結的緩衝區物件FBO上做離屏渲染的。它可以讓場景直接渲染到這個物件中(而不是到視窗系統中顯示),建立它的方法與建立FBO有點類似:

  1. void glGenRenderbuffers(GLsizei n, GLuint* ids) 建立
  2. void glDeleteRenderbuffers(GLsizei n, const Gluint* ids) 刪除
  3. void glBindRenderbuffer(GLenum target, GLuint id) 繫結
    繫結完成之後,需要為渲染物件開闢一塊空間,使用下面函式完成:
void glRenderbufferStorage(GLenum target,
    GLenum internalformat,
    GLsizei width,
    GLsizei height);

target:指定目標,必須設定為GL_RENDERBUFFER
internalformat:設定影象格式(參考《OpenGL影象格式》)
width和height:設定渲染物件的長和寬(大小必須小於 GL_MAX_RENDERBUFFER_SIZE)

可以使用glGetRenderbufferparameteriv函式來獲取當前繫結的渲染物件

void glGetRenderbufferParameteriv(GLenum target,
                                  GLenum param,
                                  GLint* value)

target:引數必須是GL_RENDERBUFFER
param:取值如下(根據查詢的不同選擇相應的值)

GL_RENDERBUFFER_WIDTH
GL_RENDERBUFFER_HEIGHT
GL_RENDERBUFFER_INTERNAL_FORMAT
GL_RENDERBUFFER_RED_SIZE
GL_RENDERBUFFER_GREEN_SIZE
GL_RENDERBUFFER_BLUE_SIZE
GL_RENDERBUFFER_ALPHA_SIZE
GL_RENDERBUFFER_DEPTH_SIZE
GL_RENDERBUFFER_STENCIL_SIZE

2.3 掛接物件到FBO

FBO本身並不包含任何影象儲存空間,它需要掛接紋理物件或者渲染物件到FBO,掛接影象物件到FBO使用下面的方式:

2.3.1 掛接2D紋理到FBO

glFramebufferTexture2D(GLenum target,
                       GLenum attachmentPoint,
                       GLenum textureTarget,
                       GLuint textureId,
                       GLint  level)

target:掛接的目標,必須指定為 GL_FRAMEBUFFER
attachmentPoint:掛接點位,取值:GL_COLOR_ATTACHMENT0到GL_COLOR_ATTACHMENTn,GL_DEPTH_ATTACHMENT,GL_STENCIL_ATTACHMENT
對應著上圖中顏色緩衝區點位和深度以及模板緩衝區點位
textureTarget:設定為二維紋理(GL_TEXTURE_2D)
textureId:紋理物件的ID值(如果設定為0,那麼紋理物件從FBO點位上解綁)
level:mipmap的層級

2.3.2 掛接渲染物件到FBO

void glFramebufferRenderbuffer(GLenum target,
                               GLenum attachmentPoint,
                               GLenum renderbufferTarget,
                               GLuint renderbufferId)

第一個和第二個引數與掛接到紋理一樣,第三個引數必須設定為GL_RENDERBUFFER,第四個引數是渲染物件的ID值。(如果設定為0,那麼該渲染物件從當前點位上解綁)

2.4 檢查FBO狀態

當掛接完成之後,我們在執行FBO下面的操作之前,必須檢查一下FBO的狀態,使用以下的函式:

GLenum glCheckFramebufferStatus(GLenum target)

target:取值必須是GL_FRAMEBUFFER,當返回GL_FRAMEBUFFER_COMPLETE時,表示所有狀態都正常,否則返回錯誤資訊。

3. 示例程式

#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "freeglut.lib")

#include <stdio.h>
#include <gl/glew.h>
#include <gl/glut.h>


int windowWidth = 0;
int windowHeight = 0;

const int TexWidth = 512;
const int TexHeight = 512;

bool leftMouseDown = false;
float mouseX, mouseY;
float cameraAngleX, cameraAngleY;
float xRot, yRot;

GLuint textureID;
GLuint frameBufferID;
GLuint renderBufferID;

void drawCube()
{
    glBindTexture(GL_TEXTURE_2D, textureID);
    glColor4f(1, 1, 1, 1);

    glBegin(GL_QUADS);
    //Front
    glNormal3d(0, 0, 1);
    glVertex3d(-1,-1, 1);   glTexCoord2d(0,0);
    glVertex3d(1, -1, 1);   glTexCoord2d(1,0);
    glVertex3d(1, 1, 1);    glTexCoord2d(1,1);
    glVertex3d(-1, 1, 1);   glTexCoord2d(0,1);

    //Back
    glNormal3d(0, 0, -1);
    glVertex3d(1, -1 , -1); glTexCoord2d(0, 0);
    glVertex3d(-1, -1, -1); glTexCoord2d(1, 0);
    glVertex3d(-1, 1, -1);  glTexCoord2d(1, 1);
    glVertex3d(1, 1, -1);   glTexCoord2d(0, 1);

    //Left
    glNormal3d(-1, 0, 0);
    glVertex3d(-1, -1, -1); glTexCoord2d(0, 0);
    glVertex3d(-1, -1, 1);  glTexCoord2d(1, 0);
    glVertex3d(-1, 1, 1);   glTexCoord2d(1, 1);
    glVertex3d(-1, 1, -1);  glTexCoord2d(0, 1);

    //Right
    glNormal3d(1, 0, 0);
    glVertex3d(1, -1, 1);   glTexCoord2d(0, 0);
    glVertex3d(1, -1, -1);  glTexCoord2d(1, 0);
    glVertex3d(1, 1, -1);   glTexCoord2d(1, 1);
    glVertex3d(1, 1, 1);    glTexCoord2d(0, 1);

    //Top
    glNormal3d(0, 1, 0);
    glVertex3d(-1, 1, 1);   glTexCoord2d(0, 0);
    glVertex3d(1, 1, 1);    glTexCoord2d(1, 0);
    glVertex3d(1, 1, -1);   glTexCoord2d(1, 1);
    glVertex3d(-1, 1, -1);  glTexCoord2d(0, 1);

    //Bottom
    glNormal3d(0, -1, 0);
    glVertex3d(1, -1, 1);   glTexCoord2d(0, 0);
    glVertex3d(-1, -1, 1);  glTexCoord2d(1, 0);
    glVertex3d(-1, -1, -1); glTexCoord2d(1, 1);
    glVertex3d(1, -1, -1);  glTexCoord2d(0, 1);

    glEnd();
    glBindTexture(GL_TEXTURE_2D, 0);
}


void ChangeSize(int w, int h)
{
    windowWidth = w;
    windowHeight = h;

    if (h == 0)
        h = 1;
}

void SetupRC()
{
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    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_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TexWidth, TexHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
    glBindTexture(GL_TEXTURE_2D, 0);

    glGenRenderbuffers(1, &renderBufferID);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBufferID);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, TexWidth, TexHeight);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);

    glGenFramebuffers(1, &frameBufferID);
    glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBufferID);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);   

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE)
    {
        fprintf(stderr, "GLEW Error: %s\n", "FRAME BUFFER STATUS Error!");
        return;
    }

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
}

void RenderScene(void)
{
    //設定渲染到紋理的視口和投影矩陣
    glViewport(0, 0, TexWidth, TexHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0f, (float)(TexWidth) / TexHeight, 1.0f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    //渲染到紋理
    glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID);
    glClearColor(1, 1, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(1, 0, 1);
    glPushMatrix();
    glTranslated(0, 0.0, -5);
    glRotated(xRot, 1, 0, 0);
    glRotated(yRot, 0, 1, 0);
    glutSolidTeapot(1.0);
    glPopMatrix();

    //切換到視窗系統的幀緩衝區
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glViewport(0, 0, windowWidth, windowHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0f, (float)(windowWidth) / windowHeight, 1.0f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glTranslated(0, 0, -5);
    glRotated(cameraAngleY*0.5, 1, 0, 0);
    glRotated(cameraAngleX*0.5, 0, 1, 0);
    glColor3d(1.0, 1.0, 1.0);
    drawCube();
    glutSwapBuffers();
}


void MouseFuncCB(int button, int state, int x, int y)
{
    mouseX = x;
    mouseY = y;

    if (button == GLUT_LEFT_BUTTON)
    {
        if (state == GLUT_DOWN)
        {
            leftMouseDown = true;
        }
        else if (state == GLUT_UP)
        {
            leftMouseDown = false;
        }
    }

}


void MouseMotionFuncCB(int x, int y)
{
    if (leftMouseDown)
    {
        cameraAngleX += (x - mouseX);
        cameraAngleY += (y - mouseY);

        mouseX = x;
        mouseY = y;
    }

    glutPostRedisplay();
}


void TimerFuncCB(int value)
{
    xRot += 2;
    yRot += 3;
    glutPostRedisplay();
    glutTimerFunc(33, TimerFuncCB, 1);
}


int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutCreateWindow("OpenGL");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    glutMouseFunc(MouseFuncCB);
    glutMotionFunc(MouseMotionFuncCB);
    glutTimerFunc(33, TimerFuncCB, 1);

    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }

    SetupRC();

    glutMainLoop();
    return 0;
}

該程式演示了使用FBO作“渲染到紋理”的技術,將茶壺場景作為視窗場景的紋理,執行的效果圖如下所示:
執行結果