OpenGL緩衝區物件之FBO
1. 概述
在OpenGL渲染管線中幾何資料和紋理經過變換和一些測試處理,最終會被展示到螢幕上。OpenGL渲染管線的最終位置是在幀緩衝區中。幀緩衝區是一系列二維的畫素儲存陣列,包括了顏色緩衝區、深度緩衝區、模板緩衝區以及累積緩衝區。預設情況下OpenGL使用的是視窗系統提供的幀緩衝區。
OpenGL的GL_ARB_framebuffer_object這個擴充套件提供了一種方式來建立額外的幀緩衝區物件(FBO)。使用幀緩衝區物件,OpenGL可以將原先繪製到視窗提供的幀緩衝區重定向到FBO之中。
和視窗提供的幀緩衝區類似,FBO提供了一系列的緩衝區,包括顏色緩衝區、深度緩衝區和模板緩衝區(需要注意的是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
具體描述參考下圖:
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有點類似:
- void glGenRenderbuffers(GLsizei n, GLuint* ids) 建立
- void glDeleteRenderbuffers(GLsizei n, const Gluint* ids) 刪除
- 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作“渲染到紋理”的技術,將茶壺場景作為視窗場景的紋理,執行的效果圖如下所示: