关于OpenGL Framebuffer Object、glReadPixels与离屏渲染

时间:2021-09-12 18:35:57

最近写论文需要用到离屏渲染(主要是因为模型太大普通窗口绘制根本做不了),于是翻阅了红宝书查了下相关api和用法。中文版的红宝书可读性有点差,很多地方翻译地晦涩,但好歹读起来比较快,主要相关章节为第8章和第10章(可以连带把第9章读完以后写GLSL会顺利成章)。貌似superbible可读性更强,但红宝书讲得也差不多了就没再继续看。

由于红宝书过于学术,想动手还是最好查查网上的资料,于是把一些还可以的资料列一下。

关于FBO:

OpenGL中的FBO对象(含源码)

OpenGL的帧缓冲对象和浮点纹理

GPGPU计算观念和基本思路总结

OpenGL.FrameBuffer Object

frame buffer object (fbo)整理

关于glReadPixels:

OpenGL中位图的操作(glReadPixels,glDrawPixels等)

关于在FBO中使用多重采样:

【OpenGL】FBO中多重采样抗锯齿(MSAA:MultiSampling Anti-Aliasing)

多重采样(MultiSample)下的FBO反锯齿

 

总结上面的参考资料,并主要参照红宝书的代码,离屏渲染的代码如下(经测试确实可用):

void *GlWidget::offScreenRender(string file_path) {
    int render_width = 2*window_width_, render_height = 2*window_height_;

    enum { Color, Depth, NumRenderbuffers };
    // multi-sampled frame buffer object as the draw target
    GLuint framebuffer_ms, renderbuffer_ms[NumRenderbuffers];

    // generate color and depth render buffers and allocate storage for the multi-sampled FBO
    glGenRenderbuffers(NumRenderbuffers, renderbuffer_ms);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer_ms[Color]);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 16,
        GL_RGBA8, render_width, render_height);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer_ms[Depth]);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 16,
        GL_DEPTH_COMPONENT24, render_width, render_height);

    // generate frame buffer object for the multi-sampled FBO
    glGenFramebuffers(1, &framebuffer_ms);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_ms);
    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
        GL_RENDERBUFFER, renderbuffer_ms[Color]);
    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 
        GL_RENDERBUFFER, renderbuffer_ms[Depth]);

    if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
        // draw
        glViewport(0, 0, render_width, render_height);
        glDrawBuffer(GL_COLOR_ATTACHMENT0);          // set draw to the created color render buffer
        glEnable(GL_MULTISAMPLE);                    // antialiasing
        glEnable(GL_DEPTH_TEST);
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClearDepth(1.0f);
        HMeshModel model;
        bool draw_success = drawModel(model, true, file_path.c_str());

        // copy to memory
        if (draw_success) {
            // single-sampled frame buffer object as the server-side copy target and copy-to-memory source
            GLuint framebuffer, renderbuffer[NumRenderbuffers];    

            // generate color and depth render buffers and allocate storage for the single-sampled FBO
            glGenRenderbuffers(NumRenderbuffers, renderbuffer);
            glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[Color]);
            glRenderbufferStorage(GL_RENDERBUFFER, 
                GL_RGBA, render_width, render_height);
            glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[Depth]);
            glRenderbufferStorage(GL_RENDERBUFFER, 
                GL_DEPTH_COMPONENT24, render_width, render_height);

            // generate frame buffer object
            glGenFramebuffers(1, &framebuffer);
            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
            glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
                GL_RENDERBUFFER, renderbuffer[Color]);
            glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 
                GL_RENDERBUFFER, renderbuffer[Depth]);
            glDrawBuffer(GL_COLOR_ATTACHMENT0);

            if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
                // set up to read from the multi-sampled FBO
                glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer_ms);

                // copy from the multi-sampled FBO to the single-sampled FBO
                glBlitFramebuffer(
                    0, 0, render_width, render_height,
                    0, 0, render_width, render_height, 
                    GL_COLOR_BUFFER_BIT, GL_NEAREST);  

                // create memory storage for the pixel array
                int image_bytes = alignInteger(render_width*3, BMP_ROW_ALIGN) * render_height;
                unsigned char *image = new unsigned char[image_bytes];

                // copy pixels to memory from the single-sampled frame bffer
                glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);    // set up to read from the single-sampled FBO
                glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);                  // do not copy into any server side buffer object
                glReadBuffer(GL_COLOR_ATTACHMENT0);
                glPixelStorei(GL_PACK_ALIGNMENT, BMP_ROW_ALIGN);
                glGetError();
                glReadPixels(0, 0, render_width, render_height, 
                    GL_BGR, GL_UNSIGNED_BYTE, image);
                GLenum error = checkGLError(__FILE__, __LINE__);

                // write to file
                string image_path = getFilename(file_path.c_str()) + "_off_screen.bmp";
                writeBMP(image_path.c_str(), render_width, render_height, BMP_BGR, image);

                delete[] image;
            } else {
                cout << internalErrorPrefix() << "): frame buffer object not complete" << endl;
            }

            // delete the created frame buffer objects and render buffer objects
            glDeleteRenderbuffers(NumRenderbuffers, renderbuffer);
            glDeleteFramebuffers(1, &framebuffer);
        }
    } else {
        cout << internalErrorPrefix() << "): frame buffer object not complete" << endl;
    }

    // delete the created frame buffer objects and render buffer objects
    glDeleteRenderbuffers(NumRenderbuffers, renderbuffer_ms);
    glDeleteFramebuffers(1, &framebuffer_ms);
    // bind the read and draw frame buffer to the default (window)
    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

    // restore the opengl status for window buffer drawing
    initializeGL();
    glViewport(0, 0, window_width_, window_height_);
}

 

