title: Save OpenGL rendering to an image file
body: <p>Although simple for what it does OpenGL is still confusing to me, I am beginning to learn how it works. </p>
  
  <p>I am looking for a minimal example of off-screen rendering to get me started.</p>
  
  <p>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. </p>
  
  <p>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.</p>
  
  <p>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.</p>
  
  <p>Update 1:
  got it sort of working.</p>
  
  <pre><code>#include &lt;QtGui/QGuiApplication&gt;
  #include &lt;QtGui/QSurfaceFormat&gt;
  #include &lt;QtGui/QOffscreenSurface&gt;
  #include &lt;QtGui/QOpenGLFunctions_4_3_Core&gt;
  #include &lt;QtGui/QOpenGLFramebufferObject&gt;
  #include &lt;QtGui/QOpenGLShaderProgram&gt;
  #include &lt;QDebug&gt;
  #include &lt;QImage&gt;
  #include &lt;QOpenGLBuffer&gt;
  
  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(&amp;surface);
  
     QOpenGLFunctions_4_3_Core f;
     if(!f.initializeOpenGLFunctions()) return -3;
  
     qDebug() &lt;&lt; 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;
  }
  </code></pre>
  
  <p>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?</p>