Using Frame Buffer Objects (FBO)

From LWJGL
Jump to: navigation, search

Introduction

EXT_Framebuffer_Object (FBO) is a new extension created by the superbuffer ARB group and is a step forward in OpenGL in terms of functionality. It has the long awaited multiple render targets and a nicer, cleaner interface to rendering to texture.

Advantages over PBuffer

Pbuffers have their own context, and are thus, quite difficult to manage correctly. Along side the context switches between the PBuffer and the Window buffer which are quite expensive, make PBuffers awkward to use.


FBOs share the same context as the current window manager and therefore do not require a context switch. An FBO contains multiple inner buffers, namely the colour buffer, the stencil buffer and the depth buffer; those 3 buffers collectively create an FBO.

FrameBuffer Object API

The FBO API is simple. It creates Attachment Points on its inner buffers, it has multiple attachments (for multiple render targets) for the colour buffer, 1 attachment for the stencil buffer and 1 attachment for the depth buffer. You either attach a texture to these attachment points, or a newly created OGL object called a renderbuffer. A renderbuffer cannot be used as a texture, but it allows readback of the pixels (usefull for offscreen rendering). Renderbuffers are outside the scope of this tutorial, please see the FBO spec for more details.

Creating an FBO

Like most OpenGL objects, an FBO is identified by an int ID which you must generate, but first, you must see if the extension is supported:

boolean FBOEnabled = GLContext.getCapabilities().GL_EXT_framebuffer_object;

Now you can generate the ID:

IntBuffer buffer = ByteBuffer.allocateDirect(1*4).order(ByteOrder.nativeOrder()).asIntBuffer(); // allocate a 1 int byte buffer
EXTFramebufferObject.glGenFramebuffersEXT( buffer ); // generate 
int myFBOId = buffer.get();

Note that the ID must be positive and non-zero, since the 0 is reserved to identify the window buffer.

Attaching a Texture

Assuming you have the ID handler for the texture (that is, the texture has been created succesfully), you can attach that texture to the FBO providing you have bound the FBO, like this:

EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, myFBOId );
EXTFramebufferObject.glFramebufferTexture2DEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT,
                GL11.GL_TEXTURE_2D, myTextureID, 0);

The first call binds the FBO and makes the operations valid on the currently bound FBO, this is needed to attach a texture.


The first argument of the second call must be GL_FRAMEBUFFER_EXT, the second argument specifies where on the FBO would you like to attach the texture; in our case, we would like to attach it to the first colour attachment point. The following argument specifies what type of texture2D this is, it could be GL_TEXTURE_2D or GL_TEXTURE_RECTANGLE_ARB from the ARB_Texture_Rectangle extension. The 4th argument is the texture ID handler and the final argument is the mipmap level on that texture you would like to render to; in most uses, its 0, but it can be any mipmap level.

Completeness check

Once you have attached your textures/renderbuffers to the FBO, you have to check whether that FBO is “complete”. A completeness check simply checks for any anomolies like an unsupported texture format, each texture having different dimensions…etc. For a full list, please see the specifications.

The basic code for the compleness check is as follows:

int framebuffer = EXTFramebufferObject.glCheckFramebufferStatusEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT ); 
switch ( framebuffer ) {
	case EXTFramebufferObject.GL_FRAMEBUFFER_COMPLETE_EXT:
		break;
	case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
		throw new RuntimeException( "FrameBuffer: " + myFBOId
				+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT exception" );
	case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
		throw new RuntimeException( "FrameBuffer: " + myFBOId
				+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT exception" );
	case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
		throw new RuntimeException( "FrameBuffer: " + myFBOId
				+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT exception" );
	case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
		throw new RuntimeException( "FrameBuffer: " + myFBOId
				+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT exception" );
	case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
		throw new RuntimeException( "FrameBuffer: " + myFBOId
				+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT exception" );
	case EXTFramebufferObject.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
		throw new RuntimeException( "FrameBuffer: " + myFBOId
				+ ", has caused a GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT exception" );
	default:
		throw new RuntimeException( "Unexpected reply from glCheckFramebufferStatusEXT: " + framebuffer );
}

Note that you must bind the FBO before doing the completeness check; typically speaking, you do this after altering the FBO with attachments as the completeness isn't altered when rendering.

Binding and rendering

Binding an FBO is much like binding a texture:

EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, myFBOId );

After binding the FBO, a few things must be taken care of. * An FBO contains its own viewport, and thus, a viewport must be set. * Calling glClear clears the current bound FBO.


Therefore, the bind code becomes:

EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, myFBOId );
GL11.glPushAttrib(GL11.GL_VIEWPORT_BIT);
GL11.glViewport( 0, 0, textureWidth, textureHeight );
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

Now we can render anything in our heart's desire, and due to the OpenGL tradition, a Quad we shall render:

GL11.glTranslatef(0, 0, -6f);
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0, 0, 0);
GL11.glVertex3f(2, 0, 0);
GL11.glVertex3f(2, 2, 0);
GL11.glVertex3f(0, 2, 0);
GL11.glEnd();

Note the vertex values are dependant on the perspective and the modelview matrix, so set them accordingly. Now we can unbind the FBO:

EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
GL11.glPopAttrib();

And viola, we have a quad rendered to texture. Give yourself a big pat on the back, and possible dance around near the computer. You can now use this texture anywhere you like.

A few notes

If you are only rendering to depth and/or stencil, you must set the draw and read buffers to GL_NONE, otherwise, the FBO isn't complete and a RuntimeException will be thrown in the completeness check:

GL11.glDrawBuffer(GL11.GL_NONE);
GL11.glReadBuffer(GL11.GL_NONE);

Conclusion

Framebuffer Objects are a neat way to render to texture, are faily fast and their API is alot nicer than what we have before. Enjoy your newly found knowledge.