Advertisement

Advice on my CoolDownTimer effects

Started by July 01, 2014 01:19 PM
9 comments, last by ankhd 10 years, 5 months ago

Hi all.

I've been looking in to making a cooldown timer effect, When you press a button and it has the shading going around.

After my third go I think I found a good way to do it but some of the math like the atan2 im doing a flip on to get 360.

Ok I know angle's again why??? only thing I know I'm a Ex metal fabricator.

Any how the cooldown timer is in the pixel shader using the texture coords and I pass in a angle based on time 1 degree or more a second

depends on how fast you want it to spin(go around).

It looks like a mess is there a neater way of doing it.

What way would you try.

//heres the pixel shader this is my test code it works I display red for now for range between 0 and 180

and green when I flip the angle.

float4 DrawPStimer(GS_OUT pIn) : SV_TARGET

{

float2 start = float2(0.0,0.0);//points up

float currentangle = radians(345.0);

//we need to turn the texture coords into a direction vector ???

//texcoord need to be converted to its 0, 0 pos

float2 e = float2(0.5,0.5);

float2 t = pIn.texC - e;

float2 p = normalize(start - t );

float a2 = atan2(p.y, p.x);

if(a2 <= currentangle && a2 > 0.0)

return float4(1.0f, 0.0f, 0.0f, 1.0f);

if(a2 <= 0)

{

if(a2+ radians(360) < currentangle)

return float4(0.0f, 1.0f, 0.0f, 1.0f);

}

return float4(0.0f, 0.0f, 0.0f, 0.0f);

}

here is a image with the current angle set to 275

0 degrees is to the left 180 right

new image now

CoolDownTimer.jpg?psid=1

Ok I know angle's again why??? only thing I know I'm a Ex metal fabricator.

Why ? Because of "don't use angles but vectors" ? Well in this case it is an angle relation, so IMO I wouldn't bother. You can improve a bit though, e.g. bring your angle into the atan2 range of -pi..pi, so you can avoid the ifs (one of which was superfluous anyway):


    // CooldownAngle is a shader constant. Or you could do the shift and radian conversion from the app already
    float currentangle = radians(CooldownAngle - 180.0);  
    // -0.5 will expand to float2(0.5, 0.5)	
    float2 t = pIn.texC - 0.5;
    // note I sign-flipped p again because of the shift, so we can use atan2 directly
    float a2 = atan2(t.y, t.x);
    // the following is the "float version" of an if(). true -> 1, false -> 0.
    float d = currentangle - a2 > 0.0;
    // which we now use for the alpha
    return float4(1, 1, 1, d);
Since atan2 produces quite a bunch of instructions here some alternatives if you got performance problems:
http://www.gamedev.net/topic/555891-warcraft-iiiwow-style-button-cooldown/
http://www.gamedev.net/topic/613851-resolvedworld-of-warcraft-image-effect-spell-cooldown-with-source-code/

Then again: Implementation-wise your approach is probably the simplest wink.png

PS: By the way, you image link is broken.
Advertisement

Hi. Thanks for the Ideas, I tryed them but I could not get them to work the same way with my new bit of code I could do the texc - 0.5.

your code works I ran that.

Heres the whole new code is there a way to use your float routine.

the c++ part updates the angle and converts it to rads now


/heres my new code. what tricks are there to turn this monster into a baby.??????

float2 start = float2(0.0f,0.0f);//points up


float4 sam = float4(0.0f, 0.0f, 0.0f, 1.0f);

float4 s = float4(0.0f, 0.0f, 0.0f, 1.0f);


//if we are at 360 then we just go standard colour the timer is up

if(currentangle >= radians(360.0))

{


return float4(0.0f, 0.0f, 0.0f, 0.0f);

}


//we need to turn the texture coords into a direction vector ???

//texcoord need to be converted to its 0, 0 pos

float2 t = pIn.texC - 0.5;

float2 p = normalize(start - t );

float a2 = atan2(p.y, p.x);

if(a2 <= currentangle && a2 > 0.0)

{

sam = gTex.Sample( TexS, pIn.texC ) * gSelectedColour;//the button has a selected colour add this before grey scale

s = float4(sam.r, sam.r, sam.r, gAlpha);

return s * intensity;


}


if(a2 <= 0)

{ if(a2+ radians(360) < currentangle)

{

sam = gTex.Sample( TexS, pIn.texC ) * gSelectedColour;

s = float4(sam.r, sam.r, sam.r, gAlpha);

return s * intensity;


}

}

return float4(0.0f, 0.0f, 0.0f, 0.0f);

