Convert If...Else to formula
I'm writing a pixel shader that will be used to draw a slider type control that has min/max and current values.
Any pixel before the current value should be full colour (original texture alpha level), anything shortly after should gradually fade away, and anything far after the current value should be fully transparent.
Currently i have this code:
Code:
if (textureCoordinate.x < SliderValue)
{
return TileColour;
}
else if(textureCoordinate.x < SliderValue + (SliderValue / 10))
{
float Proportion = (SliderValue + (SliderValue / 10) - textureCoordinate.x) / (SliderValue / 10);
return float4(TileColour.rgb, TileColour.a * Proportion);
}
else
{
return 0;
}
However, conditionals are slow on a graphics card, so i'd ideally like to get this down to a single formula.
The SliderValue is in range 0 to 1, as are texture coordinates. The TileColour variable is the original texture colour. TileColour.a is the original alpha level of the texture (range 0..1, 0 for transparent).
The first part deals with pixels before the slider value.The last part deals with pixels far after the slider value,
The middle bit deals with pixels within a range shortly after the slider value (when the slider value is low this range is small, when the slider value is high the range is high). Basically i'm defining a range that's 1 tenth the size of the current slider value. Pixels within that range i want to fade from fully oblique to fully transparent.
This code produces the effect i want, but i'd like it in a single formula if possible.
Hopefully i've explained this enough to take away any need for knowledge of pixel shaders!!
Re: Convert If...Else to formula
In pure math, you want a function f with input and output in [0, 1] where f(x) = 1 for x <= d, f(x) =0 for x >= d + s, and f linearly interpolates in [d, d+s] for some constants d and s. (No shader knowledge required :).)
You can approximate such a function by polynomials--i.e. create a function that is very close to 1, then very close to a linear interpolation, then very close to 0. Computing it would probably take longer than an If, though.
Really what you seem to want is a way to convert values > 1.0 to 1 and values < 0.0 to 0, without using an If.
You can define the sgn(x) function as x / Abs(x). Abs(x) can be defined by Sqrt(x^2), or it can be defined using binary logic depending on the underlying representation of x. For instance, if x is a signed 32 bit integer, x And &7FFF should return a positive version of x by shaving off the most significant bit. Now sgn(x) will error if x = 0. You can get around that by making sure you never pass x=0. There might be more elegant solutions I'm not thinking of. It's important for you to keep in mind this singularity if you do use this method, though it should be relatively easy to avoid.
With the sgn(x) function, we're in a position to define the usual unit step function u(x) where u(x) = 0 for x < 0, u(0) = 1/2, and u(x) = 1 for x > 0. We can do this by setting u(x) = (sgn(x) + 1) / 2.
We can shift u(x) to u(x-c) to make a function which is 1 past c and 0 before c. For a function f(x) we can make a function which is precisely f(x) before c and is 0 after c by using u(x-c)*f(x). We can make a function which is precisely f(x) *after* c and is 0 *before* c by using very similar logic to get u(c-x)*f(x). Combining these, we can make a function which is precisely f(x) between d and d+s and 0 elsewhere by using g(x) = u(x-d)*u((d+s)-x)*f(x).
If f is a linear interpolation where f(d) = 1 and f(d+s) = 0, then the function g(x) is almost what we want--it's linear between d and d+s, 0 after d+s, but is 0 before d as well. This can be rectified by using h(x) = g(x) + u(d-x).
We've been ignoring the end points. h(d) = g(d) + u(d-d) = u(d-d)*u((d+s)-d)*f(d) + u(d-d) = 1/2*1*1+1/2 = 1, which is precisely what we wanted. Similarly h(d+s) = 0.
h(x) can be slightly simplified using the identity 1-u(x-d) = u(d-x) to get h(x) = u(x-d)*u((d+s)-x)*f(x) + u(d-x) = u(x-d)*u((d+s)-x)*f(x) + 1 - u(x-d) = 1 + u(x-d)*(u((d+s)-x)*f(x)-1).
In all, you can use
h(x) = 1 + u(x-d)*(u((d+s)-x)*f(x)-1)
u(y) = (sgn(y) + 1) / 2
sgn(y) = (y) / Abs(y)
Abs(y) = Sqrt(y^2), or y And &7FFF
where you never evaluate sgn(0).
Hopefully I haven't made any mistakes, though even if I have these tools and lines of reasoning will work.
Edit: I don't have time to fix it, but the identity I used is incorrect. It should be 1-u(x-c) = u(c-x)
Further edit: Fixed.
Another edit: Fixed an issue with the singularity of sgn.
Re: Convert If...Else to formula
Fantastic! Thanks for the very detailed explanation of your thought process. Top marks for showing your workings. I would rate, but apparently i need to spread some rep around first. :(
In case anyone's interested heres the finished pixel shader code:
Code:
float SliderValue;
Texture coloredTexture;
sampler coloredTextureSampler = sampler_state
{
texture = <coloredTexture>;
};
float sgn(float x)
{
return x / abs(x);
}
float u(float x)
{
return (sgn(x) + 1) / 2;
}
float f(float x)
{
float d = SliderValue;
float s = (SliderValue / 10);
return (d + s - x) / s;
}
float h(float x)
{
float d = SliderValue;
float s = (SliderValue / 10);
return 1 + u(x - d)*(u((d + s) - x) * f(x) - 1);
}
float4 MenuSlider20(float2 textureCoordinate : TEXCOORD0) : COLOR0
{
float4 TileColour = tex2D(coloredTextureSampler, textureCoordinate);
return float4(TileColour.rgb, h(textureCoordinate.x) * TileColour.a);
}
technique MenuSlider
{
pass MenuSliderPass
{
PixelShader = compile ps_2_0 MenuSlider20();
}
}