Each smoothstep is about 7~8 instructions. If the first two inputs into the smoothsteps are constant values defined in the shader (ie: smoothstep(0.2, 0.5, x)) then this will be optimized down to about 3~5 instructions by the compiler (depending on the target). If all you need from the smoothstep is the range remapping & clamping, and you’re using constant values, then it can be done in basically a single instruction or two (depending on the target) if reformed into a MAD (multiply add).
So, for example smoothstep(0.2, 0.5, x)
is:
float t = clamp((x - 0.2) / (0.5 - 0.2), 0.0, 1.0); // remap and clamp
x = t * t * (3.0 - 2.0 * t); // apply smoothstep curve
If you remove the curve and look at just the first line without the clamp it looks like this:
(x - 0.2) / (0.5 - 0.2)
You can refactor that to this:
x * (1.0 / (0.5 - 0.2)) - (0.2 / (0.5 - 0.2))
Which looks way uglier, but it can be solved down to this:
x * 3.3333 - 0.6666
That’s a MAD, which GPUs can do in a single step. Add back the clamp (or use the saturate function) and that’ll either be one or two instructions on the GPU! Shader compilers are smart, but only so far. The original first line and the last line are identical in their results, but the compiler won’t be smart enough to do that work for you. It can however take that full refactored middle line and plug in fixed values, it will solve it down to the last line.
So, that means you can do this:
fixed remap(a, b, x) {
return x * (1.0 / (b - a)) - (a / (b - a));
}
...
x = saturate(remap(0.2, 0.5, x)); // this is just 1 or 2 instructions!
However, if you’re not using constant values defined in the shader, the original form is cheaper. 