Will Perone

Rendering to a texture in openGL is a very tricky task. There are a couple ways to do it depending on what the video card supports and both methods are extensions which makes it even more painful. The first method is a straight up openGL extension called the frame buffer object (texture buffer object) but it's supported by only newer cards, the other method is called a PBuffer and is a wgl extension.

We'll start out by creating a render to texture structure to keep track of our internal stuff
#define GL_GLEXT_PROTOTYPES 1 
#include "SDL/SDL_opengl.h"  // not needed if you are not using SDL, just include the glext.h 
#define WGL_WGLEXT_PROTOTYPES 1 
#include "wglext.h"          // windows gl extensions 

struct GLRenderToTexture
{
	union 
	{
		struct GLEXT 
		{
			GLuint        fb;				
			PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT;
			PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT;
			PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT;
			PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT;
			PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT;
		} gl;
		struct 
		{
			HDC          hdc;
			HGLRC        hGlRc;
			HDC          saveHdc;
			HGLRC        saveHglrc;
			HPBUFFERARB  hBuffer;
			PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB;
			PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
			PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB;
			PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB;
			PFNWGLQUERYPBUFFERARBPROC wglQueryPbufferARB;
			PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB;
			PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB;
			PFNWGLBINDTEXIMAGEARBPROC wglBindTexImageARB;
			PFNWGLRELEASETEXIMAGEARBPROC wglReleaseTexImageARB;
		} wgl;			
	};
	unsigned int  texture;  // the texture we're going to render to
};

GLRenderToTexture  RTT;
bool renderToTextureEXT; // keep track of whether we are in glEXT mode or not

The wglext.h is window's gl extension stuff; you can get it here.
Here's how you initialize the render to texture. The wgl version is supported by more cards but is a pain in the ass to work with. The gltexsize.x and gltexsize.y is the size of the texture to create. It must be a power of 2 in size and square. RESOLUTION_X and RESOLUTION_Y represent the internal screen resolution to use (independant of the window resolution)
RTT.gl.glGenFramebuffersEXT= (PFNGLGENFRAMEBUFFERSEXTPROC)SDL_GL_GetProcAddress("glGenFramebuffersEXT");
RTT.gl.glBindFramebufferEXT= (PFNGLBINDFRAMEBUFFEREXTPROC)SDL_GL_GetProcAddress("glBindFramebufferEXT");
RTT.gl.glFramebufferTexture2DEXT= (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
RTT.gl.glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC)SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
RTT.gl.glCheckFramebufferStatusEXT= (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC)SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");

if (RTT.gl.glGenFramebuffersEXT && 
    RTT.gl.glBindFramebufferEXT &&
    RTT.gl.glFramebufferTexture2DEXT &&
    RTT.gl.glDeleteFramebuffersEXT &&
    RTT.gl.glCheckFramebufferStatusEXT) renderToTextureEXT= true;

// create a texture to use as the backbuffer
glGenTextures(1, &RTT.texture);
glBindTexture(GL_TEXTURE_2D, RTT.texture);						
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);				
// make sure this is the same color format as the screen
glTexImage2D(GL_TEXTURE_2D, 0, 4,  gltexsize.x, gltexsize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 


if (renderToTextureEXT)	
{
	// create a backbuffer and texture
	RTT.gl.glGenFramebuffersEXT(1, &RTT.gl.fb);
	RTT.gl.glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, RTT.gl.fb);	
	RTT.gl.glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, RTT.texture, 0);
					
	GLenum status = RTT.gl.glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if (status!=GL_FRAMEBUFFER_COMPLETE_EXT) {
		renderToTextureEXT= false; // Something went wrong with the FBO approach, default to PBuffer
		// if you got GL_FRAMEBUFFER_UNSUPPORTED_EXT then you probably need to choose a different format for glTexImage2D
	}
	// point it back to normal screen drawing mode
	RTT.gl.glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
} 

