最近寫論文須要用到離屏渲染(主要是由於模型太大普通窗口繪製根本作不了),因而翻閱了紅寶書查了下相關api和用法。中文版的紅寶書可讀性有點差,不少地方翻譯地晦澀,但好歹讀起來比較快,主要相關章節爲第8章和第10章(能夠連帶把第9章讀完之後寫GLSL會順利成章)。貌似superbible可讀性更強,但紅寶書講得也差很少了就沒再繼續看。html
因爲紅寶書過於學術,想動手仍是最好查查網上的資料,因而把一些還能夠的資料列一下。api
關於FBO:ide
frame buffer object (fbo)整理url
關於glReadPixels:spa
OpenGL中位圖的操做(glReadPixels,glDrawPixels等).net
關於在FBO中使用多重採樣:
【OpenGL】FBO中多重採樣抗鋸齒(MSAA:MultiSampling Anti-Aliasing)
總結上面的參考資料,並主要參照紅寶書的代碼,離屏渲染的代碼以下(經測試確實可用):
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; }