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 
具体描述参考下图: 

2. 使用方法

2.1 创建FBO

创建FBO的方式类似于创建VBO,使用glGenFramebuffers

void glGenFramebuffers( 
    GLsizei n,
    GLuint *ids);
  • 1
  • 2
  • 3

n:创建的帧缓冲区对象的数量 
ids:保存创建帧缓冲区对象ID的数组或者变量 
其中,ID为0有特殊的含义,表示窗口系统提供的帧缓冲区(默认) 
FBO不在使用之后使用glDeleteFramebuffers删除该FBO

创建FBO之后,在使用之前需要绑定它,使用glBindFramebuffers

void glBindFramebuffer(GLenum target, GLuint id)
  • 1

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);
  • 1
  • 2
  • 3
  • 4

target:指定目标,必须设置为GL_RENDERBUFFER 
internalformat:设置图像格式(参考《OpenGL图像格式》) 
width和height:设置渲染对象的长和宽(大小必须小于 GL_MAX_RENDERBUFFER_SIZE)

可以使用glGetRenderbufferparameteriv函数来获取当前绑定的渲染对象

void glGetRenderbufferParameteriv(GLenum target,
                                  GLenum param,
                                  GLint* value)
  • 1
  • 2
  • 3

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.3 挂接对象到FBO

FBO本身并不包含任何图像存储空间,它需要挂接纹理对象或者渲染对象到FBO,挂接图像对象到FBO使用下面的方式:

2.3.1 挂接2D纹理到FBO

glFramebufferTexture2D(GLenum target,
                       GLenum attachmentPoint,
                       GLenum textureTarget,
                       GLuint textureId,
                       GLint  level)
  • 1
  • 2
  • 3
  • 4
  • 5

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)
  • 1
  • 2
  • 3
  • 4

第一个和第二个参数与挂接到纹理一样,第三个参数必须设置为GL_RENDERBUFFER,第四个参数是渲染对象的ID值。(如果设置为0,那么该渲染对象从当前点位上解绑)

2.4 检查FBO状态

当挂接完成之后,我们在执行FBO下面的操作之前,必须检查一下FBO的状态,使用以下的函数:

GLenum glCheckFramebufferStatus(GLenum target)
  • 1

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229

该程序演示了使用FBO作“渲染到纹理”的技术,将茶壶场景作为窗口场景的纹理,运行的效果图如下所示: 

猜你喜欢

转载自blog.csdn.net/qq_21743659/article/details/107833152
今日推荐