最近写论文需要用到离屏渲染(主要是因为模型太大普通窗口绘制根本做不了),于是翻阅了红宝书查了下相关api和用法。中文版的红宝书可读性有点差,很多地方翻译地晦涩,但好歹读起来比较快,主要相关章节为第8章和第10章(可以连带把第9章读完以后写GLSL会顺利成章)。貌似superbible可读性更强,但红宝书讲得也差不多了就没再继续看。
由于红宝书过于学术,想动手还是最好查查网上的资料,于是把一些还可以的资料列一下。
关于FBO:
关于glReadPixels:
OpenGL中位图的操作(glReadPixels,glDrawPixels等)
关于在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; }