The idea of my snippet was that you can now use that d value for further masking/lerping/whatever. Not sure I follow your code, maybe like so (after my snippet):


    // since you gonna use only one channel...
    float sam = Diffuse.Sample( Sampler, tex ).r * gSelectedColour.r;
    // ... swizzle that to all three channels. And combine d with alpha
    float4 s = float4(sam.rrr, gAlpha * d);        
    return s * intensity;        

PS: Please cleanup your code before posting (e.g. remove the commented statements) and use code tags wink.png
I couldn't help it. Since this topic turns up occasionally, I always wondered if one could do a procedural antialiased version. Here we go:


cbuffer CooldownParameters
{
    // Cooldown angle in degrees
    float CooldownAngle;
    // antialias transition width in pixels
    float Transition;
    // pixel in texel size. Assuming we have a GUI-typical pixel perfect 
    // ortho projection this is just 1.0/width of your rectangle.
    float PixelSize;
}
// ...
SamplerState Sampler;
Texture2D Diffuse;

float4 CooldownPS(float2 tex: TEXCOORD): SV_Target
{
    // normalized point coordinate (2D homogenous for plane evaluation) 
    float3 p = float3(tex - 0.5, 1);	
    
    // first plane for 2D plane equation (a*x + b*y + c), depends on the cooldown angle
    float3 plane1;
    sincos(radians(-CooldownAngle), plane1.x, plane1.y);
    // plane offset to avoid later bleed (just found through trial and error by playing with tweak UI)
    float offset = lerp(0, 2 * Transition, saturate(CooldownAngle / 180.0 - 1));
    // c value of plane equation:
    plane1.z = offset * PixelSize;
    
    // estimate distance to plane using pixel size
    float dist1 = dot(plane1, p) / PixelSize;
    // ... to calculate a anti-alias transition value
    float fade1 = saturate(dist1 / Transition);
    
    // second plane, fixed (horizontal)
    float3 plane2 = float3(0, -1, 0);	
    float dist2 = dot(plane2, p) / PixelSize;
    float fade2 = saturate(dist2  / Transition);
    
    float alpha;
    [flatten]
    if(CooldownAngle < 180.0)
    {
    	// intersection of the two masks
    	alpha = min(fade1, fade2);
    }
    else
    {
    	// union of the two masks
    	alpha = max(fade1, fade2);		
    }
    // uncomment the following line if you want to see both planes in action
    // alpha = (fade1 + fade2) * 0.5;	
    
    // use this alpha for some effect, here just fade to black
    float4 color = Diffuse.Sample(Sampler, tex);
    return float4(color.rgb * alpha, 1);
}
Here some screenshots with an exaggerated transition (1 would be just fine). On the left is the non AA version.

CooldownAA.png

Both planes in action (to visualize the idea):

CooldownAAShowPlanes.png

It's actually cheaper than using atan2 wink.png

Hi.

Thats some nice work unbird. I Thank you for your time.

I'm not totaly sure how your shader works but it does.

How would I go about making the pie shape have the original image colour and the lapsed time area grey scale.

I've changes it around but can't get it like my image.

I would really like to keep your antialiased version.

Advertisement
Easy. Though to increase contrast I'd also darken it a bit

    // [ AA code like before ]	
    // original color
    float3 color = Diffuse.Sample(Sampler, tex).rgb;
    // grayscale version (aka intensity calculation)
    float gray = dot(color, float3(0.3, 0.59, 0.11));
    // darken 
    gray *= 0.6;
    // lerp the two colors.
    return float4(lerp(color.rgb, gray.rrr, alpha), 1);

PS:

I'm not totaly sure how your shader works but it does.

Shall I explain ?

Hey again.

Yeah if you have the time my self and anyone who finds this post would love to here the facts behind the Magic.

I have 1 more question.

The cooldowntimers pixel shader is in a second pass and I am leaving the angle as a trigger not to render any thing and Im using this here


//needed here this pixel shader is called in a second pass and when the angle is 360 we don't render any thing at all
if(currentangle >= (360.0))
{
	clip(-1);
	return  float4(0.0f, 0.0f, 0.0f, 1.0f);//never gets here now
}


is it ok to use the clip function in this way.????

Heres the product I like it.......

CoolDownTimer2.jpg?psid=1

I've updated the shader


