c++ - 如何在 OpenGL 上渲染屏幕外?

标签 c++ windows opengl off-screen

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

我该怎么做?

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

最佳答案

一切从 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 上缓冲数据,因此该 block 可能需要更少的时间。读取的像素代码会变成这样:

//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 和 main 中的内存块内存。

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

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

关于c++ - 如何在 OpenGL 上渲染屏幕外?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12157646/

相关文章:

c++ - 在 imageStore() 之后读取纹素

c++ - OpenGL 示例中 WinMain 错误的重新定义

c++ - 在没有 OpenCV 的情况下从 C++ 中的 RGB 数组拆分 channel

c++ - 避免通用容器中 begin() 和 end() 函数的重复代码

java - 用 Java 创建的线程在 Windows 和 Linux 上的行为是否不同?

c# - 在 c# Windows 应用程序中从雅虎发送邮件时出现身份验证错误

c++ - Qt中需要释放slot参数的资源吗?

c++ - 动态数组传递给主要填充随机值的函数,返回值 3221226356

Windows 重叠 IO 与单独线程上的 IO

java - 我如何在opengl中画一个半圆