Sign in to follow this  
remigius

What does Smoothstep return?

Recommended Posts

remigius    1172
For a little hobby project of mine I had to implement a typical SmoothStep function, [url="http://en.wikipedia.org/wiki/Smoothstep"]defined by Wikipedia[/url] as:

[code]float smoothstep(float edge0, float edge1, float x)
{
// Scale, bias and saturate x to 0..1 range
x = saturate((x - edge0)/(edge1 - edge0));
// Evaluate polynomial
return x*x*(3 - 2*x);
}
[/code]

I'm a bit confused though, since this code stubbornly refuses to return anything within the [edge0;edge1] range. It's quite possible I made a coding error, but I'm wondering what a Smoothstep implementation in general is expected to return. I'd say it would be an interpolated value between edge0 and edge1 akin to Lerp (this is what the XNA's MathHelper.SmoothStep does), but this doesn't seem to be the case. I've reworked the code to this snippet below, which has the behavior I want. I still wonder though if it shouldn't return this smoothly interpolated value in the first place, so any insights would be appreciated.

[code]
private static double SmoothStep(double edge0, double edge1, double x)
{
// Perlin alternative
x = Clamp(x, 0, 1);
x = x*x*x*(x*(x*6 - 15) + 10);
return edge0 * (1 - x) + edge1 * x;

/*
x = Clamp(x, 0, 1);
x = x * x * (3 - 2 * x);
return edge0 * (1 - x) + edge1 * x;
*/
}

private static double Clamp(double x, double min, double max)
{
return x < min ? min : x > max ? max : x;
}[/code]

Share this post


Link to post
Share on other sites
haegarr    7372
AFAIS smoothstep returns a weight for interpolation (not to be confused with an interpolated value itself). Interpolation means to compute a value between 2 samples, where the argument x defines where in-between the 2 samples you want to compute the interpolated value:
f( x, g(x[sub]l[/sub]), g(x[sub]r[/sub]) ) where x[sub]l[/sub] <= x <= x[sub]r[/sub]
with the boundary conditions
f( x=x[sub]l[/sub], g(x[sub]l[/sub]), g(x[sub]r[/sub]) ) = g(x[sub]l[/sub])
f( x=x[sub]r[/sub], g(x[sub]l[/sub]), g(x[sub]r[/sub]) ) = g(x[sub]r[/sub])

In the given example, edge0 plays the role of x[sub]l[/sub], and edge1 the role of x[sub]r[/sub]. There is no g given, because just the interpolation value is computed. The first line
x = saturate((x - edge0)/(edge1 - edge0));
transforms x from the range
edge0 <= x <= edge1
into the normalized range
0 <= x <= 1
so that it becomes suitable for the following spline:
y = x*x*(3 - 2*x)

The result is in the range 0<=y<=1, is symmetric to x=0.5, and is strictly monotonic increasing. As you can see, computing y for x=edge0 gives you y=0, and computing y for x=edge1 gives you y=1. If the transition from y=0 to y=1 would happen suddenly at an x then the function would be the typical [i]step function[/i]. But because the transition is smooth due to the spline, it is called a [i]smooth step function[/i].

With that y you can interpolate between 2 points g[sub]0[/sub], g[sub]1[/sub] using an envelope:
g[sub]0[/sub] * ( 1 - y ) + g[sub]1[/sub] * y

Hence, SmoothStep is just the first half of what you want, independent on the kind of values to be interpolated.

Share this post


Link to post
Share on other sites
remigius    1172

Thanks for the insight, seems like I've been looking at SmoothStep all wrong indeed. I've always thought it's a drop-in replacement for Lerp(a, b, x), especially since some implementations do work that way. If I'm using an x that's already normalized to [0;1] though, it looks like I'm better off with the 2nd code snippet I posted, right? I'm using it to smoothly animate from one double to another, so it seems to work out, but if there's something glaringly wrong with that approach I'd be happy to hear it.


Share this post


Link to post
Share on other sites
haegarr    7372
Interpolation is always a 2 step process: (1) Computing the weights and (2) summing up the weighted samples to yield in the interpolated value. This is also the case for Lerp.

The first step can be done independently of the second step. That is how the 1st Smoothstep function is implemented, and how Lerp and any other method [i]can[/i] be implemented, too. Whether or not this is done is probably the reason that a function may but need not be used as a drop-in replacement; it cannot be used so if the one implementation computes the weights only and the second computes the interpolated value. So separating the 1st and 2nd step has the advantage that the same weight computation can be used for different types of interpolation values. E.g. the 1st implementation in the OP can be used for double values as well as for position vectors, while the 2nd implementation can be used for doubles only.

Of course, a specialized implementation is ever more efficient in runtime than a general one is. So requiring that x is already normalized and immediately weighting and summing samples is more efficient.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this