Search code examples
c++openglpngfbostb-image

PNG saved from OpenGL framebuffer using stbi_write_png is shifted to the right


I have been trying to solve this visual bug for a few days without any success, so I'm asking this question to see if somebody can help me understand what is happening.

First I will describe the problem without any code, and then I will present some code. Here is the situation:

  • My OpenGL application renders this image to a multisample framebuffer:

    enter image description here

  • I then blit that multisample framebuffer into a regular framebuffer (not a multisample one).

  • I then read the RGB data from that regular framebuffer into an array of unsigned bytes using glReadPixels.

  • Finally, I call stbi_write_png with the array of unsigned bytes. This is the result:

    enter image description here

To me it looks like the first line of bytes is shifted to the right, which causes all the other lines to be shifted, resulting in a diagonal shape.

Here is my code:

  • To create the multisample framebuffer:
   int width        = 450;
   int height       = 450;
   int numOfSamples = 1;

   // Create the multisample framebuffer

   glGenFramebuffers(1, &mMultisampleFBO);

   glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFBO);

   // Create a multisample texture and use it as a color attachment
   glGenTextures(1, &mMultisampleTexture);

   glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mMultisampleTexture);
   glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, numOfSamples, GL_RGB, width, height, GL_TRUE);
   glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);

   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, mMultisampleTexture, 0);

   // Create a multisample renderbuffer object and use it as a depth attachment
   glGenRenderbuffers(1, &mMultisampleRBO);

   glBindRenderbuffer(GL_RENDERBUFFER, mMultisampleRBO);
   glRenderbufferStorageMultisample(GL_RENDERBUFFER, numOfSamples, GL_DEPTH_COMPONENT, width, height);
   glBindRenderbuffer(GL_RENDERBUFFER, 0);

   glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, mMultisampleRBO);
  • To create the regular framebuffer:
   // Create the regular framebuffer

   glGenFramebuffers(1, &mRegularFBO);

   glBindFramebuffer(GL_FRAMEBUFFER, mRegularFBO);

   // Create a texture and use it as a color attachment
   glGenTextures(1, &mRegularTexture);

   glBindTexture(GL_TEXTURE_2D, mRegularTexture);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
   glBindTexture(GL_TEXTURE_2D, 0);

   glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mRegularTexture, 0);
  • Note that both framebuffers are reported as complete.

  • To blit the multisample framebuffer into the regular one, read from the regular one and write the PNG image:

   int width  = 450;
   int height = 450;
   static GLubyte* data = new GLubyte[3 * 450 * 450];
   memset(data, 0, 3 * width * height);

   // Blit the multisample framebuffer into the regular framebuffer
   glBindFramebuffer(GL_READ_FRAMEBUFFER, mMultisampleFBO);
   glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRegularFBO);
   glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);

   // Read from the regular framebuffer into the data array
   glBindFramebuffer(GL_FRAMEBUFFER, mRegularFBO);
   glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);

   // Write the PNG image
   int numOfComponents = 3; // RGB
   int strideInBytes   = width * 3;
   stbi_write_png(imgName.c_str(), width, height, 3, data, width * 3);
  • Note that glGetError reports no errors.

I haven't been able to figure out what is wrong. Thank you for any help!


Solution

  • The issue is cause be the alignment of a row, when the image is read by glReadPixels. By default the alignment of the start of each row of the image is assumed to be 4.
    Since the width of the image is 450, which is not divisible by 4 (450/4 = 112.5) and the format is RGB (3 bytes), the alignment has to be changed.

    Change the GL_PACK_ALIGNMENT (glPixelStore) before reading the image data:

    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);