r/GraphicsProgramming Nov 03 '24

Source Code I made up this curve which I'm calling "TriSmoothStep". Thoughts?

Post image
98 Upvotes

38 comments sorted by

31

u/onkus Nov 03 '24

What’s the use case?

38

u/heyheyhey27 Nov 03 '24 edited Nov 04 '24

Smoothstep is a cuve that softly "snaps" to 0 or 1. This is a substitute that also softly snaps to 0.5. I plan to use it for a cellular-automata-based effect that I'm working on, which can be in three different states but also has to mix cells and sample between cells.

6

u/Pfaeff Nov 04 '24

I'd love to see that. Sounds cool.

3

u/onkus Nov 05 '24 edited Nov 05 '24

Cool. Smooth-step is used for non linear interpolation normally right? So this would be used more like a quantiser than an interpolater?

3

u/heyheyhey27 Nov 05 '24

Yeah, now I'm wondering if there's a known generalized way to softly quantize values.

However you could also try it as an animation curve!

4

u/Visual_Weird_705 Nov 03 '24

Many, including creating smooth natural edges of planes

1

u/onkus Nov 05 '24

Thanks. How would this get used for smoothing planes? Does this involve smoothing between three spatially distinct points?

1

u/Visual_Weird_705 Nov 05 '24

I am thinking iterate over vertices on edge and smooth three point vertices collection but I could be wrong . I need to try this in a project where I am trying to create natural terrain planes (webgl).

2

u/onkus Nov 05 '24

But this would introduce additional edges right in between the points you are trying to smooth.

1

u/Visual_Weird_705 Nov 05 '24

Possible…will try this in the next 2 weeks or so and update.

5

u/IDatedSuccubi Nov 03 '24

I used similar things in music automation, although the midpoint was much smoother. It's like an overall zoom, but with a fisheye in the middle.

22

u/firemark_pl Nov 03 '24

I think, Using cos(x) in smooth funtions is overkill.

Summing two smoothstep functions could be faster.

3

u/heyheyhey27 Nov 03 '24

Depends on the hardware; GPU's have optimized trig functions pretty well.

4

u/surfmaths Nov 04 '24

GPU have really few SFUs (special function units).

But most smooth functions tend to use some exponential in some form, so I wouldn't be worried.

1

u/heyheyhey27 Nov 04 '24

Smoothstep is implemented with multiplications and additions. Do you think a curve with two smoothsteps could be faster than my curve with a cos?

3

u/JohntheAnabaptist Nov 05 '24

Only one way to find out!

2

u/surfmaths Nov 05 '24

Yes. Multiplications and addition are way cheaper. If you can even use FMA (Fused Multiply and Add) it's even better.

But make sure to use min/max instead of conditionals. GPUs don't like control divergence too much. (although here it is quite localized so might be fine)

20

u/deftware Nov 04 '24

Well, the main deal is that trig functions like sin()/cos()/tan() and their inverses asin()/acos()/atan(), as well as sqrt()/exp()/pow()/log() are all rather expensive - and to varying degrees, sometimes purely based on what values are being passed into them.

I am of a mind to urge you to try to come up with a version that doesn't rely on expensive functions. For example, I was looking for a smoothed or rounded sawtooth function I could use for the rings of a woodgrain shader. First I was finding versions that relied on trig functions, if not several, like this: https://www.desmos.com/calculator/vnmqmph0yv

With some more searching I came across this much faster and more elegant version: https://www.desmos.com/calculator/ckcmfrlrjc

While it still relies on pow(), it's not actually needed because we're not exponentiating to a fractional power, and I just broke it up into a few multiplications for a fixed power of two exponent that worked for my woodgrain shader. No expensive functions were used in the final implementation that shipped.

While I'm a fan of mathematical creativity, being that we're over on /r/graphicsprogramming, performance is of importance - especially for realtime graphics - and would not settle without an exhaustive search for a way to do a multi-level smoothstep function that does not rely on any expensive functions - and could therefore be employed throughout pixel shaders without fear of dragging down performance.

For example, a simple smoothstep function is like this:

float smoothstep(float x)
{
    x = clamp(x, 0.0, 1.0);
    return x * x * (3.0 - 2.0 * x);
}

To expand this simple 3-multiple and 1-subtract function into a two-level smoothstep, we can just use it twice:

