42

我的目标是将没有窗口的 OpenGL 场景直接渲染到文件中。场景可能比我的屏幕分辨率大。

我怎样才能做到这一点?

如果可能,我希望能够将渲染区域大小选择为任何大小,例如 10000x10000?

4

5 回答 5

98

这一切都从 开始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]);

这将读取当前的后台缓冲区(通常是您正在绘制的缓冲区)。您应该在交换缓冲区之前调用它。请注意,您也可以使用上述方法完美地读取后台缓冲区,清除它并在交换之前绘制完全不同的东西。从技术上讲,您也可以读取前端缓冲区,但通常不鼓励这样做,因为理论上允许实现一些优化,这可能会使您的前端缓冲区包含垃圾。

这有一些缺点。首先,我们并没有真正进行离屏渲染。我们渲染到屏幕缓冲区并从中读取。我们可以通过从不交换后台缓冲区来模拟屏幕外渲染,但感觉不对。除此之外,前缓冲区和后缓冲区都经过优化以显示像素,而不是读取它们。这就是帧缓冲对象发挥作用的地方。

本质上,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);

大写的部分是必不可少的。如果您只是glReadPixels向 PBO 发出 a,然后是glMapBuffer该 PBO 的 a,那么您只获得了很多代码。当然glReadPixels可能会立即返回,但现在glMapBuffer会停止,因为它必须安全地将数据从读取缓冲区映射到 PBO 和主 RAM 中的内存块。

另请注意,我在所有地方都使用 GL_BGRA,这是因为许多显卡内部使用它作为最佳渲染格式(或没有 alpha 的 GL_BGR 版本)。它应该是这样的像素传输最快的格式。我会尝试找到我在几个月前读到的关于这方面的 nvidia 文章。

使用 OpenGL ES 2.0 时,GL_DRAW_FRAMEBUFFER可能不可用,您应该GL_FRAMEBUFFER在这种情况下使用。

于 2012-08-28T12:32:42.417 回答
20

我假设创建一个虚拟窗口(你不渲染它;它只是在那里,因为 API 要求你创建一个),你创建你的主要上下文是一种可接受的实现策略。

以下是您的选择:

像素缓冲区

像素缓冲区或 pbuffer(不是像素缓冲区对象)首先是OpenGL 上下文。基本上,您照常创建一个窗口,然后从中选择一个像素格式wglChoosePixelFormatARB(pbuffer 格式必须从这里获取)。然后,你调用wglCreatePbufferARB,给它你的窗口的 HDC 和你想使用的像素缓冲区格式。哦,还有宽度/高度;您可以查询实现的最大宽度/高度。

pbuffer 的默认帧缓冲区在屏幕上不可见,最大宽度/高度是硬件想让您使用的任何值。所以你可以渲染它并使用它glReadPixels来读取它。

如果您在窗口上下文中创建了对象,则需要与给定上下文共享上下文。否则,您可以完全单独使用 pbuffer 上下文。只是不要破坏窗口上下文。

这里的优势是更大的实现支持(尽管大多数不支持替代方案的驱动程序也是不再受支持的硬件的旧驱动程序。或者是英特尔硬件)。

缺点就是这些。Pbuffers 不适用于核心 OpenGL 上下文。它们可能适用于兼容性,但无法提供wglCreatePbufferARB有关 OpenGL 版本和配置文件的信息。

帧缓冲对象

帧缓冲区对象比 pbuffers 更“合适”的屏幕外渲染目标。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,它仍然可能由于驱动程序错误而失败。

于 2012-08-28T12:32:10.943 回答
3

如果您需要渲染超出 GL 实现的最大 FBO 大小的东西,libtr效果很好:

TR(平铺渲染)库是用于进行平铺渲染的 OpenGL 实用程序库。平铺渲染是一种以块(平铺)形式生成大图像的技术。

TR 是内存高效的;可以生成任意大的图像文件,而无需在主存储器中分配完整大小的图像缓冲区。

于 2012-08-28T20:01:08.140 回答
2

最简单的方法是使用称为帧缓冲区对象 (FBO) 的东西。不过,您仍然必须创建一个窗口来创建 opengl 上下文(但可以隐藏此窗口)。

于 2012-08-28T10:48:11.203 回答
1

实现目标的最简单方法是使用 FBO 进行屏幕外渲染。而且你不需要渲染到纹理,然后得到teximage。只需渲染到缓冲区并使用函数 glReadPixels。此链接将很有用。请参阅帧缓冲区对象示例

于 2012-08-29T02:52:13.163 回答