将 OpenGL 渲染保存到图像文件

Posted

技术标签:

【中文标题】将 OpenGL 渲染保存到图像文件【英文标题】:Save OpenGL rendering to an image file 【发布时间】:2019-12-08 10:00:50 【问题描述】:

虽然 OpenGL 的作用很简单,但我仍然感到困惑,但我开始了解它是如何工作的。

我正在寻找一个最小的离屏渲染示例来帮助我入门。

我的应用程序将获取一堆三角形以及有关如何相对于相机定位它们并将渲染结果保存到图像文件的信息。暂时没有灯光、材质或后期处理。

我观看了有关创建屏幕外上下文、创建 FBO、渲染到纹理等的教程。我不介意使用 QT,因为它方便地提供了 OpenGL 工具、窗口和 QImage。据我了解,为了能够对渲染图像进行图像处理,您需要将渲染目标设置为纹理,然后使用着色器,最后将纹理读取到数组中。

试图把事情放在一起从来没有让我找到一个好的起点。我要么被设置依赖项卡住,出现黑屏,要么盯着那些除了我需要的东西做太多事情的项目。

更新 1: 搞定了。

#include <QtGui/QGuiApplication>
#include <QtGui/QSurfaceFormat>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QOpenGLFunctions_4_3_Core>
#include <QtGui/QOpenGLFramebufferObject>
#include <QtGui/QOpenGLShaderProgram>
#include <QDebug>
#include <QImage>
#include <QOpenGLBuffer>

int main(int argc, char* argv[])

   QGuiApplication a(argc, argv);

   QSurfaceFormat surfaceFormat;
   surfaceFormat.setMajorVersion(4);
   surfaceFormat.setMinorVersion(3);

   QOpenGLContext openGLContext;
   openGLContext.setFormat(surfaceFormat);
   openGLContext.create();
   if(!openGLContext.isValid()) return -1;

   QOffscreenSurface surface;
   surface.setFormat(surfaceFormat);
   surface.create();
   if(!surface.isValid()) return -2;

   openGLContext.makeCurrent(&surface);

   QOpenGLFunctions_4_3_Core f;
   if(!f.initializeOpenGLFunctions()) return -3;

   qDebug() << QString::fromLatin1((const char*)f.glGetString(GL_VERSION));

   QSize vpSize = QSize(100, 200);

   qDebug("Hi");

   QOpenGLFramebufferObjectFormat fboFormat;
   fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
   QOpenGLFramebufferObject fbo(vpSize, fboFormat);

   fbo.bind();

    // //////////


   static const float vertexPositions[] = 
       -0.8f, -0.8f, 0.0f,
        0.8f, -0.8f, 0.0f,
        0.0f,  0.8f, 0.0f
   ;

   static const float vertexColors[] = 
       1.0f, 0.0f, 0.0f,
       0.0f, 1.0f, 0.0f,
       0.0f, 0.0f, 1.0f
   ;

   QOpenGLBuffer vertexPositionBuffer(QOpenGLBuffer::VertexBuffer);
   vertexPositionBuffer.create();
   vertexPositionBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
   vertexPositionBuffer.bind();
   vertexPositionBuffer.allocate(vertexPositions, 9 * sizeof(float));

   QOpenGLBuffer vertexColorBuffer(QOpenGLBuffer::VertexBuffer);
   vertexColorBuffer.create();
   vertexColorBuffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
   vertexColorBuffer.bind();
   vertexColorBuffer.allocate(vertexColors, 9 * sizeof(float));

   QOpenGLShaderProgram program;
   program.addShaderFromSourceCode(QOpenGLShader::Vertex,
                                   "#version 330\n"
                                   "in vec3 position;\n"
                                   "in vec3 color;\n"
                                   "out vec3 fragColor;\n"
                                   "void main() \n"
                                   "    fragColor = color;\n"
                                   "    gl_Position = vec4(position, 1.0);\n"
                                   "\n"
                                   );
   program.addShaderFromSourceCode(QOpenGLShader::Fragment,
                                   "#version 330\n"
                                   "in vec3 fragColor;\n"
                                   "out vec4 color;\n"
                                   "void main() \n"
                                   "    color = vec4(fragColor, 1.0);\n"
                                   "\n"
                                   );
   program.link();
   program.bind();

   vertexPositionBuffer.bind();
   program.enableAttributeArray("position");
   program.setAttributeBuffer("position", GL_FLOAT, 0, 3);

   vertexColorBuffer.bind();
   program.enableAttributeArray("color");
   program.setAttributeBuffer("color", GL_FLOAT, 0, 3);

   f.glClearColor(0.3f, 0.0f, 0.7f, 1.0f);
   f.glClear(GL_COLOR_BUFFER_BIT);

   f.glDrawArrays(GL_TRIANGLES, 0, 3);

   program.disableAttributeArray("position");
   program.disableAttributeArray("color");

   program.release();

   // ///////////////

   fbo.release();

   qDebug("FBO released");

   QImage im = fbo.toImage();

   if (im.save("asd.png"))
       qDebug("Image saved!!");
   

   return 0;

