Hey guys,
This seems like it ought to be pretty easy, but I'm having a devil of a time with it.
I'm trying to write a pixel shader for my font system that will allow me to control the letter color, the background color, the letter alpha, and the background alpha (I want to have a separate background color/alpha so I can do things like highlight letters and such). So I need to do quite a bit of blending here. The final color and final alpha are then passed to the pipeline, and that gets blended with the color on the screen (via the blendstate). If this was just straight color replacement I think I could do it, but those alphas are making it tricky.
So, for inputs to the pixel shader I have:
"Texture2D DiffuseMap;
SamplerState TriLinearSampler
{
Filter = MIN_MAG_MIP_POINT;
};
struct PS_IN
{
float4 Position: SV_POSITION;
float2 TextCoord: TEXCOORD;
float4 LetterColor: LETTERCOLOR;
float4 BackgroundColor: BACKGROUNDCOLOR;
};
The texture: The texture is a greyscale of all the font glyphs (letter pixles are white, background is black, with antialiasing giving some shades of grey in between). I also have an alpha component on the texture, which I'm not sure I need, but for now I have it set to the same as the RGB channels (so R=G=B=A).
So the letter and background color variables come into the vertex shader as members of a constant buffer, and are simply passed through to the pixel shader. Notice that they both have alpha components.
So here is the idea (my implementation notes are in brackets).
1) I want to sample the texture (no problem there). [=DiffuseSample]
2) I want to replace the white pixels with the LetterColor RGB (but I want to keep that antialiasing intact, so I have to blend the greys with the letter color, yes?). [LetterPixel.RGB=DiffuseSample.RGB * LetterColor.RGB]
3) Somehow I have to blend the alpha of the texture with the alpha of the lettercolor (multiply?). Or perhaps I just replace the alpha with the lettercolor alpha? [LetterPixel.A = LetterColor.A(?????)]
4) I want to replace the black pixels with the background color RGB [BackgroundPixel = Inverse(DiffuseSample).RGB * BackgroundColor.RGB]
5) Set the background alpha to BackgroundColor alpha [BackgroundPixel.A = BackgroundColor.A]
6) So at this point I have two colors, BackgroundPixel and LetterPixel. I need to blend them [OutputColor = BackgroundPixel * LetterPixel]
So, how badly did I screw this up? I haven't written this yet (I'm going to do that now and see what happens), but I'm pretty sure it's probably not going to do what I want, heh. I could use any advice anyone is willing to throw my way.
Color blending shader
Hey guys,
This seems like it ought to be pretty easy, but I'm having a devil of a time with it.
I'm trying to write a pixel shader for my font system that will allow me to control the letter color, the background color, the letter alpha, and the background alpha (I want to have a separate background color/alpha so I can do things like highlight letters and such). So I need to do quite a bit of blending here. The final color and final alpha are then passed to the pipeline, and that gets blended with the color on the screen (via the blendstate). If this was just straight color replacement I think I could do it, but those alphas are making it tricky.
So, for inputs to the pixel shader I have:
"Texture2D DiffuseMap;
SamplerState TriLinearSampler
{
Filter = MIN_MAG_MIP_POINT;
};
struct PS_IN
{
float4 Position: SV_POSITION;
float2 TextCoord: TEXCOORD;
float4 LetterColor: LETTERCOLOR;
float4 BackgroundColor: BACKGROUNDCOLOR;
};
The texture: The texture is a greyscale of all the font glyphs (letter pixles are white, background is black, with antialiasing giving some shades of grey in between). I also have an alpha component on the texture, which I'm not sure I need, but for now I have it set to the same as the RGB channels (so R=G=B=A).
So the letter and background color variables come into the vertex shader as members of a constant buffer, and are simply passed through to the pixel shader. Notice that they both have alpha components.
So here is the idea (my implementation notes are in brackets).
1) I want to sample the texture (no problem there). [=DiffuseSample]
2) I want to replace the white pixels with the LetterColor RGB (but I want to keep that antialiasing intact, so I have to blend the greys with the letter color, yes?). [LetterPixel.RGB=DiffuseSample.RGB * LetterColor.RGB]
3) Somehow I have to blend the alpha of the texture with the alpha of the lettercolor (multiply?). Or perhaps I just replace the alpha with the lettercolor alpha? [LetterPixel.A = LetterColor.A(?????)]
4) I want to replace the black pixels with the background color RGB [BackgroundPixel = Inverse(DiffuseSample).RGB * BackgroundColor.RGB]
5) Set the background alpha to BackgroundColor alpha [BackgroundPixel.A = BackgroundColor.A]
6) So at this point I have two colors, BackgroundPixel and LetterPixel. I need to blend them [OutputColor = BackgroundPixel * LetterPixel]
So, how badly did I screw this up? I haven't written this yet (I'm going to do that now and see what happens), but I'm pretty sure it's probably not going to do what I want, heh. I could use any advice anyone is willing to throw my way.
If your texture is just a grayscale, why not use a single channel of it as the weight parameter for a lerp between the background colour and text colour? ie return lerp( BackgroundColor, LetterColor, DiffuseSample.r)?
edit: got parameters backwards
See, I was making it more complicated than it needed to be, heh.
Thanks man, that ALMOST works. It's just got one problem, which is the output color is wrong when the letter pixel alpha is < 1.0. I'll illustrate:
This call creates some text on my screen:
UIMgr.AddStaticText(Device, &TestString, &FontName, 0.5f, 0.5f, ColorRGBA(0, 1, 0, 0.1), ColorRGBA(1,0,0,1.0));
The last two parameters are the text and background colors, respectively. In this case, the letters should be very faint green (with the red background showing through), and the background should be solid red.
What I'm actually getting is a solid red background (good) but a very faint green on blue (blue being the screen background color). Bad.
Otherwise it works great! I'm working on that last issue..
[EDIT for solution]
Thanks again man, I got it: This is the final pixel shader code (just added a second lerp to do a blend between the background color and the first output with the weight coming from the letter color alpha). It seems to do exactly what I want.
float4 PS(PS_IN pIn) : SV_Target
{
float4 DiffuseSample;
float4 Output;
DiffuseSample = DiffuseMap.Sample(TriLinearSampler, pIn.TextCoord);
Output = lerp(pIn.BackgroundColor, pIn.LetterColor, DiffuseSample.r);
Output = lerp(pIn.BackgroundColor, Output, pIn.LetterColor.a);
return Output;
}
Thanks man, that ALMOST works. It's just got one problem, which is the output color is wrong when the letter pixel alpha is < 1.0. I'll illustrate:
This call creates some text on my screen:
UIMgr.AddStaticText(Device, &TestString, &FontName, 0.5f, 0.5f, ColorRGBA(0, 1, 0, 0.1), ColorRGBA(1,0,0,1.0));
The last two parameters are the text and background colors, respectively. In this case, the letters should be very faint green (with the red background showing through), and the background should be solid red.
What I'm actually getting is a solid red background (good) but a very faint green on blue (blue being the screen background color). Bad.
Otherwise it works great! I'm working on that last issue..
[EDIT for solution]
Thanks again man, I got it: This is the final pixel shader code (just added a second lerp to do a blend between the background color and the first output with the weight coming from the letter color alpha). It seems to do exactly what I want.
float4 PS(PS_IN pIn) : SV_Target
{
float4 DiffuseSample;
float4 Output;
DiffuseSample = DiffuseMap.Sample(TriLinearSampler, pIn.TextCoord);
Output = lerp(pIn.BackgroundColor, pIn.LetterColor, DiffuseSample.r);
Output = lerp(pIn.BackgroundColor, Output, pIn.LetterColor.a);
return Output;
}
See, I was making it more complicated than it needed to be, heh.
Thanks man, that ALMOST works. It's just got one problem, which is the output color is wrong when the letter pixel alpha is < 1.0. I'll illustrate:
This call creates some text on my screen:
UIMgr.AddStaticText(Device, &TestString, &FontName, 0.5f, 0.5f, ColorRGBA(0, 1, 0, 0.1), ColorRGBA(1,0,0,1.0));
The last two parameters are the text and background colors, respectively. In this case, the letters should be very faint green (with the red background showing through), and the background should be solid red.
What I'm actually getting is a solid red background (good) but a very faint green on blue (blue being the screen background color). Bad.
Otherwise it works great! I'm working on that last issue..
[EDIT for solution]
Thanks again man, I got it: This is the final pixel shader code (just added a second lerp to do a blend between the background color and the first output with the weight coming from the letter color alpha). It seems to do exactly what I want.
float4 PS(PS_IN pIn) : SV_Target
{
float4 DiffuseSample;
float4 Output;
DiffuseSample = DiffuseMap.Sample(TriLinearSampler, pIn.TextCoord);
Output = lerp(pIn.BackgroundColor, pIn.LetterColor, DiffuseSample.r);
Output = lerp(pIn.BackgroundColor, Output, pIn.LetterColor.a);
return Output;
}
Output = lerp(pln.BackgroundColor, float4(pln.LetterColor.rgb, 1.0f), DiffuseSample.r * pln.LetterColor.a) should also work for that and will simplify your code a bit
edit: got something backwards again
Hmm, that last equation doesn't work at all. I haven't gone through the math to figure out why, but I suspect there is a typo in there. I'll go through it in a bit. Anyway, with the code I posted, I get this:
The two opacity values are the input alphas for the text and background, respectively.
Everything is working as expected, except the last line looks light brown to me rather than grey. This was the code for that line:
UIMgr.AddStaticText(Device, &TestString, &FontName, 0.3f, 0.55f, ColorRGBA(0, 0, 0, 0.5f), ColorRGBA(0.5f,0.5f,0.5f,1.0f));
As you can see, it should be partially transparent black on a medium grey with no transparency. But damned if it doesn't look brown, right?
However, when I run it through Pix, it insists it is the right color (Final framebuffer value Alpha: 1.000, Red: 0.498, Green: 0.498, Blue: 0.498 for a pixel that looks brown). Photoshop agrees (RGB 127, 127, 127). If I run the same scene on a white background rather than blue, the offending line's background looks much more grey. Yet both Pix and Photoshop insist that it is the same exact color as before.
So I guess I just chalk that up to it being an optical illusion because I'm looking at it on a blue background?
BTW: I added one more line to the shader, just before returning the output:
Output.a = max(pIn.BackgroundColor.a, Output.a);
I did that because (without that correction) if the letter alpha is less than the background alpha, the screen background color will bleed through into the letter color pixels, which is not what I want. The minimum alpha value should always be the background alpha, it should never be less than that, and that's not what I was getting. There may be a better way to lerp it, I'll have to think about it. But this seems to work.
The two opacity values are the input alphas for the text and background, respectively.
Everything is working as expected, except the last line looks light brown to me rather than grey. This was the code for that line:
UIMgr.AddStaticText(Device, &TestString, &FontName, 0.3f, 0.55f, ColorRGBA(0, 0, 0, 0.5f), ColorRGBA(0.5f,0.5f,0.5f,1.0f));
As you can see, it should be partially transparent black on a medium grey with no transparency. But damned if it doesn't look brown, right?
However, when I run it through Pix, it insists it is the right color (Final framebuffer value Alpha: 1.000, Red: 0.498, Green: 0.498, Blue: 0.498 for a pixel that looks brown). Photoshop agrees (RGB 127, 127, 127). If I run the same scene on a white background rather than blue, the offending line's background looks much more grey. Yet both Pix and Photoshop insist that it is the same exact color as before.
So I guess I just chalk that up to it being an optical illusion because I'm looking at it on a blue background?
BTW: I added one more line to the shader, just before returning the output:
Output.a = max(pIn.BackgroundColor.a, Output.a);
I did that because (without that correction) if the letter alpha is less than the background alpha, the screen background color will bleed through into the letter color pixels, which is not what I want. The minimum alpha value should always be the background alpha, it should never be less than that, and that's not what I was getting. There may be a better way to lerp it, I'll have to think about it. But this seems to work.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement