1
votes

Although simple for what it does OpenGL is still confusing to me, I am beginning to learn how it works.

I am looking for a minimal example of off-screen rendering to get me started.

My application shall take a bunch of triangles and information about how to position them relative to the camera and save the rendered result to an image file. No lighting, materials or post processing for now.

I watched tutorials about creating off-screen contexts, creating an FBO, render to texture etc. I don't mind using QT as it conveniently provides OpenGL tools, windows and QImage. From what I understand, in order to be able to do image processing on the rendered image you need to set up your render target to be a texture, then use shaders and finally read the texture to an array.

Trying to put things together never landed me to a good starting point. I either get stuck with setting dependencies, getting black screen or gaze at projects that do too many things aside for what I need.

Update 1: got it sort of working.

#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;
}

The saved image has the same size as the FBO, the color corresponds to the one set in glClearColor but the triangle is not rendered. What am I missing?

2
see OpenGL Scale Single Pixel Line it sort of does 90% of what you want. However it uses on screen context ... (you do not see rendering until you swap the buffers so no need to have off screen stuff ...) ... it uses old api as the new one (render to texture) is not reliable on some Intel gfx cards due to driver bug ... glReadPixels simply reads the screen stuff into CPU side memory from there you need to encode it as image ... for the new api you can load the texture similarly to how it is stored ...Spektre
Also see simple complete GL+VAO/VBO+GLSL+shaders example in C++ in case you need to get started with the GL context and stuff (both old and new api)Spektre
Thank you for the reply. Hardware compatibility is not a concern. A render to texture example would be really helpful.canaldin
That would be FBO and render to texture but that is a lot of code without any speed bonus over glReadPixels as you need to copy the image back to CPU side anyway ...Spektre
I will need to do image processing on GPU further down the road.canaldin

2 Answers

4
votes

You can use glReadPixels after rendering and swapping:

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

Then you can write the buffer into a .tga file:

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);
0
votes

A little late.

As a FBO is being used the following code added just before the fbo->release() will save the image as a PNG.

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

If you want to make sure all the rendering is complete then you could do the following before:

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

You might also want to add the following to the fbo format:

fboFormat.setTextureTarget(GL_TEXTURE_2D);