保存的图片和FBO一样大小,颜色对应glClearColor中设置的那个,但是三角形没有渲染。我错过了什么?

【问题讨论】:

请参阅OpenGL Scale Single Pixel Line 它可以满足您 90% 的需求。但是它在屏幕上下文中使用...(在交换缓冲区之前您看不到渲染,因此不需要屏幕外的东西...)...它使用旧的 api 作为新的(渲染到纹理)不是由于驱动程序错误,在某些 Intel gfx 卡上可靠...glReadPixels 只需从那里将屏幕内容读入 CPU 端内存,您需要将其编码为图像...对于新 api,您可以加载纹理类似于它的方式存储... 另请参阅simple complete GL+VAO/VBO+GLSL+shaders example in C++,以防您需要开始使用 GL 上下文和东西(新旧 api) 感谢您的回复。硬件兼容性不是问题。渲染到纹理的例子真的很有帮助。 那将是FBO and render to texture,但这是很多代码,在glReadPixels 上没有任何速度奖励,因为无论如何您都需要将图像复制回 CPU 端... 我需要在 GPU 上进行图像处理。 【参考方案1】:

渲染交换后可以使用glReadPixels

int* buffer = new int[ WIDTH * HEIGHT * 3 ];
...
glReadPixels( 0, 0, WIDTH, HEIGHT, GL_BGR, GL_UNSIGNED_BYTE, buffer );

然后您可以将buffer 写入.tga 文件:

FILE   *out = fopen(tga_file, "w");
short  TGAhead[] = 0, 2, 0, 0, 0, 0, WIDTH, HEIGHT, 24;
fwrite(&TGAhead, sizeof(TGAhead), 1, out);
fwrite(buffer, 3 * WIDTH * HEIGHT, 1, out);
fclose(out);

【讨论】:

感谢您的 TGA 提示,看起来很简单。再往前走,我将需要对渲染图像进行图像处理。我正在完善来自here 的大部分代码。我试图只渲染一次并抓取纹理。【参考方案2】:

有点晚了。

在使用 FBO 时,在 fbo-&gt;release() 之前添加的以下代码会将图像保存为 PNG。

QImage fb = fbo->toImage().convertToFormat(QImage::Format_RGB32);
fb.save("/path_to_image_file/image_file.png", "PNG");

如果你想确保所有的渲染都完成了,那么你可以在之前做以下事情:

QOpenGLContext::currentContext()->functions()->glFlush();

您可能还想在 fbo 格式中添加以下内容:

fboFormat.setTextureTarget(GL_TEXTURE_2D);

【讨论】:

以上是关于将 OpenGL 渲染保存到图像文件的主要内容,如果未能解决你的问题,请参考以下文章

使用 FFMPEG 将 RGB 图像序列保存到 .mp4 时遇到问题

使用 OpenGL 将图像渲染到子类 QDeclarativeItem

OpenGL渲染纹理全白

openGL FBO 复制到纹理导致黑色/深色图像

使用 libpng 将 OpenGL 屏幕像素保存为 PNG

使用 openTK 绘图,保存到文件,显示在屏幕上