PDA

View Full Version : Odd artifacts when rendering to FBO



Duncan Champney
03-21-2008, 03:48 PM
Sigh...

My app is now coded to save the contents of a 3D view, or a selected portion of it, to a disk file.

I generate a polygon mesh to a display list using triangle strips, then render it with a perspective projection with GL_LIGHTING, GL_DEPTH_TEST, GL_COLOR_MATERIAL and GL_SMOOTH all turned on.

If FBOs are supported at runtime, the user is given an option to create an image that's bigger than the view on the screen (and even bigger than the entire screen.)

If the user requests to save an image bigger than the current view, I create an FBO object and render to that.

I then copy either the contents of the front colorbuffer or the FBO into an image in main memory using glReadPixels.

All this works beautifully, except that my images are strangely and subtly distorted if the output goes through an FBO.

I added a "force FBOs" flag for testing so I could generate exactly the same image directly from the front color buffer or through an FBO.

Here is how the image SHOULD look: (created without FBOs, by doing a glReadPixels directly from the front buffer)

http://www.pbase.com/image/94500810/original.jpg

And here's how it looks when the output is rendered to an FBO:
http://www.pbase.com/duncanc/image/94500809/original.jpg

Notice how the ridges of my 3D object (a fractal) are fairly uniformly bumpy from left to right across the middle of the first image.

On the image which is rendered to an FBO, the ridges start out looking too smooth on the left of the image, and have odd artifacts on the right side.

When I get ready to save my image to disk I've just rendered it to the back color buffer and then copied it to the front color buffer. Thus if I'm doing glReadPixels from my regular color buffer, I don't have to do anything.

If I'm re-rendering to an FBO, I generate the FBO, bind it, generate a renderbuffer, bind that, allocate storage in the renderbuffer, and then just do a glClear and render my mesh object. I don't set up my projection, specify my lighting or shading options, or any of that, because it is already set up.

Do I need to specify all my options again before drawing to the FBO?

Here's the code I use to set up my FBO for drawing:



//Set up a FBO with one renderbuffer attachment
glGenFramebuffersEXT( 1,
&framebuffer); //Generate a new framebuffer id
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT,
framebuffer); //Bind to it
glGenRenderbuffersEXT( 1, &renderbuffer); //Generate a new renderbuffer id
glBindRenderbufferEXT( GL_RENDERBUFFER_EXT,
renderbuffer); //Bind the renderbuffer
glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT,
GL_RGB,
save_width,
save_height); //Create storage for the renderbuffer object
glFramebufferRenderbufferEXT(
GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT,
renderbuffer); //Install the renderbuffer in the framebuffer?
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); //Make sure the framebuffer is "complete" (ready for drawing)
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{ //Error. clean up before returning
//unbind the frame buffer.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glDeleteRenderbuffersEXT(1, &renderbuffer);
NSLog(@"Error. Frame buffer not complete.");
return nil;
}


if (!save_selection)
glViewport (0, 0, openGLRect.size.width, openGLRect.size.height);
else
{// shift and scale our drawing
scale_factor = ((float)save_width) / view_selection_rect.size.width;
glViewport (-view_selection_rect.origin.x * scale_factor,
-view_selection_rect.origin.y * scale_factor,
camera.viewWidth * scale_factor,
camera.viewHeight * scale_factor );
}

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

[theMeshObject drawMesh];


glPixelStorei(GL_PACK_ALIGNMENT, 8);
glReadPixels( openGLRect.origin.x,
openGLRect.origin.y,
openGLRect.size.width,
openGLRect.size.height,
GL_RGB,
GL_UNSIGNED_BYTE,
theRepData);


Note that the code that scales the viewport isn't changing the scale at all in my simple test. I can get the odd distortion even if I render the whole viewport without any scaling.

thinks
03-22-2008, 05:31 AM
Have you tried with glPixelStorei(GL_PACK_ALIGNMENT, 1)? This is what I use for similar tasks and I have not had the problems that you describe.

T

V-man
03-22-2008, 07:58 AM
You are not creating a depth render buffer for your FBO?

Duncan Champney
03-22-2008, 08:37 AM
Have you tried with glPixelStorei(GL_PACK_ALIGNMENT, 1)? This is what I use for similar tasks and I have not had the problems that you describe.

T

Thinks,

