如何在OpenGL上渲染屏幕?

Roo*_*kie 35 c++ windows opengl off-screen

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

我怎样才能做到这一点?

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

Kil*_*nDS 88

一切都始于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]);
Run Code Online (Sandbox Code Playgroud)

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

这有一些缺点.首先,我们不是真的做屏幕外渲染吗?我们渲染到屏幕缓冲区并从中读取.我们可以通过从不在后台缓冲区中交换来模拟屏幕外渲染,但感觉不对.接下来,前后缓冲区经过优化,可显示像素,而不是读取像素.这就是Framebuffer Objects发挥作用的地方.

基本上,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);
Run Code Online (Sandbox Code Playgroud)

这是一个简单的例子,实际上您可能还需要存储深度(和模板)缓冲区.你也可能想要渲染到纹理,但我会把它留作练习.在任何情况下,您现在将执行真正的屏幕外渲染,它可能比读取后缓冲区更快.

最后,您可以使用像素缓冲区对象使读取像素异步.问题是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);
Run Code Online (Sandbox Code Playgroud)

帽子中的部分是必不可少的.如果您只是glReadPixels向PBO 发出一个,然后是一个glMapBufferPBO,那么除了大量代码之外,您什么都得不到.肯定glReadPixels会立即返回,但现在glMapBuffer意志停止,因为它必须安全地将数据从读缓冲区映射到PBO和主RAM中的内存块.

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

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

  • 可悲的是,你不能(一般而言).OpenGL没有提供创建屏幕外环境的方法.有一些技巧使用来自另一个应用程序的窗口/上下文,但大多数人只是创建一个1x1窗口并立即隐藏它. (2认同)
  • 请不要编辑有问题的帖子。答案是关于如何在屏幕外渲染,而不是如何设置没有窗口的 OpenGL 上下文(相关,但技术上完全不同的主题)。 (2认同)
  • 问题清楚地表明**没有窗口**,所以我真的希望在这里回答这个"侧面话题".对我来说,这是问题中更有趣的方面,因为默认情况下,OpenGL中的任何渲染都是_offscreen_. (2认同)
  • 在调用 glReadPixels 之前缺少 `glBindFramebuffer(GL_READ_FRAMEBUFFER​,fbo);` (2认同)
  • 这是一个很好的答案,但请务必阅读所提及的每个功能的文档.关于格式说明符,这些函数非常挑剔**. (2认同)

Nic*_*las 18

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

以下是您的选择:

像素缓冲区

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

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

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

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

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

帧缓冲对象

帧缓冲对象比pbuffers更"适当"的屏幕外渲染.FBO在上下文中,而pbuffers则用于创建新的上下文.

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

既然你没有从这些中采样纹理(你只是回读值),你应该使用renderbuffers而不是纹理.它们的最大尺寸可能大于纹理尺寸.

好处是易用性.您只需为您的呼叫选择合适的图像格式,而不必处理像素格式等glRenderbufferStorage.

唯一真正的缺点是支持它们的较窄的硬件带.一般来说,AMD或NVIDIA所做的任何他们仍然支持的东西(现在,GeForce 6xxx或更好[注意x的数量]和任何Radeon HD卡)都可以访问ARB_framebuffer_object或OpenGL 3.0+(这是它的核心功能) ).较旧的驱动程序可能只有EXT_framebuffer_object支持(有一些差异).英特尔硬件是便饭; 即使他们声称支持3.x或4.x,它仍可能因驱动程序错误而失败.