Bleeding Color in Linear Filtered Textures

Started by
14 comments, last by LeGreg 17 years, 11 months ago
Well arguably he did say...

"I want to be able to have an alpha gradient in images, rather than just all-or-nothing transparency"

..and alpha-testing does result in a sharp cut-off and not the smooth gradient alpha-blending provides [smile]

Of course, a picture paints a thousand words ...so if the answer hasn't already been provided, then screenshots of the problem are in order [wink]

Regards,
ViLiO
Richard 'ViLiO' Thomasv.net | Twitter | YouTube
Advertisement
Just out of interest, does anyone know why OpenGL doesn't have this problem with bilinear filtering when the border pixels are black? I know the Direct3D algo is using a box filter.

Quote:Original post by JohnnyCasil
That isn't the problem. The problem is he is using linear filter on the texture. This causes the textures colors to blend together in order to get rid of the pixelation. Since he is using alpha-blending, the edges of the sprite that touch the transparent part of the texture are blending.


Yep, right on the money.

I don't have the code in front of me right now, so I can't try alpha testing just yet. I wasn't aware that the alpha-tested pixels are totally thrown out before the texture filtering stage.

I'll try it out and get back with how it worked.
Quote:Original post by ViLiO
..and alpha-testing does result in a sharp cut-off and not the smooth gradient alpha-blending provides [smile]


You are right. I forgot to clarify that alpha-testing will only solve the problem of the edge pixels, but if you set the alpha test to only throw out small alpha values, blending should still be performed with the rest of the data.

Quote:Original post by JonWoyame
I don't have the code in front of me right now, so I can't try alpha testing just yet. I wasn't aware that the alpha-tested pixels are totally thrown out before the texture filtering stage.


I'm pretty sure it will work. I've personally done it before, but this is all going off the top of my head. I don't remember if there is more to it than this or not, but I am pretty sure that the data will get thrown out before hand.
Yes, it will work. The pixels that pass the alpha test will be linear filtered and the pixels that fail will be transparent.

Does OpenGL have an alpha test state? It could be that it doesn't have this specific state and instead always tests if the alpha value is 0 before performing the filtering operation.
Linear filtering will work fine, if you're using pre-multiplied alpha and additive blending. My guess is that you don't (i e, you blend with SRC_ALPHA,ONE_MINUS_SRC_ALPHA).
enum Bool { True, False, FileNotFound };
Premultiplied is the way to go.

If you don't know how, with a little bit of maths you can fall back on your feet.

What you really want is your blending to be something like that :

final color =   w1 * (alpha1 * color1 + (1 - alpha1) * dest) + w2 * (alpha2 * color2 + (1 - alpha2) * dest)+ w3 * (alpha3 * color3 + (1 - alpha3) * dest)+ w4 * (alpha4 * color4 + (1 - alpha4) * dest);


wi being the bilinear weight of the ith texel (sum(wi) = 1). alphai being 0 or 1 depending on where you stand. dest being the destination color. There is only one dest color per pixel obviously.

The goal of the formula above would be that only the texel with alpha != 0 contribute to the final color. So that there is no bleeding black in your picture. Of course the above formula is too complicated for fixed function hardware and even a pixel shader would be horribly slow if we tried to implement it as such.

Now how can we simplify that to make it work on every hardware ?

First you can distribute wi :

final color =     w1 * (alpha1 * color1) + w1 * (1 - alpha1) * dest+ w2 * (alpha2 * color2) + w2 * (1 - alpha2) * dest+ w3 * (alpha3 * color3) + w3 * (1 - alpha3) * dest+ w4 * (alpha4 * color4) + w4 * (1 - alpha4) * dest;


Then factorize dest :

final color =   w1 * (alpha1 * color1) + w2 * (alpha2 * color2) + w3 * (alpha3 * color3)+ w4 * (alpha4 * color4) + (w1 * (1 - alpha1)  + w2 * (1 - alpha2) + w3 * (1 - alpha3) + w4 * (1 - alpha4)) * dest;


Distribute wi again :

final color =   w1 * (alpha1 * color1) + w2 * (alpha2 * color2) + w3 * (alpha3 * color3) + w4 * (alpha4 * color4) + (w1 - w1 * alpha1 + w2 - w2 * alpha2 + w3 - w3 * alpha3 + w4 - w4 * alpha4) * dest;


Then use the property that sum(wi) = 1 :

final color =   w1 * (alpha1 * color1) + w2 * (alpha2 * color2) + w3 * (alpha3 * color3) + w4 * (alpha4 * color4) + (w1 + w2 + w3 + w4    - (w1 * alpha1 + w2 * alpha2 + w3 * alpha3 + w4 * alpha4)) * dest;final color =   w1 * (alpha1 * color1) + w2 * (alpha2 * color2) + w3 * (alpha3 * color3) + w4 * (alpha4 * color4) + (1 - (w1 * alpha1 + w2 * alpha2 + w3 * alpha3 + w4 * alpha4)) * dest;


This looks familiar.

So from here you can guess the right solution :

Replace your texture with a new premultiplied texture that means
premulti = alphai * colori and copy alphai from your original texture to your new texture unchanged.

Then set the following renderstates :

pDevice->SetRenderState(D3DRS_BLENDENABLE, TRUE);pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); // <- don't use srcAlpha because it is already factored in.pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); // <- this isn't pure additive we still have to fade the destination color for opaque texelspDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);


LeGreg

This topic is closed to new replies.

Advertisement