premultiplied alpha

Started by
3 comments, last by ehmdjii 17 years, 7 months ago
hello, i hear a lot that a technique called "premultiplied alpha" can be used to get rid of the dark halos when using alpha-textures and the standard blend mode of: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); unfortunately i dont quite understand it and i also havent found an openGL implementation of it, so i would be glad if you could give me any pointers into premultiplied alpha. thanks a lot!
Advertisement
Blending with GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA means that the resulting color would be
C' := CB * A + CF * ( 1 - A )
where C means a color vector, index B means the background, index F means the incoming fragment, and A means the normalized (i.e. A in [0,1]) alpha. This method works fine for all values being in the allowed ranges.

Now, you can pre-multiply the fragment with 1-A
CF' := CF * ( 1 - A )
and then using GL_SRC_ALPHA, GL_ONE to yield in
C' := CB * A + CF'
what is obviously the same as above.

IMO, if the texture used for the fragments is properly prepared, there is no problem with alpha blending at all. We are currently working on a sprite system for our game engine, and we have had the problem of white peaks on the border of sprite shapes in the case of extra alpha channels (not in the case of the standard alpha channel; we're speaking of PhotoShop here that allows several alpha channels). After looking at the process we saw that the peaks arised from how the artists have generated the alpha already in PhotoShop. They arised from the fact that PhotoShop has blended the image already to achieve antialiasing, and the border color (black) was different from the background color (white) when the additional alpha channel was generated, so that too much of the background was taken into account (notice that we don't want _any_ background when exporting the image). Dark peaks will occur in the inverse case. The problem disappeared as soon as the background color and border color were equal when the alpha was generated.

Notice that using PhotoShop's standard alpha channel only doesn't show this problem. (This is also sometimes called using pre-multiplied alpha, as our artists have stated.) This is because it solely works like the formula given above.

So, how is the process you use to generate the textures?
thank you very much for your detailed explanaition.

how can i implement the formulas you gave using openGL?


for the textures i also use photoshop and save them as PNGs, which dont seem to have a "real" alpha channel. at least it doesnt show up in photoshop.

thanks!
The formulas are simply achieved by using the blending factors I've written down. If you have a normal (i.e. not pre-multiplied image) then use
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
and if you have a pre-multiplied image then use (as I've described above)
glBlendFunc(GL_SRC_ALPHA, GL_ONE);

AFAIK PNG can have a normal alpha channel. Unfortunately, ATM we cannot import PNG but PhotoShop directly in our engine, so I can't just do a quick test with PNG.

In our engine we currently do the following: We allocate an uint8_t array, load the red, green, blue, and alpha channels of PhotoShop as they are (okay, they are re-arranged since PhotoShop stores its images banked and not interleaved), push that data into a
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
and use the aforementioned
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
with it. Until now we've no problems found with it (but perhaps we don't have looked at the right places yet ;)


EDIT: If of interest, here are the belonging code snippets. This routine prepares the texture:
voidGLRenderer::activateTexture(const ImageTexture& texture,uint32_t unit) {	::glActiveTexture(GL_TEXTURE0+unit);	// generating an OpenGL texture object if necessary ... else ...	const uint32_t textureID = texture.textureID();	if(_arrayOfTexObjects[textureID]==0) {		const GLuint width = texture.image()->width();		const GLuint height = texture.image()->height();		const void* data = texture.image()->backingPixelMap().buffer();		::glGenTextures(1,&_arrayOfTexObjects[textureID]);		::glPixelStorei(GL_UNPACK_ALIGNMENT,1);		::glBindTexture(GL_TEXTURE_2D,_arrayOfTexObjects[textureID]);		::glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);		::glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);		// determining mode		// TODO rudimentary implementation yet		GLenum mode = (*texture.image())[Image::Channel::ALPHA] ? GL_RGBA : GL_RGB;		// generating texture		::glTexImage2D(GL_TEXTURE_2D,0,mode,width,height,0,mode,GL_UNSIGNED_BYTE,data);	} else {		::glBindTexture(GL_TEXTURE_2D,_arrayOfTexObjects[textureID]);	}	// enabling texturing	::glEnable(GL_TEXTURE_2D);}


And this routine renders the sprite onto a quad:
voidGLRenderer::renderRectangle(const Region2s& region,const ImageTexture& texture,const Region2s& imageRegion) {	activateTexture(texture);	::glColor4f(1.0f,1.0f,1.0f,1.0f);	float u0 = float(imageRegion.minimum0())/texture.image()->width();	float u1 = float(imageRegion.maximum0())/texture.image()->width();	float v0 = float(imageRegion.minimum1())/texture.image()->height();	float v1 = float(imageRegion.maximum1())/texture.image()->height();	if(texture.image()->rightToLeft()) {		float temp;		temp = u0, u0 = u1, u1 = temp;	}	if(!texture.image()->bottomToTop()) {		float temp;		temp = v0, v0 = v1, v1 = temp;	}	const bool withBlending = (*texture.image())[Image::Channel::ALPHA];	if(withBlending) {		::glEnable(GL_BLEND);		::glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);	}	::glBegin(GL_QUADS);	::glTexCoord2f(u0,v0);	::glVertex2sv(region.minimum());	::glTexCoord2f(u0,v1);	::glVertex2s(region.minimum0(),region.maximum1());	::glTexCoord2f(u1,v1);	::glVertex2sv(region.maximum());	::glTexCoord2f(u1,v0);	::glVertex2s(region.maximum0(),region.minimum1());	::glEnd();	::glDisable(GL_TEXTURE_2D);	if(withBlending) {		::glDisable(GL_BLEND);	}}
thanks for your help!

i thought that when doing premultiplied alpha, you have to multiply the R, G and B channels with the alpha value to get the correct results when doing blending with glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).

is that correct?

This topic is closed to new replies.

Advertisement