OpenGL帧缓冲:可以清除它,但不能绘制它

时间:2011-09-06 18:07:05

标签: opengl framebuffer

在Mac上,我有一个OpenGL设置,除了帧缓冲器之外还可以正常工作 - 纹理工作等等。所以我知道纹理已经启用,我有一个有效的上下文等等。所有工作都完美无缺,直到我尝试创建一个帧缓冲区。

我使用glGenFramebuffers,glBindFramebuffer和glFramebufferTexture2D创建了一个帧缓冲区,glCheckFramebufferStatus返回GL_FRAMEBUFFER_COMPLETE。如果我然后调用glClear,然后调用glGetTexImage,返回的数据显示glClear就像应该的那样对绑定到帧缓冲区的纹理起作用。我可以将glClearColor设置为我想要的任何东西,glClear可以正确设置纹理数据。

但这就是好消息停止的地方。无论是使用VBO还是glBegin / glEnd,我都无法在帧缓冲区中绘制任何内容。绑定到帧缓冲区的纹理的纹理数据不受绘制调用的影响(尽管glClear结果仍然出现)。即使我在glGetTexImage调用之前调用glFlush和glFinish也是如此。此外,glGetError对我的任何调用都没有返回任何错误。

我在下面发布了一些示例代码,我在程序中的相关点添加了这些代码只是为了尝试解决此问题,以防任何人提出想法。 (这不包括glClear调用,但我从同一点的单独测试中知道它可行。)

    glGenFramebuffers(1, &fb);
glBindFramebuffer(GL_FRAMEBUFFER, fb);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbTexID, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 
if(status != GL_FRAMEBUFFER_COMPLETE)
    Debugger(); 

glEnable(GL_TEXTURE_2D); 
glCullFace(GL_NONE); 
glGenTextures(1,(GLuint*)&tex); 
glBindTexture(GL_TEXTURE_2D,tex); 
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,1024,1024,0,GL_RGBA,GL_UNSIGNED_BYTE,NULL); 
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 1024, 1024, 0, -5000, 5000); 
glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); 
glViewport(0, 0, 1024, 1024); 
glColor4f(1,1,1,1); 
glBegin(GL_TRIANGLES);
    glTexCoord2f(0, 0);
    glVertex2f(0, 0);
    glTexCoord2f(1, 0);
    glVertex2f(1024, 0);
    glTexCoord2f(0, 1);
    glVertex2f(0, 1024);
glEnd();
glFlush();
glFinish(); 
unsigned char *dd = new unsigned char[1024*1024*4]; 
glBindTexture(GL_TEXTURE_2D, fbTexID); //I've tried calling glBindFramebuffer(GL_FRAMEBUFFER,0) before this bind - makes no difference
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, dd);
delete dd; 

4 个答案:

答案 0 :(得分:9)

你的纹理不起作用,因为它被束缚了! 您不能将FBO上的绑定纹理作为rendertarget工作! 这是一个没有很好记录的东西,但是当你想到它时才有意义 驱动程序开发人员也需要一些安全措施,以防万一你做同样的事情同时读取和写入相同的纹理。

答案 1 :(得分:5)

好的,回答了我自己的问题。在生成帧缓冲区之后,似乎必须生成生成的纹理作为帧缓冲区绘制的表面。所以,这有效:

glGenFramebuffers...
glBindFramebuffer...
glGenTextures...
glBindTexture...
glTexParameterf etc.
glFramebufferTexture2D...

但这不是:

glGenTextures...
glBindTexture...
glGenFramebuffers
glBindFramebuffer...
glFramebufferTexture2D...

我没有在任何地方看到这个问题,这看起来很令人惊讶,但是我的代码从不工作到仅通过移动纹理的生成工作。

答案 2 :(得分:1)

这是我前一段时间写的FBO测试:

#include <GL/glew.h>
#include <GL/glut.h>

#include <cmath>
#include <iostream>

using namespace std;

namespace render
{
    int width, height;
    float aspect;

    void init();
    void reshape(int width, int height);
    void display();

    int const fbo_width = 512;
    int const fbo_height = 512;

    GLuint fb, color, depth;
};

void idle();

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );

    glutCreateWindow("FBO test");
    glutDisplayFunc(render::display);
    glutReshapeFunc(render::reshape);
    glutIdleFunc(idle);

    glewInit();

    render::init();
    glutMainLoop();

    return 0;
}

void idle()
{
    glutPostRedisplay();
}