//this here needs the angle in degrees passed in to the shader
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
float4 DrawPStimer(GS_OUT pIn) : SV_TARGET
{
 // antialias transition width in pixels
    float Transition = 2.5;
    //pixel in texel size. Assuming we have a GUI-typical pixel perfect 
    // ortho projection this is just 1.0/width of your rectangle.
    float PixelSize =1.0/128.0;//image width

//if we are at 360 then we just go standard colour the timer is up
//needed here this pixel shader is called in a second pass and when the angle is 360 we don't render any thing at all
if(currentangle >= (360.0))
{
	clip(-1);
	return  float4(0.0f, 0.0f, 0.0f, 1.0f);
}

    // normalized point coordinate (2D homogenous for plane evaluation) 
    float3 p = float3(pIn.texC - 0.5, 1);	
    
    // first plane for 2D plane equation (a*x + b*y + c), depends on the cooldown angle
    float3 plane1;
    sincos(radians(-currentangle), plane1.x, plane1.y);//flipped here
    // plane offset to avoid later bleed (just found through trial and error by playing with tweak UI)
    float offset = lerp(0, 2 * Transition, saturate(currentangle / 180.0 - 1));
    // c value of plane equation:
    plane1.z = offset * PixelSize;
    
    // estimate distance to plane using pixel size
    float dist1 = dot(plane1, p) / PixelSize;
    // ... to calculate a anti-alias transition value
    float fade1 = saturate(dist1 / Transition);
    
    // second plane, fixed (horizontal)
    float3 plane2 = float3(0, -1, 0);	
    float dist2 = dot(plane2, p) / PixelSize;
    float fade2 = saturate(dist2  / Transition);
    
    float alpha;
    [flatten]
    if(currentangle < 180.0)
    {
    	// intersection of the two masks
    	alpha = min(fade1, fade2);
    }
    else
    {
    	// union of the two masks
    	alpha = max(fade1, fade2);		
    }
    // uncomment the following line if you want to see both planes in action
    // alpha = (fade1 + fade2) * 0.5;	
    
    // use this alpha for some effect, here just fade to black
    float4 color = gTex.Sample(TexS, pIn.texC);
    //return float4(color.rgb * alpha, 0.5);//gAlpha
//new gray scale around elapsed
//area the pie shape is image colour
// grayscale version (aka intensity calculation)
    float gray = dot(color, float3(0.3, 0.59, 0.11)) * intensity;
    // darken 
    gray *= 0.6;
    // lerp the two colors.
    return float4(lerp(color.rgb, gray.rrr, alpha), gAlpha);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Maybe remove that if statement and put clip on top of the shader like this:


//if(currentangle >= (360.0))
//{
//   clip(-1);
//    return float4(0.0f, 0.0f, 0.0f, 1.0f);//never gets here now
//}
 
clip(359.999 - currentangle);

Better yet skip whole shader if you can calculate currentangle on cpu.

is it ok to use the clip function in this way.?


Sure. Just know that clip may have a performance impact when there are lots of diverging pixels (pixels that get clipped and pixels that don't). Unlikely in this case since you clip the whole rectangle anyway.

Anyway, for future reference here some alternatives:
  • Since you don't want do draw anything when currentangle >= 360°, don't issue a draw call at all (PS: Beaten by belfegor wink.png)
  • If these rectangles are batched and since you are using a geometry shader: Don't generate triangles in your GS at all when currentangle >= 360°.
  • Looks like you're using alpha blending. An alpha of 0 will have the same effect.
Now for the explanation:

The idea is to use a Plane equation, in 2D that is:

f(x,y) = a*x + b*y + c

x,y are our coordinates in texture space (though offset by 0.5, i.e. centered). a and b are the plane's normal, which we get using sincos of the cooldown angle. c is the distance of the plane to the origin (here 0, we define our origin at the center of the quad).

If we evaluate above formula we get the signed distance to the plane.
Here a snippet and screenshot which shows that (Note that dot(float3(x,y,1), float3(a,b,c)) == a*x + b*y + c):

    float3 p = float3(tex - 0.5, 1);	    
    float3 plane1;
    sincos(radians(-CooldownAngle), plane1.x, plane1.y);
    plane1.z = 0;
    float dist = dot(p, plane1);
    if(dist < 0)
    	return float4(-dist, 0, 0, 1);
    else
    	return float4(0, dist, 0, 1);
PlaneDistance.png

This distance is in the space we used for the plane and the point p. Since we want to AA in pixel space, we need to transform this distance using /PixelSize.
Using a transition value gives use a nice AA mask (saturate just makes sure we stay in 0..1 range).

PlaneAAMask.png


We do this again with another plane (this time fixed). Now we combine them. If the cooldown angle is < 180° then we intersect, otherwise we use the union. The mask values are [0..1] floats, so using min for intersection and max for union is one way. Alternatively one can use e.g. multiplication for intersection.

There was a nasty problem though: When the plane normals were exactly opposite, the combined masks produced this ("bleed"):
PlaneBleed.png

Like said, here I played just around to see how I had to offset one plane so this would never happen wink.png

I actually started with a more complex approach using Rendering Vector Art on the GPU(25.5 Antialiasing). This uses hardware derivatives (ddx, ddy) and is therefore foolproof, i.e. and does not need to provide the PixelSize. But for a GUI effect I think this is overkill.

This topic is closed to new replies.

Advertisement