我的目标是将没有窗口的OpenGL场景直接渲染到文件中。场景可能比我的屏幕分辨率大。
我该怎么做?
我希望能够选择任意大小的渲染区域大小,例如10000x10000,如果可能的话?
答案 0 :(得分:84)
这一切都始于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 Objects发挥作用的地方。
基本上,FBO允许您创建一个非默认的帧缓冲区(如FRONT和BACK缓冲区),它允许您绘制到内存缓冲区而不是屏幕缓冲区。实际上,您可以绘制纹理或renderbuffer。当你想重新使用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);
这是一个简单的例子,实际上您可能还希望存储深度(和模板)缓冲区。你也可能想要渲染到纹理,但我会把它留作练习。在任何情况下,您现在都将执行真正的屏幕外渲染,它可能比读取后缓冲区更快。
最后,您可以使用pixel buffer objects使读取像素异步。问题是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版本)。它应该是像这样的像素传输的最快格式。我会试着找回我读过的关于这几个monts的nvidia文章。
使用OpenGL ES 2.0时,GL_DRAW_FRAMEBUFFER
可能无法使用,在这种情况下您应该只使用GL_FRAMEBUFFER
。
答案 1 :(得分:16)
我会假设创建一个虚拟窗口(你没有渲染它;它就在那里,因为API要求你创建一个)你创建主要上下文是一个可接受的实现策略。
以下是您的选择:
像素缓冲区或pbuffer(不是pixel buffer object)首先是 OpenGL上下文。基本上,你正常创建一个窗口,然后从wglChoosePixelFormatARB
中选择一个像素格式(必须从这里获取pbuffer格式)。然后,你调用wglCreatePbufferARB
,给它你的窗口的HDC和你想要使用的像素缓冲区格式。哦,宽度/高度;您可以查询实现的最大宽度/高度。
屏幕上看不到pbuffer的默认帧缓冲区,最大宽度/高度是硬件要让您使用的任何内容。所以你可以渲染它并使用glReadPixels
从它回读。
如果您已在窗口上下文中创建了对象,则需要与给定的上下文共享上下文。否则,您可以完全单独使用pbuffer上下文。只是不要破坏窗口上下文。
这里的优势是更大的实现支持(尽管大多数不支持替代方案的驱动程序也是不再支持的硬件的旧驱动程序。或者是Intel硬件。)
缺点是这些。 Pbuffers不能与core OpenGL contexts一起使用。它们可能兼容,但无法提供有关OpenGL版本和配置文件的wglCreatePbufferARB
信息。
Framebuffer Objects比pbuffers更“适当”的屏幕外渲染。 FBO在上下文中,而pbuffers则用于创建新的上下文。
FBO只是您呈现的图像的容器。可以查询实现允许的最大维度;你可以假设它是GL_MAX_VIEWPORT_DIMS
(确保在检查之前绑定FBO,因为它会根据是否绑定FBO而改变。)
由于你没有从这些中采样纹理(你只是回读值),你应该使用renderbuffers而不是纹理。它们的最大尺寸可能比纹理的尺寸大。
优点是易用性。您只需为glRenderbufferStorage
电话选择合适的image format,而不必处理像素格式等。
唯一真正的缺点是支持它们的较窄的硬件带。一般来说,AMD或NVIDIA所做的任何他们仍然支持的东西(现在,GeForce 6xxx或更好[注意x的数量]和任何Radeon HD卡)都可以访问ARB_framebuffer_object或OpenGL 3.0+(这是它的核心功能) )。较旧的驱动程序可能只有EXT_framebuffer_object支持(有一些差异)。英特尔硬件是便饭;即使他们声称支持3.x或4.x,它仍可能因驱动程序错误而失败。
答案 2 :(得分:3)
如果您需要渲染超出GL实施的最大FBO大小的内容libtr
效果很好:
TR(Tile Rendering)库是一个OpenGL实用程序库 平铺渲染。平铺渲染是一种生成大型的技术 图像碎片(瓷砖)。
TR具有内存效率;可以生成任意大的图像文件 不在主存储器中分配全尺寸图像缓冲区。
答案 3 :(得分:2)
最简单的方法是使用称为帧缓冲对象(FBO)的东西。你仍然需要创建一个窗口来创建一个opengl上下文(但是这个窗口可以被隐藏)。
答案 4 :(得分:1)
实现目标的最简单方法是使用FBO进行离屏渲染。而且你不需要渲染纹理,然后获得teximage。只需渲染缓冲并使用函数 glReadPixels 。这个链接很有用。见Framebuffer Object Examples