void CHECK_FRAMEBUFFER_STATUS()
{                                                         
    GLenum status;
    status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); 
    switch(status) {
    case GL_FRAMEBUFFER_COMPLETE:
        break;

    case GL_FRAMEBUFFER_UNSUPPORTED:
    /* choose different formats */
        break;

    default:
        /* programming error; will fail on all hardware */
        throw "Framebuffer Error";
    }
}

namespace render
{
    float const light_dir[]={1,1,1,0};
    float const light_color[]={1,0.95,0.9,1};

    void init()
    {
        glGenFramebuffers(1, &fb);
        glGenTextures(1, &color);
        glGenRenderbuffers(1, &depth);

        glBindFramebuffer(GL_FRAMEBUFFER, fb);

        glBindTexture(GL_TEXTURE_2D, color);
        glTexImage2D(   GL_TEXTURE_2D, 
                0, 
                GL_RGBA, 
                fbo_width, fbo_height,
                0, 
                GL_RGBA, 
                GL_UNSIGNED_BYTE, 
                NULL);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);

        glBindRenderbuffer(GL_RENDERBUFFER, depth);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, fbo_width, fbo_height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);

        CHECK_FRAMEBUFFER_STATUS();
    }

    void reshape(int width, int height)
    {
        render::width=width;
        render::height=height;
        aspect=float(width)/float(height);
        glutPostRedisplay();
    }

    void prepare()
    {
        static float a=0, b=0, c=0;

        glBindTexture(GL_TEXTURE_2D, 0);
        glEnable(GL_TEXTURE_2D);
        glBindFramebuffer(GL_FRAMEBUFFER, fb);

        glViewport(0,0,fbo_width, fbo_height);

        glClearColor(1,1,1,0);
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(45, 1, 1, 10);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glEnable(GL_LIGHT0);
        glEnable(GL_LIGHTING);

        glEnable(GL_DEPTH_TEST);
        glDisable(GL_CULL_FACE);

        glLightfv(GL_LIGHT0, GL_POSITION, light_dir);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, light_color);

        glTranslatef(0,0,-5);

        glRotatef(a, 1, 0, 0);
        glRotatef(b, 0, 1, 0);
        glRotatef(c, 0, 0, 1);

        glutSolidTeapot(0.75);

        a=fmod(a+0.1, 360.);
        b=fmod(b+0.5, 360.);
        c=fmod(c+0.25, 360.);
    }

    void final()
    {
        static float a=0, b=0, c=0;

        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        glViewport(0,0, width, height);

        glClearColor(1.,1.,1.,0.);
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(45, aspect, 1, 10);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0,0,-5);

        glRotatef(b, 0, 1, 0);

        b=fmod(b+0.5, 360.);

        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, color);

        glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        glDisable(GL_LIGHTING);

        float cube[][5]=
        {
            {-1, -1, -1,  0,  0},
            { 1, -1, -1,  1,  0},
            { 1,  1, -1,  1,  1},
            {-1,  1, -1,  0,  1},

            {-1, -1,  1, -1,  0},
            { 1, -1,  1,  0,  0},
            { 1,  1,  1,  0,  1},
            {-1,  1,  1, -1,  1},
        };
        unsigned int faces[]=
        {
            0, 1, 2, 3,
            1, 5, 6, 2,
            5, 4, 7, 6,
            4, 0, 3, 7,
            3, 2, 6, 7,
            4, 5, 1, 0
        };

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        glVertexPointer(3, GL_FLOAT, 5*sizeof(float), &cube[0][0]);
        glTexCoordPointer(2, GL_FLOAT, 5*sizeof(float), &cube[0][3]);

        glCullFace(GL_BACK);
        glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, faces);

        glCullFace(GL_FRONT);
        glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, faces);

        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    }

    void display()
    {
        prepare();
        final();

        glutSwapBuffers();
    }
}

也许这个参考帮助

答案 3 :(得分:0)

这是一个FBO“基准”,我写了while agoset_fbo_size()具有Works For Me(TM)的创建序列。

/////////////////////////////////////////////////////////////////////////////
// INCLUDES /////////////////////////////////////////////////////////////////

#include <GL/glew.h>
#include <GL/glut.h>

#include <iostream>
#include <iomanip>
#include <sstream>

using namespace std;


/////////////////////////////////////////////////////////////////////////////
// CLASSES //////////////////////////////////////////////////////////////////

// http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
class ExpAvg 
{
public:
    ExpAvg(float initial, unsigned int time_periods) : avg(initial), alpha(2.0f / ( time_periods + 1 )) {}
    void Update(float nextval) { avg = alpha*nextval + (1.0f-alpha)*avg; }
    float Get() { return avg; }
private:
    float avg;
    float alpha;
};

class gl2D
{
public:
    gl2D() {
        int viewport[4];
        glGetIntegerv(GL_VIEWPORT, viewport);
        glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity();
        gluOrtho2D(0, viewport[2], 0, viewport[3]);
        glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity();
    }
    ~gl2D() {
        glMatrixMode(GL_PROJECTION); glPopMatrix();
        glMatrixMode(GL_MODELVIEW); glPopMatrix();
    }
};


/////////////////////////////////////////////////////////////////////////////
// GLOBALS //////////////////////////////////////////////////////////////////

int screen_width = 1024;
int screen_height = 768;
int mouse_x, mouse_y;
bool mouse_left, mouse_right;
float camera_angle_x = 45;
float camera_angle_y = 45;
float camera_distance = 0;

int texture_width, texture_height;
GLuint tex, fbo, rbo;  // object IDs


/////////////////////////////////////////////////////////////////////////////
// UTILITIES ////////////////////////////////////////////////////////////////

bool get_fbo_status()
{
    GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    switch(status) {
    case GL_FRAMEBUFFER_COMPLETE_EXT: 
        cout << "Framebuffer complete." << endl; return true;
    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
        cerr << "[ERROR] Attachment is NOT complete." << endl; return false;
    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
        cerr << "[ERROR] No image is attached to FBO." << endl; return false;
    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
        cerr << "[ERROR] Attached images have different dimensions." << endl; return false;
    case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
        cerr << "[ERROR] Color attached images have different internal formats." << endl; return false;
    case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
        cerr << "[ERROR] Draw buffer." << endl; return false;
    case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
        cerr << "[ERROR] Read buffer." << endl; return false;
    case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
        cerr << "[ERROR] Unsupported by FBO implementation." << endl; return false;
    default:
        cerr << "[ERROR] Unknow error." << endl; return false;
    }
}

void gl_print(const char *str, int x, int y, void *font)
{
    glPushAttrib(GL_ENABLE_BIT);
    glDisable(GL_LIGHTING);     // need to disable lighting for proper text color
    glDisable(GL_TEXTURE_2D);
    glRasterPos2i(x, y);        // place text position
    while(*str) glutBitmapCharacter(font, *str++); 
    glPopAttrib();
}

void textured_cube()
{
    glBegin(GL_QUADS);
    glColor4f(1, 1, 1, 1);

    // face v0-v1-v2-v3
    glNormal3f(0,0,1);
    glTexCoord2f(1, 1);  glVertex3f(1,1,1);
    glTexCoord2f(0, 1);  glVertex3f(-1,1,1);
    glTexCoord2f(0, 0);  glVertex3f(-1,-1,1);
    glTexCoord2f(1, 0);  glVertex3f(1,-1,1);

    // face v0-v3-v4-v5
    glNormal3f(1,0,0);
    glTexCoord2f(0, 1);  glVertex3f(1,1,1);
    glTexCoord2f(0, 0);  glVertex3f(1,-1,1);
    glTexCoord2f(1, 0);  glVertex3f(1,-1,-1);
    glTexCoord2f(1, 1);  glVertex3f(1,1,-1);

    // face v0-v5-v6-v1
    glNormal3f(0,1,0);
    glTexCoord2f(1, 0);  glVertex3f(1,1,1);
    glTexCoord2f(1, 1);  glVertex3f(1,1,-1);
    glTexCoord2f(0, 1);  glVertex3f(-1,1,-1);
    glTexCoord2f(0, 0);  glVertex3f(-1,1,1);

    // face  v1-v6-v7-v2
    glNormal3f(-1,0,0);
    glTexCoord2f(1, 1);  glVertex3f(-1,1,1);
    glTexCoord2f(0, 1);  glVertex3f(-1,1,-1);
    glTexCoord2f(0, 0);  glVertex3f(-1,-1,-1);
    glTexCoord2f(1, 0);  glVertex3f(-1,-1,1);

    // face v7-v4-v3-v2
    glNormal3f(0,-1,0);
    glTexCoord2f(0, 0);  glVertex3f(-1,-1,-1);
    glTexCoord2f(1, 0);  glVertex3f(1,-1,-1);
    glTexCoord2f(1, 1);  glVertex3f(1,-1,1);
    glTexCoord2f(0, 1);  glVertex3f(-1,-1,1);

    // face v4-v7-v6-v5
    glNormal3f(0,0,-1);
    glTexCoord2f(0, 0);  glVertex3f(1,-1,-1);
    glTexCoord2f(1, 0);  glVertex3f(-1,-1,-1);
    glTexCoord2f(1, 1);  glVertex3f(-1,1,-1);
    glTexCoord2f(0, 1);  glVertex3f(1,1,-1);
    glEnd();
}

