OpenGL中FrameBuffer使用
這邊先引用別人寫的比較好的文章,以便快速的瞭解關於framebuffer的一些函式。
《-------------------------------------------------------------------一下內容為引用-----------------------------------------------------------------------》
Frame Buffer Object(FBO)擴充套件,被推薦用於把資料渲染到紋理對像。相對於其它同類技術,如資料拷貝或交換緩衝區等,使用FBO技術會更高效並且更容易實現。 建立 和OpenGL中的其它對像一樣,如紋理對像(texture object), 畫素緩衝對像(pixel buffer objects) , 頂點緩衝對像(vertex buffer object)等,在使用一個FBO對像之前,你必須先要生成該對像,並取得一個有效的對像標識。 GLuint fbo;glGenFramebuffersEXT(1, &fbo); 要對一個FBO進行任何的操作,你必須先要對它進行繫結。這一步驟與我們平時使用VBO或者紋理的過程很像。繫結對像後,我們便可以對FBO進行各種操作了,以下程式碼演示如何進行繫結。 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); 第一個引數是“目標(target)”,指的是你要把FBO與哪個幀緩衝區進行繫結,目前來說,我個引數就只有一些預定義的選擇(GL_FRAMEBUFFER_EXT),但將來擴充套件的發展,可能會來現其它的選擇,讓你把FBO與其它的目標進行繫結。整型變數fbo,是用來儲存FBO對像標識的,這個標識我們已在前面生成了。要實現任何與FBO有關的操作,我們必須有一個FBO被繫結,否則呼叫就會出錯 |
加入一個深度快取(Depth Buffer) 一個FBO它本身其實沒有多大用處,要想讓它能被更有效的利用,我們需要把它與一些可被渲染的緩衝區繫結在一起,這樣的緩衝區可以是紋理,也可以是下面我們將要介紹的渲染緩衝區 一個渲染緩衝區,其實就是一個用來支援離屏渲染的緩衝區。通常是幀緩衝區的一部份,一般不具有紋理格式。常見的模版緩衝和深度緩衝就是這樣一類對像。 在這裡,我們要為我們的FBO指定一個渲染緩衝區。這樣,當我們渲染的時候,我們便把這個渲染緩衝區作為FBO的一個深度快取來使用。 和FBO的生成一樣,我們首先也要為渲染緩衝區指定一個有效的標識。 GLuint depthbuffer;glGenRenderbuffersEXT(1, &depthbuffer); 成功完成上面一步之後,我們就要對該緩衝區進行繫結,讓它成為當前渲染緩衝,下面是實現程式碼。 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer); 和FBO的繫結函式一樣,第一個引數是“目標(target)”,指的是你要與哪個目標進行繫結,目前來說,只能是一些預定義好的目標。變數dephtbuffer用來儲存對像標識。 這裡有一個關鍵的地方,也就是我們生成的渲染緩衝對像,它本身並不會自動分配記憶體空間。因此我們要呼叫OpenGL的函式來給它分配指定大小的記憶體空間,在這裡,我們分配一個固定大小的深度緩顯空間。 glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height); 上面這一函式成功執行之後,OpenGL將會為我們分配好一個大小為width x height的深度緩衝區。注意的是,這裡用了GL_DEPTH_COMPONENT,就是指我們的空間是用來儲存深度值的,但除了這個之外,渲染緩衝區 還可以用來儲存普通的RGB/RGBA格式的資料或者是模板緩衝的資訊。 準被好了深度快取的視訊記憶體空間後,接下來要做的工作就是把它與前面我們準備好了的FBO對像繫結在一起。 glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer); 這個函式看起來有點複雜,但其實它很好理解的。它要做的全部工作就是把把前面我們生成的深度快取對像與當前的FBO對像進行繫結,當然我們要注意一個FBO有多個不同繫結點,這裡是要繫結在FBO的深度緩衝繫結點上。 |
加入用於渲染的紋理 到現在為止,我們還沒有辦法往FBO中寫入顏色資訊。這也是我們接下來正要討論的,我們有以下兩種方法來實現它:
前者在某些地方會用到,後面的章節我們會深入討論。現在我們先來說說第二種方法。 在你想要把紋理與一個FBO進行繫結之前,我們得先要生成這個紋理。這個生成紋理的過程種我們平時見到的紋理生成沒什麼區別。 GLuint img;glGenTextures(1, &img);glBindTexture(GL_TEXTURE_2D, img);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 這個例項中,我們生成一個普通的RGBA影象,大小是width x height,與前面我們生成的渲染緩衝區的大小是一樣的,這一點很重要,也就是FBO中所有的繫結對像,都必須要有相同的寬度和高度。還有要注意的就是:這裡我們沒有上傳任何的資料,只是讓OpenGL保留分配好的空間,稍後我們將會用到。 生成好紋理之後,接下來的工作就是把這個紋理與FBO繫結在一起,以便我們可以把資料渲染到紋理空間中去。 glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0); 這裡再次看到這個看起來非常可怕的函式,當然它也並沒有我們想像中那麼難理解。引數GL_COLOR_ATTACHMENT0_EXT是告訴OpenGL把紋理對像繫結到FBO的0號繫結點(一個FBO在同一個時間內可以繫結多個顏色緩衝區,每個對應FBO的一個繫結點),引數GL_TEXTURE_2D是指定紋理的格式,img儲存的是紋理標識,指向一個之前就準備好了的紋理對像。紋理可以是多重對映的影象,最後一個引數指定級級為0,指的是使用原影象。 最後還有一步要做的工作,就是檢查一下FBO的準備工作是否全部完成,是否以經能被正確使用了。 這個測試工作由下面一個函式來完成,它會返回一個當前繫結的FBO是否正確的狀態資訊。 GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); 如果所有工作都已經做好,那麼返回的狀態值是GL_FRAMEBUFFER_COMPLETE_EXT,也就是說你的FBO已經準備好,並可以用來作為渲染對像了。否則就會返回其它一個錯誤碼,通過查詢定義文件,可以找到相關的錯誤資訊,從而了角錯誤大概是在哪一步驟中產生的。 |
渲染到紋理 所有困難的工作就是前面建立FBO環境的部份,剩下來的工作就相當簡單了,相關的事情就只是呼叫一下以下這個函式:glBindFramebufferEXT(). 當我們要把資料渲染並輸出到FBO的時候,我們只需要用這個函式來把一個FBO對像進行繫結。當我們要停止輸出到FBO,我們只要把引數設為0,再重新呼叫一次該函式就可以了。當然,停止向FBO輸出,這也是很重要的,當我們完成了FBO的工作,就得停止FBO,讓影象可以在螢幕上正確輸出。 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);glPushAttrib(GL_VIEWPORT_BIT);glViewport(0,0,width, height);// Render as normal here// output goes to the FBO and it's attached buffersglPopAttrib();glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); 上面另外三行程式碼glPushAttrib/glPopAttrib 及 glViewport,是用來確保在你跳出FBO渲染的時候可以返回原正常的渲染路徑。glViewport在這裡的呼叫是十分必要的,我們不要常試把資料渲染到一個大於或小於FBO大小的區域。 函式glPushAtrrib 和 glPopAttrib 是用來快速儲存視口資訊。這一步也是必要的,因為FBO會共享主上下文的所有資訊。任何的變動,都會同時影響到FBO及主上下文,當然也就會直接影響到你的正常螢幕渲染。 這裡一個重要資訊,你可能也注意到了,我們只是在繪製的時候繫結或解除FBO,但是我們沒有重新繫結紋理或渲染緩衝區,這裡因為在FBO中會一直儲存了這種繫結關係,除非你要把它們分開或FBO對像被銷燬了。 |
《------------------------------------------------------------------------以上內容為引用-----------------------------------------------------------------------》
看完上面的內容,你應該對FrameBuffer有一個比較完整的瞭解,其實我要做的是整理framebuffer程式為介面,以便被使用。
CFramebuffer.h
#pragma once
#include <gl/glew.h>
#include <glut.h>
#include <cv.h>
#include <highgui.h>
class CFrameBuffer
{
public:
CFrameBuffer();
public:
~CFrameBuffer();
private:
unsigned int m_FboID;
unsigned int m_RboID;
unsigned int m_tex;
bool m_bIsBegined;
int m_curbuff;
public:
int m_width;
int m_height;
public:
void init(int width, int height);
bool begin();
bool end();
void saveFrameBuff(const char* fileName);
unsigned int getTex(){return m_tex;}
};
CFramebuffer.cpp
#include "stdafx.h"
#include <iostream>
#include "CFrameBuffer.h"
CFrameBuffer::CFrameBuffer()
{
m_FboID = 0;
m_RboID = 0;
m_tex = 0;
m_bIsBegined = false;
m_width = 0;
m_height = 0;
m_curbuff = 0;
}
CFrameBuffer::~CFrameBuffer()
{
if(m_bIsBegined)
{
end();
m_bIsBegined = false;
}
glDeleteTextures(1,&m_tex);
glDeleteRenderbuffersEXT(1,&m_RboID);
glDeleteFramebuffersEXT(1,&m_FboID);
}
void CFrameBuffer::init(int width, int height)
{
glewInit();
m_width = width;
m_height = height;
glEnable(GL_TEXTURE_2D);
glGenTextures(1,&m_tex);
glBindTexture(GL_TEXTURE_2D,m_tex);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,m_width,m_height,0,GL_BGR,GL_UNSIGNED_BYTE,NULL);
glBindTexture(GL_TEXTURE_2D,0);
glDisable(GL_TEXTURE_2D);
glEnable(GL_RENDERBUFFER_EXT);
glGenRenderbuffersEXT(1,&m_RboID);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT,m_RboID);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,GL_DEPTH_COMPONENT,m_width,m_height);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT,0);
glDisable(GL_RENDERBUFFER_EXT);
glEnable(GL_FRAMEBUFFER_EXT);
glGenFramebuffersEXT(1,&m_FboID);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,m_FboID);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D,m_tex,0);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,GL_DEPTH_ATTACHMENT_EXT,GL_RENDERBUFFER_EXT,m_RboID);
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
switch(status)
{
case GL_FRAMEBUFFER_COMPLETE_EXT:
std::cout << "Framebuffer complete." << std::endl;
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
std::cout << "[ERROR] Framebuffer incomplete: Attachment is NOT complete." << std::endl;
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
std::cout << "[ERROR] Framebuffer incomplete: No image is attached to FBO." << std::endl;
break;
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
std::cout << "[ERROR] Framebuffer incomplete: Attached images have different dimensions." << std::endl;
break;
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
std::cout << "[ERROR] Framebuffer incomplete: Color attached images have different internal formats." << std::endl;
break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
std::cout << "[ERROR] Framebuffer incomplete: Draw buffer." << std::endl;
break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
std::cout << "[ERROR] Framebuffer incomplete: Read buffer." << std::endl;
break;
case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
std::cout << "[ERROR] Unsupported by FBO implementation." << std::endl;
break;
default:
std::cout << "[ERROR] Unknow error." << std::endl;
break;
}
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
glDisable(GL_FRAMEBUFFER_EXT);
m_curbuff = 0;
}
bool CFrameBuffer::begin()
{
if(m_bIsBegined)
{
return false;
}
else
{
//glPushAttrib(GL_ALL_ATTRIB_BITS);
//glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT,&m_curbuff);
//glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,m_FboID);
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT,&m_curbuff);
glPushAttrib(GL_VIEWPORT_BIT);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,m_FboID);
//cout<<"begin after : GL_FRAMEBUFFER_BINDING_EXT = "<<FboId<<endl;
glViewport(0,0,m_width,m_height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,m_width,0,m_height,-1000,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
m_bIsBegined = true;
return true;
}
}
bool CFrameBuffer::end()
{
if(m_bIsBegined)
{
//glPopAttrib();
//glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,m_curbuff);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,m_curbuff);
m_bIsBegined = false;
return true;
}
else
{
return false;
}
}
void CFrameBuffer::saveFrameBuff(const char* fileName)
{
IplImage* pImage = cvCreateImage(cvSize(m_width,m_height),8,3);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,m_tex);
glGetTexImage(GL_TEXTURE_2D,0,GL_BGR,GL_UNSIGNED_BYTE,pImage->imageData);
glDisable(GL_TEXTURE_2D);
cvFlip(pImage,NULL,0);
cvSaveImage(fileName,pImage);
cvReleaseImage(&pImage);
}
上面已經很詳細寫出的framebuffer的內容,你只要在draw函式之前呼叫begin()和draw函式之後用end()就可以完成將紋理繪製到framebuffer中了,這裡還使用一個函式來儲存framebuffer的紋理到圖片,接觸到OpenCV的一些函式。