1. 程式人生 > >Opengl入門系列- FBO的渲染到紋理的用法

Opengl入門系列- FBO的渲染到紋理的用法

前半部分引用的是這位大俠的部落格,目的說明渲染到紋理的過程。後面例子是我自己寫的。

幀緩衝區物件呢又稱為FBO,它允許我們把渲染從視窗的幀緩衝區轉移到我們所建立的一個或者多個離屏幀緩衝區。被推薦用於資料渲染到紋理物件,相對於其他同類技術,如資料拷貝或者交換緩衝區等等,使用FBO技術會更高效且易於實現。此buffer包含了color buffer,depth buffer,stencil buffer.渲染到紋理這個技術在遊戲中經常用來模擬電視機或者監視器等等的效果。

1.FBO並不受視窗大小的限制。
2.紋理可以連線到FBO,允許直接渲染到紋理,不需要顯示glCopyTexImage。
3.FBO可以包含許多顏色緩衝區,可以同時從一個片段著色器寫入

FBO為OpenGL core API的一部分,使用它之前要檢查GL_EXT_frmaebuffer_object擴充套件



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;
}
-----------