bool set_fbo_size(int width, int height)
{
    int max_size;
    glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, &max_size);
    if(width > max_size) return false;
    if(height > max_size) return false;

    texture_width = width; texture_height = height;

    // create FBO
    if(fbo) glDeleteFramebuffersEXT(1, &fbo);
    glGenFramebuffersEXT(1, &fbo);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);    

    // create and attach a new texture as the FBO's color buffer
    if(tex) glDeleteTextures(1, &tex);
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    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_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);

    // create and attach a new depth buffer to currently bound FBO
    if(rbo) glDeleteRenderbuffersEXT(1, &rbo);
    glGenRenderbuffersEXT(1, &rbo);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rbo);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height);
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rbo);

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // unbind fbo
    if( !get_fbo_status() ) exit(1);
    return true;
}


/////////////////////////////////////////////////////////////////////////////
// GLUT CALLBACKS ///////////////////////////////////////////////////////////

void CB_Idle()
{
    glutPostRedisplay();
}

void CB_Reshape(int width, int height)
{
    screen_width = width;
    screen_height = height;
    glViewport(0, 0, (GLsizei)width, (GLsizei)height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0f, (float)(width)/height, 1.0f, 1000.0f); 
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void CB_Mouse(int button, int state, int x, int y)
{
    mouse_x = x; mouse_y = y;
    if(button == GLUT_LEFT_BUTTON)
        mouse_left = (state == GLUT_DOWN);
    else if(button == GLUT_RIGHT_BUTTON)
        mouse_right = (state == GLUT_DOWN);
}

void CB_Motion(int x, int y)
{
    if(mouse_left) {
        camera_angle_y += (x - mouse_x);
        camera_angle_x += (y - mouse_y);
        mouse_x = x; mouse_y = y;
    }
    if(mouse_right) {
        camera_distance += (y - mouse_y) * 0.2f;
        mouse_y = y;
    }
}

void CB_Keyboard(unsigned char key, int x, int y)
{
    static int drawMode = 0;
    static int tex_size = 0;
    bool ret = false;

    switch(key) {
    case 27: // ESCAPE
        exit(0);
        break;
    case ' ':
        while(!ret) {
            tex_size = (tex_size+1) % 7;
            switch(tex_size) {
                case 0: ret = set_fbo_size(128,128); break;
                case 1: ret = set_fbo_size(256,256); break;
                case 2: ret = set_fbo_size(512,512); break;
                case 3: ret = set_fbo_size(1024,1024); break;
                case 4: ret = set_fbo_size(2048,2048); break;
                case 5: ret = set_fbo_size(4096,4096); break;
                case 6: ret = set_fbo_size(8192,8192); break;
                default: ; break;
            }
        }
        break;
    default:
        break;
    }
    glutPostRedisplay();
}

void CB_Init() 
{
    GLenum err = glewInit();
    if(GLEW_OK != err) {
        cerr << "Error: " << glewGetErrorString(err) << endl; 
        exit(1);
    }

    if(!GLEW_EXT_framebuffer_object) {
        cerr << "Requires EXT_framebuffer_object" << endl; 
        exit(1);
    }

    tex = fbo = rbo = 0;

    glShadeModel(GL_SMOOTH);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_CULL_FACE);

    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
    glClearColor(0, 0, 0, 0);

    GLfloat lightKa[] = {.2f, .2f, .2f, 1.0f};  // ambient light
    GLfloat lightKd[] = {.7f, .7f, .7f, 1.0f};  // diffuse light
    GLfloat lightKs[] = {1, 1, 1, 1};           // specular light
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightKa);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightKd);
    glLightfv(GL_LIGHT0, GL_SPECULAR, lightKs);
    float lightPos[4] = {0, 0, 20, 1};          // positional light
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glEnable(GL_LIGHT0);

    set_fbo_size(128, 128);
}

void CB_Exit()
{
    glDeleteTextures(1, &tex);
    glDeleteFramebuffersEXT(1, &fbo);
    glDeleteRenderbuffersEXT(1, &rbo);
}