window_width_和window_height_分别是窗口的宽高,checkGLError是一个获取openGL错误的函数。虽然函数是一个QT的GLWidget类的一个成员函数,不过由于实际上与类本身的关联并不大,故比较容易粘贴移植到其他场景下(之所以没有做一个可复用的版本可能是觉得这样做起来实在太麻烦而且容易出错,也许以后可以写一个)。使用时把drawModel函数替换成自己的其他可能只要稍微修改下就好了。另附上一些支撑的函数和自己从网上修改得来的写BMP文件的代码:

template<typename T>
T alignInteger (T n, T divisor) {
    if (n % divisor != 0)
        n = n - n % divisor + divisor;

    return n;
}

const int BMP_ROW_ALIGN = 4;
enum BMPPixelType { BMP_BGR = 0, BMP_BGRA = 1 };
const int BMP_HEADER_LEN = 54;
const static unsigned char pixel_bytes[2] = {  3/*BGR*/,  4/*BGRA*/ };
const static unsigned char pixel_bits[2]  = { 24/*BGR*/, 32/*BGRA*/ };

bool writeBMP(
    const char *file_path, const int width, const int height,
    const BMPPixelType pixel_type, const unsigned char *pdata
){
    unsigned char header[BMP_HEADER_LEN] = {
        0x42, 0x4d, 0, 0, 0, 0, 0, 0, 0, 0,
        54, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, pixel_bits[pixel_type], 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0
    };

    long file_size = (long)width * (long)height * pixel_bytes[pixel_type] + 54;
    header[2] = (unsigned char)(file_size &0x000000ff);
    header[3] = (file_size >> 8) & 0x000000ff;
    header[4] = (file_size >> 16) & 0x000000ff;
    header[5] = (file_size >> 24) & 0x000000ff;

    long w = width;
    header[18] = w & 0x000000ff;
    header[19] = (w >> 8) &0x000000ff;
    header[20] = (w >> 16) &0x000000ff;
    header[21] = (w >> 24) &0x000000ff;

    long h = height;
    header[22] = h &0x000000ff;
    header[23] = (h >> 8) &0x000000ff;
    header[24] = (h >> 16) &0x000000ff;
    header[25] = (h >> 24) &0x000000ff;

    FILE *pfile = NULL;

    pfile = fopen(file_path, "wb");
    if (pfile == NULL) {
        fprintf(stderr, 
            "#error(%s, line %d): open file '%s' for write failed\n", 
            __FILE__, __LINE__, file_path);
        return false;
    }

    fwrite(header, sizeof(unsigned char), 54, pfile);
    int row_length, total_length;
    row_length = width * pixel_bytes[pixel_type];         // 每行数据长度大致为图象宽度乘以每像素字节数
    row_length = alignInteger(row_length, BMP_ROW_ALIGN); // 修正LineLength使其为4的倍数
    total_length = row_length * height;                   // 数据总长 = 每行长度 * 图象高度

    fwrite(pdata, sizeof(unsigned char), (size_t)(long)total_length,  pfile);

    // 释放内存和关闭文件
    fclose(pfile);
    return true;
}