我的目的是把OpenGL场景渲染成没有窗口的,直接渲染成一个文件。 现场可能比我的屏幕分辨率更大。
我怎样才能做到这一点?
我希望能够将渲染区域大小select为任意大小,例如10000×10000,如果可能的话?
这一切都以glReadPixels
开始,您将使用它来将存储在GPU上特定缓冲区中的像素传输到主内存(RAM)。 正如你会在文档中注意到的,没有选择哪个缓冲区的参数。 和OpenGL一样,当前读取的缓冲区是一个状态,您可以使用glReadBuffer
进行设置。
所以一个非常基本的屏幕外渲染方法将如下所示。 我使用c + +伪代码,所以它可能会包含错误,但应使总体流程清晰:
//Before swapping std::vector<std::uint8_t> data(width*height*4); glReadBuffer(GL_BACK); glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]);
这将读取当前的后台缓冲区(通常是你正在绘制的缓冲区)。 在交换缓冲区之前,您应该调用它。 请注意,你也可以用上面的方法完美地读取后台缓冲区,清除它并在交换之前绘制完全不同的东西。 从技术上讲,你也可以阅读前端缓冲区,但是这经常令人沮丧,理论上实现允许进行一些优化,这可能会使前端缓冲区包含垃圾。
这有一些缺点。 首先,我们不是真的在屏幕外渲染。 我们渲染到屏幕缓冲区并从那里读取。 我们可以通过从不在后台缓冲区交换模拟屏幕外渲染,但它不正确。 接下来,正面和背面的缓冲区被优化来显示像素,而不是读回来。 这就是Framebuffer对象的作用。
本质上,FBO可以让你创建一个非默认的帧缓冲区(如FRONT和BACK缓冲区),允许你绘制一个内存缓冲区而不是屏幕缓冲区。 实际上,您可以绘制纹理或渲染缓冲区 。 当你想在OpenGL本身中重新使用像素作为纹理时(例如游戏中一个天真的“安全摄像机”),第一个是最佳的,如果你只想渲染/回读,后者是最好的。 有了这个,上面的代码就会变成类似这样的东西,再一个伪代码,所以如果输入错误或者忘记了一些陈述,不要杀了我。
//Somewhere at initialization GLuint fbo, render_buf; glGenFramebuffers(1,&fbo); glGenRenderbuffers(1,&render_buf); glBindRenderbuffer(render_buf); glRenderbufferStorage(GL_RENDERBUFFER, GL_BGRA8, width, height); glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fbo); glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_buf); //At deinit: glDeleteFramebuffers(1,&fbo); glDeleteRenderbuffers(1,&render_buf); //Before drawing glBindFramebuffer(GL_DRAW_FRAMEBUFFER,fbo); //after drawing std::vector<std::uint8_t> data(width*height*4); glReadBuffer(GL_COLOR_ATTACHMENT0); glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]); // Return to onscreen rendering: glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);
这是一个简单的例子,实际上你可能也想存储深度(和模板)缓冲区。 你也可能想渲染纹理,但是我将把它作为一个练习。 在任何情况下,您现在都将执行真正的屏幕外渲染,并且在读取后台缓冲区时可能会运行得更快。
最后,可以使用像素缓冲区对象使读像素异步。 问题是, glReadPixels
阻塞,直到像素数据完全传输,这可能会停滞你的CPU。 PBO的实现可能会立即返回,因为它仍然控制缓冲区。 只有当你映射管道将阻塞的缓冲区时。 但是,PBO可能会被优化,以缓冲RAM上的数据,所以这个块可能花费很少的时间。 读取的像素代码会变成这样的东西:
//Init: GLuint pbo; glGenBuffers(1,&pbo); glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glBufferData(GL_PIXEL_PACK_BUFFER, width*height*4, NULL, GL_DYNAMIC_READ); //Deinit: glDeleteBuffers(1,&pbo); //Reading: glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,0); // 0 instead of a pointer, it is now an offset in the buffer. //DO SOME OTHER STUFF (otherwise this is a waste of your time) glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); //Might not be necessary... pixel_data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
帽中的部分是必不可少的。 如果你只是向一个PBO发出一个glReadPixels
,然后是该PBO的一个glMapBuffer
,那么你只能获得很多代码。 确保glReadPixels
可能会立即返回,但是现在glMapBuffer
将会停止工作,因为它必须安全地将数据从读缓冲区映射到PBO和主RAM中的一块内存。
另外请注意,我在任何地方都使用GL_BGRA,这是因为许多图形卡在内部使用这个作为最佳的渲染格式(或没有alpha的GL_BGR版本)。 它应该是像这样的像素传输最快的格式。 我会尽力找到我读过的关于这几个mont的nvidia文章。
在使用OpenGL ES 2.0时, GL_DRAW_FRAMEBUFFER
可能不可用,在这种情况下应该只使用GL_FRAMEBUFFER
。
我假定创建一个虚拟窗口(你不会渲染它,它只是在那里,因为API需要你做),你创建主要上下文是一个可接受的实现策略。
这里是你的选择:
像素缓冲区或pbuffer(不是像素缓冲区对象 )首先是一个OpenGL上下文 。 基本上,你创建一个正常的窗口,然后从wglChoosePixelFormatARB
选择一个像素格式(必须从这里获得pbuffer格式)。 然后,调用wglCreatePbufferARB
,给它的窗口的HDC和你想要使用的像素缓冲区格式。 哦,宽度/高度; 你可以查询实现的最大宽度/高度。
pbuffer的默认framebuffer在屏幕上是不可见的,最大宽度/高度是硬件希望让你使用的。 所以你可以渲染它,并使用glReadPixels
从它读回。
如果您在窗口上下文中创建了对象,则需要与给定的上下文共享您的上下文。 否则,您可以完全单独使用pbuffer上下文。 只是不要破坏窗口的上下文。
这样做的好处是支持更多的实现(尽管大多数不支持这些选项的驱动程序也是不再支持硬件的旧驱动程序,或者是Intel硬件)。
缺点是这些。 Pbuffers不能与核心的OpenGL上下文一起工作。 他们可能会兼容,但是没有办法给wglCreatePbufferARB
关于OpenGL版本和配置文件的信息。
Framebuffer对象比pbuffers更适合屏幕外的rendertargets。 FBO是在一个上下文中,而pbuffers则是在创造新的上下文。
FBO只是您渲染的图像的容器。 可以查询实现允许的最大尺寸; 你可以假设它是GL_MAX_VIEWPORT_DIMS
(确定一个FBO是绑定的,然后再检查这个,因为FBO是绑定的)。
既然你不是从这些纹理中抽取纹理(你只是读取值),你应该使用渲染缓冲而不是纹理。 它们的最大尺寸可能大于纹理的尺寸。
好处是易于使用。 而不是必须处理像素格式等,你只需为你的glRenderbufferStorage
调用选择一个合适的图像格式 。
唯一真正的缺点是支持它们的较窄的硬件。 一般来说,AMD或NVIDIA所支持的任何东西(即现在的GeForce 6xxx或更高版本(注意x的数量)以及任何Radeon HD卡)都可以访问ARB_framebuffer_object或OpenGL 3.0+(这是其核心功能)。 老的驱动程序可能只有EXT_framebuffer_object的支持(有一些差异)。 英特尔硬件是一个家伙, 即使他们声称支持3.x或4.x,但由于驱动程序错误,它仍然可能会失败。
如果你需要渲染的东西超过了GL实现的最大FBO大小, libtr
可以很好地工作:
TR(Tile Rendering)库是用于进行平铺渲染的OpenGL实用程序库。 平铺渲染是一种用于以块(tile)生成大图像的技术。
TR是有记忆效率的; 可以在主存储器中没有分配全尺寸图像缓冲器的情况下生成任意大的图像文件。
最简单的方法是使用称为帧缓冲对象(FBO)的东西。 你仍然需要创建一个窗口来创建一个opengl上下文虽然(但这个窗口可以隐藏)。
实现你的目标最简单的方法是使用FBO做离屏渲染。 而且你不需要渲染纹理,然后得到teximage。 只需渲染缓冲区并使用函数glReadPixels 。 这个链接将是有用的。 请参阅Framebuffer对象示例