I had to use glPixelStorei(GL_PACK_ALIGNMENT, 8) for platform-specific reasons. After drawing to my renderbuffer, I use glReadPixels() to transfer my pixel data from the renderbuffer into an image structure in main memory. That image structure (a Macintosh NSBitmapImageRep) has each row aligned on an 8-byte boundary for performance reasons. Actually, the system was aligning each row of pixels on a <u>32 byte</u> boundary until I specifically told it to use 8-byte boundaries instead.

I'm quite confident that this part of the code is correct. If you get your pack alignment incorrect, the resulting image comes out looking like a video image where the sync signal is off. It's torn into diagonal strips or otherwise mangled. The problem I'm describing in this thread is much more subtle.

Duncan Champney
03-22-2008, 08:46 AM
You are not creating a depth render buffer for your FBO?

V-man,

I did not know that I had to. What setup do I need to do for an FBO, and what does it inherit from the current context/framebuffer?

My main (on-screen) renderbuffer, is set up uses GL_DEPTH_TEST. It's pixelformat is set up like this: (sorry, Mac-specific code here: )



NSOpenGLPixelFormatAttribute attributes [] = {
NSOpenGLPFAWindow,
NSOpenGLPFADoubleBuffer, // double buffered
NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)16, // 16 bit depth buffer
(NSOpenGLPixelFormatAttribute)nil
};

I'm an OpenGL noob, so I wouldn't doubt that I'm missing something.

The strange thing is that my images are close to correct. The changes are pretty subtle.

ZbuffeR
03-22-2008, 09:43 AM
I think V-Man is right, the image artifacts can indeed be explained by a lack of a working depth testing : painter algorithm almost done right, artefacts should depends on the drawing order of your grid.

Duncan Champney
03-22-2008, 10:14 AM
Ok,

So i need to add a depth buffer to my FBO. How do I go about this? Do I create another named renderbuffer object, allocate storage to it, then attach it to my FBO with a call like this?



glFramebufferRenderbufferEXT(
GL_FRAMEBUFFER_EXT,
GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT,
renderbuffer);


In my research I haven't found any mention of needing to add a depth buffer to my FBO for off-screen rendering, nor any documentation on HOW to do it, so any help you can offer would be greatly appreciated.

arekkusu
03-22-2008, 10:49 AM
Did your research include reading the instructions (http://www.opengl.org/registry/specs/EXT/framebuffer_object.txt) or the sample code (http://developer.apple.com/samplecode/FBOBunnies/index.html)?

Duncan Champney
03-22-2008, 01:00 PM
Folks,

Adding a depth buffer to my FBO did indeed fix the problem. Thanks to ZbuffeR and V-man for the suggestions, and thanks to arekkusu for the links.

arekkusu, I only found this site last week, and don't know my way around it very well yet.

I've been relying mostly on 3 different print OpenGL references, the online documentation included with my development system, and google searches. I clearly need to learn my way around this site better.

I wonder why none of the documentation for off-screen rendering to FBOs/PBOs tells you to add a depth buffer to the FBO before rendering? It seems like it should be part of the normal setup. Neither the OpenGL SuperBible nor the book "OpenGL Programming on Mac OS X" included depth buffers in their sections on using FBOs with PBOs for off-screen rendering.

For fellow newbies like me, the resolution is this:

If you need to draw to an offscreen framebuffer (probably because you need to render larger than the screen) you need to allocate a framebuffer object and 2 renderbuffer objects. One renderbuffer object is used to hold the color data for the plot, and the other is used as a depth buffer. You have to allocate storage for both the offscreen color renderbuffer and the one used as the depth buffer, then make calls to bind the objects to the framebuffer object.

Here is the code I came up with, with some things taken out for clarity:



//Set up an FBO with one renderbuffer attachment
GLuint framebuffer, renderbuffers[2];
glGenFramebuffersEXT( 1,
&amp;framebuffer); //Generate a new framebuffer id
glBindFramebufferEXT( GL_FRAMEBUFFER_EXT,
framebuffer); //Bind to it
glGenRenderbuffersEXT( 2, renderbuffers); //Generate 2 new renderbuffer ids
//one for the color data, and one for a depth buffer
glBindRenderbufferEXT( GL_RENDERBUFFER_EXT,
renderbuffers[0]); //Bind the first renderbuffer
glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT,
GL_RGB,
save_width,
save_height); //Create storage for the renderbuffer object
glFramebufferRenderbufferEXT(
GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT,
renderbuffers[0]); //Install the renderbuffer in the framebuffer?

