【问题标题】:How to render offscreen on OpenGL? [duplicate]如何在 OpenGL 上渲染屏幕外? [复制]
【发布时间】:2012-08-22 20:46:42
【问题描述】:

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

我该怎么做?

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

【问题讨论】:

    标签: c++ windows opengl off-screen


    【解决方案1】:

    这一切都从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 版本)。它应该是这样的像素传输最快的格式。我会尝试找到我在几个月前读过的关于这方面的 nvidia 文章。

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

    【讨论】:

    • 如何在不打开窗口的情况下使用FBO?如果我不打电话给glutCreateWindow(argv[0]);,我的程序就不会运行。
    • 很遗憾你不能(一般来说)。 OpenGL 不提供创建离屏上下文的方法。使用来自另一个应用程序的窗口/上下文有一些技巧,但大多数人只是创建一个 1x1 窗口并立即将其隐藏。
    • 请不要编辑带有附带问题的帖子。答案是关于如何在屏幕外渲染,而不是如何设置没有窗口的 OpenGL 上下文(相关,但技术上完全不同的主题)。
    • 这个问题清楚地说明了没有窗口,所以我真的很期待这个“附带话题”的答案。对我来说,这是这个问题更有趣的方面,因为默认情况下,OpenGL 中的任何渲染实际上都是 offscreen
    • 这是一个很好的答案,但请务必阅读所提到的每个功能的文档。这些函数对格式说明符非常挑剔
    【解决方案2】:

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

    以下是您的选择:

    像素缓冲区

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

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

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

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

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

    帧缓冲对象

    Framebuffer Objects 比 pbuffers 更“合适”的屏幕外渲染目标。 FBO 是在一个上下文中,而 pbuffers 是关于创建新的上下文。

    FBO 只是您渲染到的图像的容器。可以查询实现允许的最大维度;你可以假设它是GL_MAX_VIEWPORT_DIMS(在检查之前确保绑定了 FBO,因为它会根据 FBO 是否被绑定而变化)。

    由于您没有从这些纹理中采样(您只是在读取值),因此您应该使用渲染缓冲区而不是纹理。它们的最大尺寸可能大于纹理。

    好处是易于使用。您不必处理像素格式等问题,只需为您的glRenderbufferStorage 调用选择一个合适的image format

    唯一真正的缺点是支持它们的硬件范围较窄。一般来说,AMD 或 NVIDIA 制造但仍支持的任何东西(目前,GeForce 6xxx 或更高版本 [注意 x 的数量],以及任何 Radeon HD 卡)都可以访问 ARB_framebuffer_object 或 OpenGL 3.0+(这是核心功能) )。较旧的驱动程序可能只有 EXT_framebuffer_object 支持(有一些差异)。英特尔硬件是家常便饭;即使他们声称支持 3.x 或 4.x,它仍然可能由于驱动程序错误而失败。

    【讨论】:

    • 看起来 GL_MAX_VIEWPORT_DIMS 不能保证返回有效值。它给了我 8192x8192,但是当我创建这样的 FBO 时,它给出了错误:GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER。较小的尺寸,例如 8192x4096 作品。我相信这是因为我的 gfx 卡只有 256mb 内存?
    • @Rookie:最大视口大小为视口大小;您是否可以成功创建该查询未回答大小的渲染缓冲区。您的问题很可能是 glRenderbufferStorage 失败并出现 GL_OUT_OF_MEMORY 错误,因为 INCOMPLETE_DRAW_BUFFER 会建议附加图像没有存储空间。当然,如果您使用像 GL_R8 这样的 8 位格式,它可能会成功。所以尺寸还是准确的;它因其他原因而失败。
    • Framebuffer 被引用为首选方法,以及示例here。具体请参考“渲染到缓冲区”示例。
    【解决方案3】:

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

    TR(平铺渲染)库是一个 OpenGL 实用程序库,用于执行 平铺渲染。平铺渲染是一种生成大型 图像碎片(平铺)。

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

    【讨论】:

    • 出于兴趣:libtr 也可以在 3d 透视模式下工作吗? (我想不出它怎么可能,没有任何接缝)。
    • 确实如此! “OpenGL 程序通常调用 glFrustum、glOrtho 或 gluPerspective 来设置投影矩阵。TR 库中有三个对应的函数。”
    • 据我记忆,libtr 相当不错。对于如此巨大的分辨率,您肯定需要它。
    【解决方案4】:

    最简单的方法是使用称为帧缓冲区对象 (FBO) 的东西。你仍然需要创建一个窗口来创建一个 opengl 上下文(但是这个窗口可以被隐藏)。

    【讨论】:

    • 那个或者 pbuffers。尽管如此,渲染目标大小受 MAX_TEXTURE_SIZE 限制(在 FBO 的情况下),因此对于一些非常大的输出可能需要拆分为多个区域。
    • 与PBO有什么区别?哪个更快?
    • 那么,如果我使用FBO,我渲染成一个纹理,然后使用glGetTexImage()获取纹理像素数据并保存到文件?
    • 类似的东西。但是,您仍然需要有效的初始化 GL 上下文来执行任何渲染 - 无论是窗口还是屏幕外。
    • @Rookie:本质上,您使用 PBO 来加快将 GPU 传输到主内存 (RAM) 的速度,它不是一种离屏渲染技术,可以与 FBO 一起使用。
    【解决方案5】:

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

    【讨论】:

    • glReadPixels 是否完全符合读取 teximage 的功能?还是更快?不过它可能会在边缘加速,因为有一些未使用的像素区域我会忽略...
    • 应该更快。 glReadPixels 直接将数据从 fb 读取到内存中。但是 glGetTexImage 需要先 fb 渲染到纹理,然后从纹理读取到内存。通过正确使用 fb,您可以读取所需的像素区域。
    猜你喜欢
    • 2015-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多