OpenGL framebuffer: can clear it, but can't draw to it

跟風遠走 提交于 2019-11-30 10:03:36

Your texture did not work because it was bound! You cannnot have a bound texture work on a FBO as a rendertarget! This is something that isn't very documented, but makes sense when you think about it The driver developers need some safeguards too, just in case you do something weird like reading and writing to the same texture at the same time

OK, answered my own question. It seems that the texture you generate to be the surface that the frame buffer draws to must be generated after the frame buffer is generated. So, this works:

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

but this does not:

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

I don't see this addressed anywhere, and it seems surprising, but my code went from not working to working just by moving the generation of the texture.

This is a FBO test I wrote some time ago:

#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();
    }
}

Maybe this reference helps

Here's a FBO "benchmark" I wrote a while ago. set_fbo_size() has a creation sequence that 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;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!