void CB_Display()
{
    static ExpAvg ft_fbo(0, 19);
    static ExpAvg ft_overall(0, 19);

    int before = glutGet(GLUT_ELAPSED_TIME);  

    // compute rotation angle
    const float ANGLE_SPEED = 90;   // degree/s
    float angle = ANGLE_SPEED * (glutGet(GLUT_ELAPSED_TIME) / 1000.0f);

    // render using fbo /////////////////////////////////////////////
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); // bind fbo

    // adjust viewport and projection matrix to texture dimension
    glViewport(0, 0, texture_width, texture_height);
    glMatrixMode(GL_PROJECTION); glLoadIdentity();
    gluPerspective(60.0f, (float)(texture_width)/texture_height, 1.0f, 100.0f);
    glMatrixMode(GL_MODELVIEW); glLoadIdentity();

    // clear buffer
    glClearColor(1, 1, 1, 0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glTranslatef(0,0,-3);
    glPushMatrix();
    glRotatef(angle*0.5f, 1, 0, 0);
    glRotatef(angle, 0, 1, 0);
    glRotatef(angle*0.7f, 0, 0, 1);

    // set up teapot colors
    float shininess = 15.0f;
    float diffuseColor[3] = {0.929524f, 0.796542f, 0.178823f};
    float specularColor[4] = {1.00000f, 0.980392f, 0.549020f, 1.0f};
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess); // range 0 ~ 128
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specularColor);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glColor3fv(diffuseColor);

    glBindTexture(GL_TEXTURE_2D, 0);
    glFrontFace(GL_CW);
    glutSolidTeapot(1.0); 
    glFrontFace(GL_CCW); 
    glPopMatrix();

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // unbind fbo
    glFinish();

    ft_fbo.Update( (float)(glutGet(GLUT_ELAPSED_TIME) - before) );

    // normal rendering ///////////////////////////////////

    // back to normal viewport and projection matrix
    glViewport(0, 0, screen_width, screen_height);
    glMatrixMode(GL_PROJECTION); glLoadIdentity();
    gluPerspective(60.0f, (float)(screen_width)/screen_height, 1.0f, 100.0f);
    glMatrixMode(GL_MODELVIEW); glLoadIdentity();

    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glTranslatef(0,0,-4);
    glPushMatrix();
    glTranslatef(0, 0, camera_distance);
    glRotatef(camera_angle_x, 1, 0, 0);
    glRotatef(camera_angle_y, 0, 1, 0);

    // draw a cube with the dynamic texture
    glBindTexture(GL_TEXTURE_2D, tex);
    textured_cube();     
    glPopMatrix();

    { 
        gl2D two_dee;   // set 2D mode
        glDisable(GL_DEPTH_TEST);
        stringstream ss;
        glColor3f(1,1,0);
        ss << fixed << setprecision(3);
        int pos = 1;

        ss.str(""); ss << "Texture size: " << texture_width << "x" << texture_height;
        gl_print(ss.str().c_str(), 10, screen_height - (pos++ * 20), GLUT_BITMAP_8_BY_13);
        ss.str(""); ss << "Overall frame time: " << ft_overall.Get() << " ms";
        gl_print(ss.str().c_str(), 10, screen_height - (pos++ * 20), GLUT_BITMAP_8_BY_13);
        ss.str(""); ss << "    FBO frame time: " << ft_fbo.Get() << " ms";
        gl_print(ss.str().c_str(), 10, screen_height - (pos++ * 20), GLUT_BITMAP_8_BY_13);
        ss.str(""); ss << "Press space to change texture size; mouse moves/zooms cube";
        gl_print(ss.str().c_str(), 10, 10, GLUT_BITMAP_8_BY_13);
        glEnable(GL_DEPTH_TEST);
    }

    glutSwapBuffers();
    ft_overall.Update( (float)(glutGet(GLUT_ELAPSED_TIME) - before) );
}


/////////////////////////////////////////////////////////////////////////////
// MAIN /////////////////////////////////////////////////////////////////////

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(screen_width, screen_height);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("FBO Test");

    glutDisplayFunc(CB_Display);
    glutIdleFunc(CB_Idle);
    glutReshapeFunc(CB_Reshape);
    glutKeyboardFunc(CB_Keyboard);
    glutMouseFunc(CB_Mouse);
    glutMotionFunc(CB_Motion);
    atexit(CB_Exit);

    CB_Init();
    glutMainLoop();
    return 0;
}