1. 程式人生 > >OpenGL中FrameBuffer使用

OpenGL中FrameBuffer使用

這邊先引用別人寫的比較好的文章,以便快速的瞭解關於framebuffer的一些函式。

《-------------------------------------------------------------------一下內容為引用-----------------------------------------------------------------------》

Frame Buffer Object(FBO)擴充套件,被推薦用於把資料渲染到紋理對像。相對於其它同類技術,如資料拷貝或交換緩衝區等,使用FBO技術會更高效並且更容易實現。
在這篇文章中,我將會快速地講解一下如何來使用這一擴充套件,同時會介紹一些在使用過程中我們要注意的地方。學會該技術後,你便可以把一些渲染到紋理(render to texture)的功能加入到你的程式中,實現更快速的執行。

建立

和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它本身其實沒有多大用處,要想讓它能被更有效的利用,我們需要把它與一些可被渲染的緩衝區繫結在一起,這樣的緩衝區可以是紋理,也可以是下面我們將要介紹的渲染緩衝區

(renderbuffers)。

一個渲染緩衝區,其實就是一個用來支援離屏渲染的緩衝區。通常是幀緩衝區的一部份,一般不具有紋理格式。常見的模版緩衝和深度緩衝就是這樣一類對像。

在這裡,我們要為我們的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中寫入顏色資訊。這也是我們接下來正要討論的,我們有以下兩種方法來實現它:

  1. 把一個顏色渲染緩衝與FBO繫結。
  2. 把一個紋理與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的一些函式。