Сохранение рендеринга OpenGL в файл изображения

Несмотря на простоту того, что он делает, 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, но треугольник не визуализируется. Что мне не хватает?


person canaldin    schedule 08.12.2019    source источник
comment
см. OpenGL Scale Single Pixel Line, он делает 90% того, что вы хотите. Однако он использует экранный контекст... (вы не видите рендеринг, пока не поменяете местами буферы, поэтому нет необходимости иметь что-то вне экрана...)... он использует старый API, поскольку новый (рендеринг в текстуру) не надежный на некоторых картах Intel gfx из-за ошибки драйвера ... glReadPixels просто считывает содержимое экрана в память процессора, оттуда вам нужно закодировать его как изображение ... для нового API вы можете загрузить текстуру аналогично тому, как она хранится ...   -  person Spektre    schedule 08.12.2019
comment
Также см. простой полный пример GL+VAO/VBO+GLSL+shaders в C++ на случай, если вам нужно начать работу с GL контекст и прочее (как старый, так и новый API)   -  person Spektre    schedule 08.12.2019
comment
Спасибо за ответ. Аппаратная совместимость не имеет значения. Пример рендеринга в текстуру был бы очень полезен.   -  person canaldin    schedule 09.12.2019
comment
Это будет FBO и визуализация в текстуру, но это много кода без какого-либо бонуса к скорости по сравнению с glReadPixels, поскольку вам нужно скопировать изображение обратно на сторону процессора в любом случае...   -  person Spektre    schedule 09.12.2019
comment
В дальнейшем мне нужно будет выполнять обработку изображений на графическом процессоре.   -  person canaldin    schedule 09.12.2019
comment
тогда FBO - это способ, поскольку он устраняет медленную передачу между памятью CPU и GPU ... вы делаете это только для конечного результата для сохранения. Однако для эффективной обработки изображений требуется многопроходный рендеринг, поскольку вы не можете читать и записывать в одну и ту же цель за один проход...   -  person Spektre    schedule 09.12.2019


Ответы (2)


Вы можете использовать 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);
person SurvivalMachine    schedule 08.12.2019
comment
Спасибо за совет TGA, выглядит довольно просто. В дальнейшем мне нужно будет выполнить обработку визуализированного изображения. Я беру большую часть кода из здесь. Я пытаюсь сделать только один раз и захватить текстуру. - person canaldin; 09.12.2019

Немного поздно.

Поскольку используется FBO, следующий код, добавленный непосредственно перед fbo->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);
person Damian Dixon    schedule 08.06.2021