views:

569

answers:

4

Is there something unusual about how the alpha component is handled in a pixel shader? I have a WPF application for which my artist is giving me grayscale images to use as backgrounds, and the application colorizes those images according to the current state. So I wrote a pixel shader (using the WPF Pixel Shader Effects Library infrastructure) to use as an effect on an Image element. The shader takes a color as a parameter, which it converts to HSL so it can manipulate brightness. Then for each grey pixel, it computes a color whose brightness is interpolated between the color parameter and white in proportion to the brightness of the source pixel.

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 src = tex2D(implicitInputSampler, uv);

    // ...Do messy computation involving src brightness and color parameter...

    float4 dst;
    dst.r = ...
    dst.g = ...
    dst.b = ...
    dst.a = src.a;
    return dst;
}

This works just fine on the pixels where alpha = 1. But where alpha = 0, the resultant pixels come out white, rather than having the window's background show through. So I made a tiny change:

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 src = tex2D(implicitInputSampler, uv);
    if (src.a == 0) 
        return src;
    ...

and now the transparent parts really are transparent. Why? Why didn't the dst.a = src.a statement in the first version accomplish that? Unfortunately, even this is only a partial fix, because it looks to me like the pixels with 0 < alpha < 1 are coming out white.

Does anyone know what I'm not understanding about alpha?

A: 

0 < alpha < 1 are coming out white

What ranges are you expecting here?

All values are going to be in the range 0.0 and 1.0... pixel shaders do not work in discrete 256 colour ranges, they are floating point where 1.0 is the maximum intensity.

If your calculations end up setting r/g/b values to >1.0 you are going to get white...

http://www.facewound.com/tutorials/shader1/

Schneider
Notice I said "<", not "<=". I understand that all the values are on the unit interval. Since I'd already discussed the a=1 case (works as expected) and the a=0 case (works only when I put in the shortcircuiting test for src.a==0), 0 < a < 1 is all the other cases, where the source bitmap is partially transparent.
vanmelle
Are you able to debug the actual values output by the shader? From the sample you show I cannot see how this could be failing. You are just copying the alpha. What image formats are you using? What happens to the alpha without your shader?
Schneider
A: 

(This isn't an answer, but the comment limit is too short.)

To answer Schneider above, here are some more details.

My image files are 32-bit PNG files. The transparent bits happen to be at the corners, producing the appearance of a rounded corner.

I don't know how to debug shader output other than looking at what WPF displays as its final rendering on the window. I do have a simple test rig which just contains a bunch of Image elements, with my effect applied, against varying backgrounds.

Without the shader, the images appear as expected - the transparent parts show the background, and the partially transparent parts show a lighter version of the background.

If I take out the short circuit and just rely on dst.a = src.a, then the transparent parts show a non-obvious function of the background:

  • On a black background, all the transparent pixels come out as the tint color, as though the shader were receiving the black background pixel (for which the algorithm would produce exactly the tint color), but the alpha channel output was being ignored.

  • On a 50% gray background, the transparent pixels come out a lighter version of the tint color, and if the tint wasn't a very dark color, this turns out to be white.

  • On a colored background, the transparent pixels show a lighter version of the background, mixed somehow with the tint color.

With the short circuit in, it's only the semi-transparent pixels that come out wrong. As best I can tell (this is harder to figure out), the semi-transparent pixels are showing the same color as in the list above. Which means on a black background, the corner is a bit ragged, on medium bright backgrounds, the corner shows a kind of bright fringe, and on very light backgrounds, it looks about right.

I'm trying to form a theory out of what's going on, but I'm having a hard time. At first I thought that on the transparent pixels, my shader was getting the background as the image pixel, but the numbers don't work out. Besides which, when I tried an experiment where I set dst.a = 1.0 unconditionally, the results were the same as for the black background above, independent of the actual background.

vanmelle
A: 

Dude I am working on a XNA game, and I had to use a grayscale pixel shader and I got the same problem you are facing. I donno if you are familiar with XNA environment or not, but I solved the problem by changing the SpriteBatch drawing SpriteBlendMode from SpriteBlendMode.None to SpriteBlendMode.AlphaBlend, I hope this can help you knowing the reason.

regards,

Muhamad Hesham
Thanks, but I don't know what that means for WPF, or if WPF even has such a thing. I've read elsewhere that WPF doesn't have support for all the directX blend modes.
vanmelle
+5  A: 

After some more web searching, I discovered the piece I was missing.

According to an article on MSDN: "WPF uses pre-multiplied alpha everywhere internally for a number of performance reasons, so that's also the way we interpret the color values in the custom pixel shader."

So the fix turns out to be to throw in a multiplication by alpha:

float4 main(float2 uv : TEXCOORD) : COLOR
{
    ...
    dst.rgb *= src.a;
    return dst;
}

And now my output looks as I expect it to.

vanmelle