//Now bind a depth buffer to the FBO
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffers[1]);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, save_width, save_height);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, renderbuffers[1]);


status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); //Make sure the framebuffer is "complete" (ready for drawing)
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{ //Error. clean up before returning
//unbind the frame buffer.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glDeleteRenderbuffersEXT(2, renderbuffers);
NSLog(@"Error. Frame buffer not complete.");
return nil;
}

glViewport (0, 0, openGLRect.size.width, openGLRect.size.height);

// code to draw content to the renderbuffer
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
[theMeshObject drawMesh];

glPixelStorei(GL_PACK_ALIGNMENT, 8); //Our target "NSBitMapImageRep was set up with rows on 8-byte boundaries.
//This will be different on different on different platforms
glReadPixels( openGLRect.origin.x,
openGLRect.origin.y,
openGLRect.size.width,
openGLRect.size.height,
GL_RGB,
GL_UNSIGNED_BYTE,
theRepData);

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); //Now unbind the framebuffer memcopy

glDeleteRenderbuffersEXT(2, renderbuffers); //delete both renderbuffer objects
glViewport (0, 0, camera.viewWidth, camera.viewHeight); //restore the viewport back to it's original value.

ZbuffeR
03-22-2008, 01:11 PM
Good to know you solved your problem.
Nice image by the way :-)

Duncan Champney
03-22-2008, 02:30 PM
Good to know you solved your problem.
Nice image by the way :-)

ZbuffeR,

Thanks.

That is a small area cropped from a larger image.

All the code I've been writing lately has been to support saving my 3D views to disk. Users can either save the whole view, at current size or scaled, or a selected region, also optionally scaled. Using FBOs lets me generate big images, suitable for printing at high resolution.

Swirly Grape Julia 3D huge (http://www.pbase.com/image/94537709)

Even after adding a depth buffer to my FBO I'm still getting some odd artifacts on the back right side of my images, especially when I tilt my height map at an angle away from the camera, like in this image. These artifacts are visible on screen as well as in the saved files, so I know it's not specific to my FBO code. It's odd - the artifacts are most pronounced in the upper right of my viewport. If I drag my height map object down to the left of the viewport, the artifacts all but go away.

I wonder if I've got a problem with the normal calculations for my mesh.

Do the symptoms I describe ring any bells for you?

-NiCo-
03-22-2008, 02:41 PM
Wow, that's a really nice image!

Did you calculate your near and far plane so that the distance between the near and far plane is minimal but still encapsulates the entire object to get the best z-buffer accuracy?

Also, may I suggest using a multisample framebuffer.

Great work :)

ZbuffeR
03-22-2008, 04:23 PM
Even after adding a depth buffer to my FBO I'm still getting some odd artifacts on the back right side of my images, especially when I tilt my height map at an angle away from the camera, like in this image. These artifacts are visible on screen as well as in the saved files, so I know it's not specific to my FBO code. It's odd - the artifacts are most pronounced in the upper right of my viewport. If I drag my height map object down to the left of the viewport, the artifacts all but go away.
It can be zbuffer imprecision showing up, as said above tightening your znear/zfar will improve it.
You can also try to render the grid from back to front, instead of left to right. Like with the "painter's algorithm", you may even drop the depth test.
I am not sure to be very clear. The idea is that a grid can be drawn along 4 major directions : row by row or column by column, and along increasing or decreasing indices. You can choose select this direction depending on the viewing direction. As your grid is very dense, it will have a performance impact to traverse the grid in a different direction, but apparently you are more interested in correctness than in pure speed.

Duncan Champney
03-22-2008, 06:44 PM
ZbuffeR,

Tightening up near and far in my frustum helped quite a bit. Yet again you and NiCo came through. Thank you!

Duncan Champney
03-22-2008, 06:54 PM
ZbuffeR,

(What an appropriate conversation, given your handle)

I reposted my sample image under the same link, with my frustum tightened up. Take a look. It's much improved.

Here's the link so you don't have to go digging for it

Swirly Grape Julia huge (http://www.pbase.com/image/94537709/original)