if (!renderToTextureEXT)// the wgl version is a pain in the ass
{
	RTT.wgl.wglGetExtensionsStringARB= (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
	RTT.wgl.wglChoosePixelFormatARB= (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
	RTT.wgl.wglCreatePbufferARB= (PFNWGLCREATEPBUFFERARBPROC)wglGetProcAddress("wglCreatePbufferARB");
	RTT.wgl.wglGetPbufferDCARB= (PFNWGLGETPBUFFERDCARBPROC)wglGetProcAddress("wglGetPbufferDCARB");
	RTT.wgl.wglQueryPbufferARB= (PFNWGLQUERYPBUFFERARBPROC)wglGetProcAddress("wglQueryPbufferARB");
	RTT.wgl.wglDestroyPbufferARB= (PFNWGLDESTROYPBUFFERARBPROC)wglGetProcAddress("wglDestroyPbufferARB");
	RTT.wgl.wglReleasePbufferDCARB= (PFNWGLRELEASEPBUFFERDCARBPROC)wglGetProcAddress("wglReleasePbufferDCARB");
	RTT.wgl.wglBindTexImageARB= (PFNWGLBINDTEXIMAGEARBPROC)wglGetProcAddress("wglBindTexImageARB");
	RTT.wgl.wglReleaseTexImageARB= (PFNWGLRELEASETEXIMAGEARBPROC)wglGetProcAddress("wglReleaseTexImageARB");
	
	RTT.wgl.saveHdc   = wglGetCurrentDC();	
	RTT.wgl.saveHglrc = wglGetCurrentContext();		


	int     pixelFormats;
	int     intAttrs[32] ={WGL_RED_BITS_ARB,8,
                               WGL_GREEN_BITS_ARB,8,
                               WGL_BLUE_BITS_ARB,8,
                               WGL_ALPHA_BITS_ARB,8,
                               WGL_DRAW_TO_PBUFFER_ARB, GL_TRUE,
                               WGL_BIND_TO_TEXTURE_RGBA_ARB, GL_TRUE,
                               WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
                               WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
                               WGL_DOUBLE_BUFFER_ARB,GL_FALSE,
                               0}; // 0 terminate the list			
	unsigned int numFormats = 0;	
	// get an acceptable pixel format to create the PBuffer with
	if (RTT.wgl.wglChoosePixelFormatARB(RTT.wgl.saveHdc, intAttrs, NULL, 1, &pixelFormats, &numFormats)==FALSE)
	{
		char str[64];
		sprintf(str, "wglChoosePixelFormatARB returned %i", GetLastError());			
		assert(false); // GetLastError will tell us why it failed			
	}
	if (numFormats==0) // no supported formats, we need to change the parameters to something acceptable
	{
		assert(false);
	}

	//Set some p-buffer attributes so that we can use this p-buffer as a 2d texture target
	const int attributes[]= {WGL_TEXTURE_FORMAT_ARB,  WGL_TEXTURE_RGBA_ARB, // p-buffer will have RBA texture format
                        WGL_TEXTURE_TARGET_ARB, WGL_TEXTURE_2D_ARB, 0}; // Of texture target will be GL_TEXTURE_2D
	// the size of the PBuffer must be the same size as the texture
	RTT.wgl.hBuffer= RTT.wgl.wglCreatePbufferARB(RTT.wgl.saveHdc, pixelFormats, gltexsize.x, gltexsize.y, attributes);
	RTT.wgl.hdc= RTT.wgl.wglGetPbufferDCARB(RTT.wgl.hBuffer);
	RTT.wgl.hGlRc= wglCreateContext(RTT.wgl.hdc);		

	//query dimensions of created texture to make sure it was created right
	int width, height;
	RTT.wgl.wglQueryPbufferARB(RTT.wgl.hBuffer, WGL_PBUFFER_WIDTH_ARB, &width);
	RTT.wgl.wglQueryPbufferARB(RTT.wgl.hBuffer, WGL_PBUFFER_HEIGHT_ARB, &height);
	assert(width==gltexsize.x && height==gltexsize.y);
	
	// switch from the screen context to the texture context to set up the openGL stuff for that context
	wglMakeCurrent(RTT.wgl.hdc, RTT.wgl.hGlRc);
	
	// initialize all your openGL stuff the same as your main window context here, example:
	glEnable(GL_TEXTURE_2D);		      // Enable Texture Mapping
	glEnable(GL_BLEND);		
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // enable transparency
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();	
	glOrtho(0, RESOLUTION_X, RESOLUTION_Y, 0, -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	glClearColor(0,0,0,1);
	glClear(GL_COLOR_BUFFER_BIT);
	
	// You don't need to call glViewport for this context, it will use the otherviewport automatically
	
	// switch back to the screen context
	wglMakeCurrent(RTT.wgl.saveHdc, RTT.wgl.saveHglrc);
	
	// so that we can share textures between contexts
	wglShareLists(RTT.wgl.saveHglrc, RTT.wgl.hGlRc);
}
// Initialize your normal openGL stuff here, example:
glEnable(GL_TEXTURE_2D);		      // Enable Texture Mapping
glEnable(GL_BLEND);		
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // enable transparency

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, RESOLUTION_X, RESOLUTION_Y, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0, 0, windowresolutionX, windowresolutionY);	

glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);


To draw a texture to the back texture:
if (renderToTextureEXT) {
    RTT.gl.glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, RTT.gl.fb);
} else {
    // need to switch to the right context if in wgl mode
    wglMakeCurrent(RTT.wgl.hdc, RTT.wgl.hGlRc);
}
glBindTexture(GL_TEXTURE_2D, gltextureid);
// set up some vertices to render the texture here; example:
glTranslatef(x, y, 0);
glBegin(GL_QUADS);		
glTexCoord2f(0,0); glVertex2i(-surface->w/2, -surface->h/2);
glTexCoord2f(1,0); glVertex2i(surface->w/2, -surface->h/2);
glTexCoord2f(1,1); glVertex2i(surface->w/2, surface->h/2);
glTexCoord2f(0,1); glVertex2i(-surface->w/2, surface->h/2);
glEnd();
glLoadIdentity();
if (renderToTextureEXT) {
    RTT.gl.glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
} else { // switch the context back to the screen
    wglMakeCurrent(RTT.wgl.saveHdc, RTT.wgl.saveHglrc);
}

