• FEATURED

View more

View more

View more

### Image of the Day Submit

IOTD | Top Screenshots

### The latest, straight to your Inbox.

Subscribe to GameDev.net Direct to receive the latest updates and exclusive content.

# Advice on my CoolDownTimer effects

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

10 replies to this topic

### #1ankhd  Members

Posted 01 July 2014 - 07:19 AM

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

//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)

{

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

Edited by ankhd, 02 July 2014 - 09:08 PM.

### #2unbird  Members

Posted 02 July 2014 - 02:56 AM

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

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

### #3ankhd  Members

Posted 02 July 2014 - 06:41 AM

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

{

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)

{

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);



Edited by ankhd, 02 July 2014 - 09:12 PM.

### #4unbird  Members

Posted 02 July 2014 - 01:55 PM

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

### #5unbird  Members

Posted 03 July 2014 - 01:12 PM

POPULAR

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;
// 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
}
else
{
// union of the two masks
}
// uncomment the following line if you want to see both planes in action

// 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.

Both planes in action (to visualize the idea):

It's actually cheaper than using atan2

### #6ankhd  Members

Posted 03 July 2014 - 08:20 PM

Hi.

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

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.

### #7unbird  Members

Posted 04 July 2014 - 04:42 AM

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:

Shall I explain ?

### #8ankhd  Members

Posted 05 July 2014 - 01:31 AM

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.......

//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;
// 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
}
else
{
// union of the two masks
}
// uncomment the following line if you want to see both planes in action

// 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);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////


### #9belfegor  Members

Posted 05 July 2014 - 04:59 AM

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.

Edited by belfegor, 05 July 2014 - 05:02 AM.

### #10unbird  Members

Posted 05 July 2014 - 05:12 AM

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 )
• 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;
plane1.z = 0;
float dist = dot(p, plane1);
if(dist < 0)
return float4(-dist, 0, 0, 1);
else
return float4(0, dist, 0, 1);


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).

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"):

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

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.

### #11ankhd  Members

Posted 05 July 2014 - 06:58 AM

Hey Thanys everyone.

excellent work on the explanation.

I'll do the clip(359.999 - currentangle); thing I reacon.

Why.??? the button image is drawn in the first pass. when the user presses the button it gets set to 1 degree and a buttonevent activated which does the timer update.

I have removed the gs it was there when I was trying the cool down by building circles(but I needed a packman). My google never showed me them links. I always use bad search terms.

Remove all my stuff and you would have a nice tutorial... cheers all.

Edited by ankhd, 06 July 2014 - 10:59 PM.

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.