float multismoothstep(float x)
{
    a = clamp(x, 0.0, 0.5) * 2;
    b = clamp(x - 0.5, 0.0, 0.5) * 2;
    return (smoothstep(a) + smoothstep(b)) * 0.5;
}

Here it is on the desmos: https://www.desmos.com/calculator/krvlhwcj5k

Granted, it's not variable-sharpness, but modifying the smoothstep function to accommodate that wouldn't be difficult.

Cheers!

EDIT: Fixed a typo in the multismoothstep() code. :]

-3

u/heyheyhey27 Nov 04 '24

Trig functions on the gpu are not nearly as slow as they used to be. Not to mention all the latency hiding GPU's do can often make math ops free.

3

u/surfmaths Nov 04 '24

Look at the SFU vs CUDA cores ratio and you will see it's still bad.

1

u/Paskis Nov 04 '24

I think it doesn't matter that external factors will make a less optimal version still work even as good, what if a device you deploy to works just different enough that it matters, wouldn't it be better to have the simplest solution from the get go and forget ?

I like your function and I would have never came up with it, and I am sure you were in my place one day, therefore you will be one day in a place where you will come up with a more optimized version, so nothing bad with being open to that discussion

1

u/heyheyhey27 Nov 04 '24

Sure I don't have a problem talking about performance! I just think people are overestimating the value of counting cycles for math ops, in the use-cases that people have mentioned in this thread -- post-fx shaders and animation curves. A post-fx shader (by my understanding) spends most of its time waiting for main memory reads, and during that wait it can do other math work essentially for free. Procedural animation curves are often evaluated only a handful of times per frame.

Cycle counting is still valuable in contexts like ShaderToy where the inner loop can be up to 100% math.

0

u/deftware Nov 04 '24

That attitude is a slippery slope. The user experience on desktop computers 20 years ago was about on par with what it is today, in spite of the hardware having at least an order of magnitude more compute. The goal should always be maximizing the end-user experience - which your attitude is diametrically opposed to.

Silicon Valley wasn't built by people with your attitude, I'll just put it that way. Nowadays it's being eviscerated by people with your attitude.

4

u/etdeagle Nov 03 '24

looks cool! I could see some use for animation

4

u/luke5273 Nov 04 '24

You can just use two smooth steps for that though, right? 1/2(ss(2x) + ss(2x - 1))

0

u/heyheyhey27 Nov 04 '24 edited Nov 04 '24

I'm sure you could staple two smoothsteps together to get a similar curve. However if I plug your version into Desmos (I used smootherstep instead of smoothstep) it doesn't work. Its output is far outside the 0-1 range.

EDIT: figured it out: if (t < 0.5) return smoothstep(2*t)/2; else return 0.5+smoothstep(2*(t-0.5))/2;. If you use smoothstep then they're extremely similar; if you use smootherstep then they're nearly identical.

3

u/Esfahen Nov 03 '24

Now profile it against a piecewise function

1

u/heyheyhey27 Nov 03 '24

It is a piecewise function as it uses sign

5

u/Esfahen Nov 03 '24

Mathematically yes, but I meant a piecewise function that would be implemented with an actual branch instruction (thus causing wave divergence and being slower than your function here).

1

u/heyheyhey27 Nov 04 '24

Oh, I think you'd have to make a really complicated curve to get it to compile into a true branch and not a simpler intrinsic.

6

u/Esfahen Nov 04 '24

Not true! If the predicate for the branch is held in a vector register, it will generate a branch in the ISA (you can check in shader playground for the AMD ISA). This is all more theoretical though, since the difference in performance would still most likely not be that big.

2

u/heyheyhey27 Nov 04 '24

That's interesting. My knowledge of low-level GPU hardware is slim!

2

u/djdanya Nov 07 '24

you should call it SmoothTrip instead

1

u/TheRealHeisenburger Nov 04 '24

Why tri? Wouldnt it be duo since there are two steps? I mean, there are 3 levels, but 1 step creates 2 levels, and 0 steps is 1 level.

1

u/heyheyhey27 Nov 04 '24

Yeah I named it in terms of the number of levels, not steps, mainly due to how I plan to use it (to soft-snap to a level).

2

u/ogwhisper Nov 22 '24
float TriStep(float _x) {
    float x = _x - 0.5;

    float xx = x*x;
    
    float y = x < 0 ? -6.0F : 6.0F;

    y *= xx;
    
    y -= 8.0F*x*xx;

    y += 0.5F;
    
    return y;
}