[LWJGL] problem with alpha texture and GL_LINEAR rendering

Hello!

I’m using lwjgl to render a 2D scene. The map is based on blocks referencing textures.

here is the init code for orthographic view and alpha channel;


      GL11.glMatrixMode(GL11.GL_PROJECTION);
      GL11.glLoadIdentity();
      GL11.glOrtho(0, W, H, 0, 1, -1);
      GL11.glMatrixMode(GL11.GL_MODELVIEW);		

      GL11.glEnable(GL11.GL_TEXTURE_2D);
      GL11.glEnable(GL11.GL_BLEND);
      GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

the textures are loaded (then all mipmap levels are generated), they have the following parameters:


    GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, w, h, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);

I tried the alternatives to LINEAR and LINEAR_MIPMAP_LINEAR modes with NEAREST but so far I see rendering artifacts when the textures are scaled (zoom in or out).

Here are some examples of what I get with linear interpolation:

http://img4.hostingpics.net/thumbs/mini_607812ex1.jpg


http://img4.hostingpics.net/thumbs/mini_692230ex2.jpg

These are some basic textures with a strong zoom. As you can see there are artifacts on the border of the bloc, a dark semi-transparent line which comes from a pixel mix with the opposite side of the texture (dark lines can also be seen at the transition between opaque and transparent zones but are less problematic). I checked that the original textures do not include those artifacts.

So the default linear pixel mixing openGL function really does not seem appropriate to just draw scaled alpha textures ??? Isn’t that weird ? Is there a solution to remove those unwanted lines ? Does it require some GLSL code or is there a standard method ? (Would the result be the same if I used a 3D projection matrix instead of an orthogonal one ?)

Thanks for reading :slight_smile:

This is caused by linear interpolation of both RGB and alpha. The problem is that your transparent pixels have a different color. Consider two texels in your texture next to each other. One has the color (0, 0, 0, 0) which is transparent so the actual color <shouldn’t> matter. The second one has the color (1, 1, 1, 1). Let’s say that the pixel you’re rendering is sampling the texture at exactly the point inbetween these two texels. With GL_LINEAR blending, you end up with an even mix between these two colors. The intuitive result you’re expecting is (1, 1, 1, 0.5) which is the color of the opaque texel (white) but with 50% transparency, but what you’re actually getting is
((0, 0, 0, 0) * 0.5 + (1, 1, 1, 1) * 0.5) = (0.5, 0.5, 0.5, 0.5)

The more transparent the color gets, the more you mix in the hidden color of the transparent texel, which is usually black. Let’s say that we’re blending this color with a white background, (1, 1, 1, 1). The result with your current blend function is then
((0.5, 0.5, 0.5, 0.5) * srcAlpha + (1, 1, 1, 1) * (1-srcAlpha))

srcAlpha is simply the alpha of the incoming color, 0.5, so the result is
((0.5, 0.5, 0.5, 0.5) * 0.5 + (1, 1, 1, 1) * (1-0.5)) = ((0.25, 0.25, 0.25, 0.25) + (0.5, 0.5, 0.5, 0.5) = (0.75, 0.75, 0.75, 0.75)

Boom, blending a “white” color onto a white background resulted in gray! Also note that we actually made the framebuffer transparent even though it was 100% opaque before we added a transparent color on top of it.

The solution is to use premultiplied alpha. The idea is to move the multiplication by srcAlpha to when the texture is loaded. When loading in your texture, simply multiply R, G and B by the alpha of the pixel and leave the alpha intact. Here’s an example of a function which takes in an int pixel from a BufferedImage and converts it to premultiplied RGBA bytes.


        public void handlePixel(ByteBuffer b, int pixel) {
            int rawAlpha = (pixel >> 24) & 0xFF; //0 - 255
            float a = rawAlpha / 255f; //0.0 - 1.0
        	
            b.put((byte) Math.round(((pixel >> 16) & 0xFF) * a));
            b.put((byte) Math.round(((pixel >> 8) & 0xFF) * a));
            b.put((byte) Math.round((pixel & 0xFF) * a));
            b.put((byte) rawAlpha);
        }

Since we’ve already multiplied the colors in our textures by the alpha, we no longer need to do so after blending anymore. We therefore change our blend function to (GL_ONE, GL_ONE_MINUS_ALPHA). What happens if we go through the above steps again, mixing a transparent texel with an opaque texel? Our premultiplication didn’t do much to our two texels. (00, 00, 00, 0) is still (0, 0, 0, 0) and (11, 11, 11, 1) is still just (1, 1, 1, 1). The premultiplication is necessary when using more interesting colors though, so don’t skip it! Anyway, after interpolation, we get the same even mix between these two texels as before:
((0, 0, 0, 0) * 0.5 + (1, 1, 1, 1) * 0.5) = (0.5, 0.5, 0.5, 0.5)

Here comes the interesting part. What happens when we blend this together with the background using the new blend function, (GL_ONE, GL_ONE_MINUS_ALPHA)?

((0.5, 0.5, 0.5, 0.5) * 1 + (1, 1, 1, 1) * (1-0.5)) = ((0.5, 0.5, 0.5, 0.5) + (0.5, 0.5, 0.5, 0.5) = (1.0, 1.0, 1.0, 1.0)

Adding white to white resulted in white! And the surface is still opaque! Wooh!

Thanks a lot!

I just tested this solution and it’s perfect to remove any artifact inside a bloc with abrupt alpha jumps !

However, I still have the problem with the blocs’ borders.
It’s obvious that when the texture of a bloc is scaled, its last line is interpolated with its first line, and its last column with its first one.
Is there anything I can do about this ?

Disable wrapping?


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

You may also want to try GL_CLAMP_TO_BORDER, which blends in (0, 0, 0, 0) at the edge. This may give a slight anti-aliasing effect, although this might be better handled by multisampling.

Yeah ! Excellent, now I know all that I needed to rule the gaming world, thanks !!! mouhahahahaha ;D

Glad to help with your quest for world domination. =P