Transparent background in OpenGL

Log-in or register.

Transparent background in OpenGL

Published by on August 19th 2013.

One of the things I plan to improve in future versions of RW icon editor is the 3D object to image conversion. Right now, I am only using OpenGL while editing. In the future, it may also be used for the final conversion from a 3D object to an image or an icon. Icons typically have a transparent background, so let me say a few words about what I had to do to perform offscreen rendering in OpenGL with transparent background.

Creating an OpenGL context

First, I had to abandon the old way for creating an OpenGL context for offline rendering. Using a bitmap and a PFD_DRAW_TO_BITMAP flag when calling SetPixelFormat turns out to be the wrong way. It seems like if one does this, hardware rendering may be disabled. Also, it did not seem to offer a pixel format with an alpha channel, which is necessary when transparent background is needed.

So, I had to create a dummy, invisible window and then attach an OpenGL context to that window. The window will never be visible and the selected pixel format is not important.

Creating a frame buffer

The target of the offscreen rendering will be a custom frame buffer with R, G, B colors and an alpha channels. A frame buffer is created in three steps.

First, a render buffer containing all required channels is created.

GLuint renderbuf;
glGenRenderbuffers(1, renderbuf);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, canvasWidth, canvasHeight);

The GL_RGBA8 format contains a 8-bit alpha channel. In theory, a graphic card may not support it, but it is quite unlikely. If you want to use a higher color depth or floating point format, you should first check if that one is actually supported.

Now, let's create a depth buffer.

GLuint depthbuf;
glGenRenderbuffers(1, &depthbuf);
glBindRenderbuffer(GL_RENDERBUFFER, depthbuf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, canvasWidth, canvasHeight);

Finally, the render buffers are attached to a frame buffer.

GLuint framebuf;
glGenFramebuffers(1, &framebuf);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuf);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuf);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthbuf);

Rendering to a transparent frame buffer

The frame buffer must be activated before rendering.

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuf);

The frame buffer should be cleared to the transparent color.

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

When rendering a semitransparent primitive, blending must be enabled like this:

glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

The above command will instruct OpenGL to do the following (Src refers to the currently rendered primitive, while Dst represents the values in the frame buffer):

DstColor = DstColor * (1-SrcAlpha) + SrcColor * SrcAlpha;
DstAlpha = DstAlpha * (1-SrcAlpha) + SrcAlpha;

This blending equations will produce a premultiplied RGBA color values in the frame buffer. If a normal color has values (R, G, B, A), the same premultiplied color will be (R*A, G*A, B*A, A). Premultiplied colors are better when a lot of blending is being done, because the equations are much simpler. OpenGL is actually unable to perform a blending with non-premultiplied values.

Reading the content of the frame buffer

After the rendering is done, the image needs to be moved from the graphic adapter to the main memory. This can be done with the glReadPixels function.

BYTE* pBuffer = new BYTE[canvasWidth*canvasHeight*4];
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuf);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0, 0, canvasWidth, canvasHeight, GL_BGRA, GL_UNSIGNED_BYTE, pBuffer);

After the data is read, it must be converted from premultiplied to normal format. This means dividing the R, G, and B channels with the A value (if it is not 0). This can be done on the main processor, but it would be also possible to do on the graphic adapter - more on that perhaps later.

if (pixel->a != 0)
{
  pixel->r = pixel->r*255/pixel->a;
  pixel->g = pixel->g*255/pixel->a;
  pixel->b = pixel->b*255/pixel->a;
}

This can be further optimized:

if (BYTE(pixel->a+1) < 2)
{
  ULONG mul = 0xff000000/pixel->a
  pixel->r = (pixel->r*mul+0x800000)>>24;
  pixel->g = (pixel->g*mul)>>24;
  pixel->b = (pixel->b*mul)>>24;
}

Things to consider

Then working with premultiplied colors, the color accuracy is lowered. Because the color channels are multiplied by the alpha channel, the effective ranges for the color channels are lowered. If color accuracy is important, consider using more than 8 bits for each channel.

The described method of rendering on a transparent background works nicely with multisampled frame buffers. (At least on my quite old NVIDIA card.)

Vlasta's blog RSS feed

Recent comments

user icon jojois74 registered user on August 19th 2013

I have never worked with open GL and so can not even pretend that I understand this....

user icon Vlasta site administrator on August 20th 2013

Don't worry about it, this is just a summary of things one needs to do when they want to render 3D objects on a transparent background. This is a pretty special scenario and there does not seem to be any guide on the internet.

user icon Anonymous
What about ICL files?
Select background
Vista & Win 7 icons
I wish there were...