Render the FBO/PBuffer texture to the screen
glBindTexture(GL_TEXTURE_2D, RTT.texture);
if (!renderToTextureEXT) {
    RTT.wgl.wglBindTexImageARB(RTT.wgl.hBuffer, WGL_FRONT_LEFT_ARB);	
} 		
// set up some vertices to render a texture to a model; example:
glBegin(GL_QUADS);		
glTexCoord2f(0,0); glVertex2i(0,RESOLUTION_Y);
glTexCoord2f(1,0); glVertex2i(RESOLUTION_X,RESOLUTION_Y);
glTexCoord2f(1,1); glVertex2i(RESOLUTION_X,0);
glTexCoord2f(0,1); glVertex2i(0,0);
glEnd();
if (!renderToTextureEXT) {	
    RTT.wgl.wglReleaseTexImageARB(RTT.wgl.hBuffer, WGL_FRONT_LEFT_ARB);
}

And here's the cleanup function
if (renderToTextureEXT) 
{
    RTT.gl.glDeleteFramebuffersEXT(1, &RTT.gl.fb);
    glDeleteTextures(1, &RTT.texture);
} else {		
    wglDeleteContext(RTT.wgl.hGlRc);
    if (RTT.wgl.wglReleasePbufferDCARB &&
        RTT.wgl.wglDestroyPbufferARB) 
    {
        RTT.wgl.wglReleasePbufferDCARB(RTT.wgl.hBuffer, RTT.wgl.hdc);
        RTT.wgl.wglDestroyPbufferARB(RTT.wgl.hBuffer);
    }
}


If for some reason BOTH of these methods don't work then you'll have to do things the old fashioned way. This method is really slow though because it has to copy back and forth from RAM to VRAM:
  1. Generate the texture to render to with glGenTextures
  2. Render everything to the screen that you want to render to a texture
  3. Call glReadPixels to copy the screen pixels to a surface
  4. Call glBindTexture with the texture target
  5. Call glTexImage2D to copy the surface into the texture
  6. Clear the screen
  7. Render the stuff you ACTUALLY want to render on the screen
  8. Render the texture to the screen like any other texture
  9. Swap the gl buffers to display the draw buffer
For more info on Frame Buffer Objects visit the SGI doc on it.

7 Comments
Jared (http://hpalace.com) 1 0
So, where's the library? Let's get it all in a .[ch] that has Init(), BindRenderTarget(), and Fini()!
_Andrey_ 0 0
Thank you for article, but what about rendering to texture under Linux or Mac OS X without FBO?
Will 0 1
See the bullet points below the article, I don't actually have the code for that method though.
art111858111[@]gmail.com 0 0
what if to try to render to a texture assuming that cameras "looks" normal to polygons of a scene and then texturemap this polygon with texture rendered before and then repeat that stuff few times
www.maxloh.com 0 1
no dates = article fail. What does "newer graphics cards" mean.
garydarten 2012/05/08 Contact Me0 0
well dennis here is there site address ,check out there great deals , tell them gary dartin told you to ring
martinnetsims 2012/07/21 Contact Me0 0
hello there steve if you are still in need of them here is there web address
and some info , they have a wealth of knowledge , mention mart put you on

<- for private contact