Blend RGBA-Texture using srcAlpha

I’m trying to blend a texture with alpha channel onto the framebuffer using the blend function

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

The texture is loaded using texture environment GL_MODULATE. I’m using OpenGL ES 1.1 on Android, on the DROID MOTOROLA and Nexus One phone.

This results in the dark colors where the texture has a semi-transparent alpha (ie. 0< alpha < 1). The resulting color of each pixel roughly corresponds to the alpha channel of the texture having been applied twice to the color of the textures pixel ie
Cout=Ct * At * At + Cd * (1-At), where Ct (At) is color (alpha) of the texture, and Cd is the color of the destination, and Cout is the resulting color.

If I use the blend function

gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);

I get the behaviour I expect namely that the resulting color is Cout = Ct * At + Cd * (1-At).

If I draw an RGB-texture (ie non-alpha) and sets a half opaque yellow color using glColor4f(1,1,0,.5), then the former blend function
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
correctly blends my background to my half opaque yellow, and the latter blend function gl.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
correctly draws fully opaque yellow (regardless of the background).

It appears I can get away with using the latter blend function, by applying alpha values to the color component myself when setting colors on vertices and when setting the global glColor. However it frustrates me that I cannot understand whats going on.

According to the red book chapter 9 (Texture Functions) the GL_MODULATE should make the texture map onto the incoming fragment using the function:
C = CfCt,
A = AfAt
I’m not 100% certain what ‘the incoming fragment’ means, but my understanding is that Cf and Af refer to color and alpha of the object I’m texture mapping, and is thus defined by either glColor or the color of the vertices of my object, dependent upon whether i’m using color arrays. So I expect that if I map an RGBA texture on an object with the color 1,1,1,1, and then draw this object onto the screen, then I would get the blending I mentioned in the beginning, and not the dark colors where the texture contains semi-opaque colors.

On the left the texture can be seen. The texture is fully opaque yellow in the center and gradually fades. On the right there is an opengl render example where the texture is rendered on a black,yellow,blue and white square using the blend function GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA.

My Code:
I have the following code:

@Override
public void onSurfaceChanged(GL10 gl, int w, int h) {
    this.w = w;
	this.h = h;
	gl.glDisable(GL10.GL_DEPTH_TEST);
	gl.glDisable(GL10.GL_DITHER);
    gl.glDisable(GL10.GL_LIGHTING);
    
	gl.glViewport(0, 0, w, h);
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glOrthof(0.0f, w, h, 0.0f, 0.0f, 1.0f);
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    
    gl.glEnable(GL10.GL_TEXTURE_2D);
    starTextureName = loadTexture(gl, R.drawable.yellow_alpha_squares);
    redTextureName = loadTexture(gl, R.drawable.red);
    greenTextureName = loadTexture(gl, R.drawable.green);
    blueTextureName = loadTexture(gl, R.drawable.blue);
    whiteTextureName = loadTexture(gl, R.drawable.white);
    blackTextureName = loadTexture(gl, R.drawable.black);
    yellowTextureName = loadTexture(gl, R.drawable.yellow);
    checkNoError(gl);      
}

private void checkNoError(GL10 gl) {
	int error = gl.glGetError();
    if (error != GL10.GL_NO_ERROR) {
    	throw new RuntimeException("GLError: " + error + " (" + GLU.gluErrorString(error) + "): ");
    }
}

private int loadTexture(GL10 gl, int resourceId) {
	int[] mTextureNameWorkspace = new int[1];
	gl.glGenTextures(1, mTextureNameWorkspace, 0);
	int textureName = mTextureNameWorkspace[0];
	gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName);
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);

	gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);

	Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId);
	GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
	((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES,
			new int[] { 0, bitmap.getHeight(), bitmap.getWidth(), -bitmap.getHeight() }, 0);
	return textureName;
}
public void onDrawFrameSimple(GL10 gl) {
	gl.glEnable(GL10.GL_BLEND);
	gl.glLoadIdentity();
	gl.glClearColor(0, 0, 0, 1.0f);
	gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
	
	gl.glColor4f(1, 1, 1, 1);
	gl.glBindTexture(GL10.GL_TEXTURE_2D, redTextureName);
	gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
	gl.glBindTexture(GL10.GL_TEXTURE_2D, starTextureName);
	((GL11Ext) gl).glDrawTexfOES(0, 0, 0, w/2,h/2);
}

It seems you have done everything correctly, so maybe your GL implementation have a bug.

Some suggestions :

  • try GL_REPLACE instead of GL_MODULATE, to rule out side effects of the vertex color.
  • maybe your image loader converts your image to pre-multiplied alpha (the name of GL_ONE, GL_ONE_MINUS_SRC_ALPHA blending mode) ? You can try instead to texture with an in-code array of values, to see if that makes a difference.
  • can you convert your code to desktop OpenGL, to see if if works differently ?

I’ve tried all texEnv modes. GL_REPLACE does not change the pre-multiplication behaviour, but it does remove the vertex color dependency (which I’m not interested in, because I need the vertex color to do fading and anti-alias). But indeed, is was one thing which should be checked.

Ah, awesome. Your idea number two did it. Apparently the GLUtils.texImage2D premultiplies the alpha values (or it reads premultiplied alpha values of the Bitmap class). Replacing GLUtils.texImage2D with a direct call to gl.glTexImage2D fixed the problem.

For other frustrated Android developers, I have posted a sample code in the android-developer google-group: PNG loading that doesn’t premultiply alpha?

ZBuffeR, Thanks a lot for the help solving my problem.

Alex