關於OpenGL Framebuffer Object、glReadPixels與離屏渲染

最近寫論文須要用到離屏渲染(主要是由於模型太大普通窗口繪製根本作不了),因而翻閱了紅寶書查了下相關api和用法。中文版的紅寶書可讀性有點差,不少地方翻譯地晦澀,但好歹讀起來比較快,主要相關章節爲第8章和第10章(能夠連帶把第9章讀完之後寫GLSL會順利成章)。貌似superbible可讀性更強,但紅寶書講得也差很少了就沒再繼續看。html

因爲紅寶書過於學術,想動手仍是最好查查網上的資料,因而把一些還能夠的資料列一下。api

關於FBO:ide

OpenGL中的FBO對象(含源碼)函數

OpenGL的幀緩衝對象和浮點紋理post

GPGPU計算觀念和基本思路總結 測試

OpenGL.FrameBuffer Object ui

frame buffer object (fbo)整理url

關於glReadPixels:spa

OpenGL中位圖的操做(glReadPixels,glDrawPixels等).net

關於在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;
}
相關文章
相關標籤/搜索