Skip to content

Commit 5215a63

Browse files
committed
docs: Fix specification clarity and behavior of smoothstep
Swap the confusing edge0/edge1 nomenclature of the smoothstep declaration to the more intuitive low/high. It was pointed out on Slack by Arnon Marcus that the spec's description of smoothstep was ambiguous about the behavior when low==high: does it return 0 or 1 if x is the same value? The documentation is unclear. The implementation returned 1, which I think is the "correct" behavior in the sense that it matches the results of step() function with that edge. So update the documentation to match. Also Arnon pointed out that things are especially weird if low > high, it's non-monotonic. This seems to be fixed by simply reversing the relative order of the `if x < low` and `if x >= high` tests: basically, it also makes it match step(x, high) and be monotonic. This is a cleaner formal definition of what smoothstep should do, namely: if (x >= high) { return 1.0f; } else if (x < low) { return 0.0f; } else { float t = (x - low) / (high - low); return (3.0f-2.0f*t)*(t*t); } Signed-off-by: Larry Gritz <[email protected]>
1 parent 9f4aa5e commit 5215a63

File tree

4 files changed

+90
-87
lines changed

4 files changed

+90
-87
lines changed

src/doc/languagespec.tex

+21-20
Original file line numberDiff line numberDiff line change
@@ -3488,42 +3488,43 @@ \section{Pattern generation}
34883488
performed component-by-component (separately for $x$, $y$, and $z$).
34893489
\apiend
34903490

3491-
\apiitem{float {\ce linearstep} (float edge0, float edge1, float x) \\
3492-
\emph{type} {\ce linearstep} (\emph{type} edge0, \emph{type} edge1, \emph{type} x)}
3491+
\apiitem{float {\ce linearstep} (float low, float high, float x) \\
3492+
\emph{type} {\ce linearstep} (\emph{type} low, \emph{type} high, \emph{type} x)}
34933493
\indexapi{linearstep()}
3494-
Returns 0 if $x \le {\mathit edge0}$, and 1 if $x \ge {\mathit edge1}$,
3494+
Returns 0 if $x \le {\mathit low}$, and 1 if $x \ge {\mathit high}$,
34953495
and performs a linear
3496-
interpolation between 0 and 1 when ${\mathit edge0} < x < {\mathit edge1}$.
3497-
This is equivalent to {\cf step(edge0, x)} when {\cf edge0 == edge1}.
3496+
interpolation between 0 and 1 when ${\mathit low} < x < {\mathit high}$.
3497+
This is equivalent to {\cf step(low, x)} when {\cf low == high}.
34983498
For \color and \point-like types, the computations are
34993499
performed component-by-component (separately for $x$, $y$, and $z$).
35003500
\apiend
35013501

3502-
\apiitem{float {\ce smoothstep} (float edge0, float edge1, float x) \\
3503-
\emph{type} {\ce smoothstep} (\emph{type} edge0, \emph{type} edge1, \emph{type} x)}
3502+
\apiitem{float {\ce smoothstep} (float low, float high, float x) \\
3503+
\emph{type} {\ce smoothstep} (\emph{type} low, \emph{type} high, \emph{type} x)}
35043504
\indexapi{smoothstep()}
3505-
Returns 0 if $x \le {\mathit edge0}$, and 1 if $x \ge {\mathit edge1}$,
3505+
Returns 0 if $x < {\mathit low}$, and 1 if $x \ge {\mathit high}$,
35063506
and performs a smooth Hermite
3507-
interpolation between 0 and 1 when ${\mathit edge0} < x < {\mathit edge1}$.
3507+
interpolation between 0 and 1 when ${\mathit low} < x < {\mathit high}$.
35083508
This is useful in cases where you would want a thresholding function
3509-
with a smooth transition.
3509+
with a smooth transition. In the degenerate case where ${\mathit high} \le
3510+
{\mathit low}$, the return value will be 0 if $x < {\mathit high}$ and 1 if $x
3511+
\ge {\mathit high}$, making the behavior identical to `step(high, x)`.
35103512

35113513
The \emph{type} may be any of of \float, \color, \point, \vector, or
35123514
\normal. For \color and \point-like types, the computations are
35133515
performed component-by-component.
35143516
\apiend
35153517

3516-
\apiitem{float {\ce smooth_linearstep} (float edge0, float edge1, float x, float eps) \\
3517-
\emph{type} {\ce smooth_linearstep} (\emph{type} edge0, \emph{type} edge1, \emph{type} x, \emph{type} eps)}
3518+
\apiitem{float {\ce smooth_linearstep} (float low, float high, float x, float eps) \\
3519+
\emph{type} {\ce smooth_linearstep} (\emph{type} low, \emph{type} high, \emph{type} x, \emph{type} eps)}
35183520
\indexapi{smooth_linearstep()}
3519-
This function is strictly linear between ${\mathit edge0}+{\mathit eps}$ and ${\mathit edge1}-{\mathit eps}$
3520-
but smoothly ramps to 0 between ${\mathit edge0}-{\mathit eps}$ and ${\mathit edge0}+{\mathit eps}$
3521-
and smoothly ramps to 1 between ${\mathit edge1}-{\mathit eps}$ and ${\mathit edge1}+{\mathit eps}$.
3522-
It is 0 when $x \le {\mathit edge0}-{\mathit eps}$, and 1 if $x \ge {\mathit edge1}+{\mathit eps}$,
3523-
and performs a linear
3524-
interpolation between 0 and 1 when ${\mathit edge0} < x < {\mathit edge1}$.
3525-
For \color and \point-like types, the computations are
3526-
performed component-by-component.
3521+
This function returns 0 if $x < {\mathit low}-{\mathit eps}$, and 1 if
3522+
$x \ge {\mathit high}+{\mathit eps}$, is strictly linear between
3523+
${\mathit low}+{\mathit eps}$ and ${\mathit high}-{\mathit eps}$ but smoothly
3524+
ramps to 0 between ${\mathit low}-{\mathit eps}$ and ${\mathit low}+{\mathit eps}$
3525+
and smoothly ramps to 1 between ${\mathit high}-{\mathit eps}$ and
3526+
${\mathit high}+{\mathit eps}$. For \color and \point-like types, the
3527+
computations are performed component-by-component.
35273528
\apiend
35283529

35293530

src/doc/stdlib.md

+17-17
Original file line numberDiff line numberDiff line change
@@ -458,36 +458,36 @@ the computations are performed component-by-component (separately for `x`,
458458
performed component-by-component (separately for $x$, $y$, and $z$).
459459

460460

461-
`float` **`linearstep`** `(float edge0, float edge1, float x)` <br> *`type`* **`linearstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`)
461+
`float` **`linearstep`** `(float low, float high, float x)` <br> *`type`* **`linearstep`** (*`type`* `low`, *`type`* `high`, *`type`* `x`)
462462

463-
: Returns 0 if `x` $\le$ `edge0`, and 1 if `x` $\ge$ `edge1`, and performs a
464-
linear interpolation between 0 and 1 when `edge0` $<$ `x` $<$ `edge1`.
465-
This is equivalent to `step(edge0, x)` when `edge0 == edge1`. For `color`
463+
: Returns 0 if `x` $<$ `low`, and 1 if `x` $\ge$ `high`, and performs a
464+
linear interpolation between 0 and 1 when `low` $<$ `x` $<$ `high`.
465+
This is equivalent to `step(low, x)` when `low == high`. For `color`
466466
and `point`-like types, the computations are performed
467467
component-by-component (separately for $x$, $y$, and $z$).
468468

469469

470-
`float` **`smoothstep`** `(float edge0, float edge1, float x)` <br> *`type`* **`smoothstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`)
470+
`float` **`smoothstep`** `(float low, float high, float x)` <br> *`type`* **`smoothstep`** (*`type`* `low`, *`type`* `high`, *`type`* `x`)
471471

472-
: Returns 0 if `x` $\le$ `edge0`, and 1 if `x` $\ge$ `edge1`, and performs a
473-
smooth Hermite interpolation between 0 and 1 when `edge0` $<$ `x` $<$
474-
`edge1`. This is useful in cases where you would want a thresholding
475-
function with a smooth transition.
472+
: Returns 0 if `x` $<$ `low`, and 1 if `x` $\ge$ `high`, and performs a
473+
smooth Hermite interpolation between 0 and 1 when `low` $<$ `x` $<$
474+
`high`. This is useful in cases where you would want a thresholding
475+
function with a smooth transition. In the degenerate case where
476+
`high` $\le$ `low`, the return value will be 0 if `x` $<$ `high` and 1 if
477+
`x` $\ge$ `high`, making the behavior identical to `step(high, x)`.
476478

477479
The *`type`* may be any of of `float`, `color`, `point`, `vector`, or
478480
`normal`. For `color` and `point`-like types, the computations are
479481
performed component-by-component.
480482

481483

482-
`float` **`smooth_linearstep`** `(float edge0, float edge1, float x, float eps)` <br> *`type`* **`smooth_linearstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`, *`type`* eps)
484+
`float` **`smooth_linearstep`** `(float low, float high, float x, float eps)` <br> *`type`* **`smooth_linearstep`** (*`type`* `low`, *`type`* `high`, *`type`* `x`, *`type`* eps)
483485

484-
: This function is strictly linear between `edge0 + eps` and `edge1 - eps`
485-
but smoothly ramps to 0 between `edge0 - eps` and `edge0 + eps`
486-
and smoothly ramps to 1 between `edge1 - eps` and `edge1 + eps`.
487-
It is 0 when `x` $\le$ `edge0-eps,` and 1 if `x` $\ge$ `edge1 + eps`,
488-
and performs a linear interpolation between 0 and 1 when
489-
`edge0` < x < `edge1`. For `color` and `point`-like types, the
490-
computations are performed component-by-component.
486+
: This function returns 0 if `x` $<$ `low-eps`, and 1 if `x` $\ge$ `high + eps`,
487+
is strictly linear between `low + eps` and `high - eps`, but smoothly
488+
ramps to 0 between `low - eps` and `low + eps` and smoothly ramps to 1
489+
between `high - eps` and `high + eps`. For `color` and `point`-like types,
490+
the computations are performed component-by-component.
491491

492492

493493
%## Noise functions

src/include/OSL/dual.h

+17-15
Original file line numberDiff line numberDiff line change
@@ -1311,27 +1311,29 @@ safe_fmod (const Dual<T,P>& a, const Dual<T,P>& b)
13111311

13121312

13131313

1314-
OSL_HOSTDEVICE OSL_FORCEINLINE float smoothstep(float e0, float e1, float x) {
1315-
if (x < e0) return 0.0f;
1316-
else if (x >= e1) return 1.0f;
1317-
else {
1318-
float t = (x - e0)/(e1 - e0);
1314+
OSL_HOSTDEVICE OSL_FORCEINLINE float smoothstep(float low, float high, float x) {
1315+
if (x >= high) {
1316+
return 1.0f;
1317+
} else if (x < low) {
1318+
return 0.0f;
1319+
} else {
1320+
float t = (x - low) / (high - low);
13191321
return (3.0f-2.0f*t)*(t*t);
13201322
}
13211323
}
13221324

1323-
// f(t) = (3-2t)t^2, t = (x-e0)/(e1-e0)
1325+
// f(t) = (3-2t)t^2, t = (x-low)/(high-low)
13241326
template<class T, int P>
1325-
OSL_HOSTDEVICE OSL_FORCEINLINE Dual<T,P> smoothstep (const Dual<T,P> &e0, const Dual<T,P> &e1, const Dual<T,P> &x)
1327+
OSL_HOSTDEVICE OSL_FORCEINLINE Dual<T,P> smoothstep (const Dual<T,P> &low, const Dual<T,P> &high, const Dual<T,P> &x)
13261328
{
1327-
if (x.val() < e0.val()) {
1328-
return Dual<T,P> (T(0));
1329-
}
1330-
else if (x.val() >= e1.val()) {
1331-
return Dual<T,P> (T(1));
1332-
}
1333-
Dual<T,P> t = (x - e0)/(e1-e0);
1334-
return (T(3) - T(2)*t)*t*t;
1329+
if (x.val() >= high.val()) {
1330+
return Dual<T,P>(T(1));
1331+
} else if (x.val() < low.val()) {
1332+
return Dual<T,P>(T(0));
1333+
} else {
1334+
Dual<T,P> t = (x - low) / (high - low);
1335+
return (T(3) - T(2)*t)*t*t;
1336+
}
13351337
}
13361338

13371339

src/shaders/stdosl.h

+35-35
Original file line numberDiff line numberDiff line change
@@ -319,73 +319,73 @@ point step (point edge, point x) BUILTIN;
319319
vector step (vector edge, vector x) BUILTIN;
320320
normal step (normal edge, normal x) BUILTIN;
321321
float step (float edge, float x) BUILTIN;
322-
float smoothstep (float edge0, float edge1, float x) BUILTIN;
322+
float smoothstep (float low, float high, float x) BUILTIN;
323323

324-
color smoothstep (color edge0, color edge1, color x)
324+
color smoothstep (color low, color high, color x)
325325
{
326-
return color (smoothstep(edge0[0], edge1[0], x[0]),
327-
smoothstep(edge0[1], edge1[1], x[1]),
328-
smoothstep(edge0[2], edge1[2], x[2]));
326+
return color (smoothstep(low[0], high[0], x[0]),
327+
smoothstep(low[1], high[1], x[1]),
328+
smoothstep(low[2], high[2], x[2]));
329329
}
330-
vector smoothstep (vector edge0, vector edge1, vector x)
330+
vector smoothstep (vector low, vector high, vector x)
331331
{
332-
return vector (smoothstep(edge0[0], edge1[0], x[0]),
333-
smoothstep(edge0[1], edge1[1], x[1]),
334-
smoothstep(edge0[2], edge1[2], x[2]));
332+
return vector (smoothstep(low[0], high[0], x[0]),
333+
smoothstep(low[1], high[1], x[1]),
334+
smoothstep(low[2], high[2], x[2]));
335335
}
336336

337-
float linearstep (float edge0, float edge1, float x) {
337+
float linearstep (float low, float high, float x) {
338338
float result;
339-
if (edge0 != edge1) {
340-
float xclamped = clamp (x, edge0, edge1);
341-
result = (xclamped - edge0) / (edge1 - edge0);
339+
if (low != high) {
340+
float xclamped = clamp (x, low, high);
341+
result = (xclamped - low) / (high - low);
342342
} else { // special case: edges coincide
343-
result = step (edge0, x);
343+
result = step (low, x);
344344
}
345345
return result;
346346
}
347-
color linearstep (color edge0, color edge1, color x)
347+
color linearstep (color low, color high, color x)
348348
{
349-
return color (linearstep(edge0[0], edge1[0], x[0]),
350-
linearstep(edge0[1], edge1[1], x[1]),
351-
linearstep(edge0[2], edge1[2], x[2]));
349+
return color (linearstep(low[0], high[0], x[0]),
350+
linearstep(low[1], high[1], x[1]),
351+
linearstep(low[2], high[2], x[2]));
352352
}
353-
vector linearstep (vector edge0, vector edge1, vector x)
353+
vector linearstep (vector low, vector high, vector x)
354354
{
355-
return vector (linearstep(edge0[0], edge1[0], x[0]),
356-
linearstep(edge0[1], edge1[1], x[1]),
357-
linearstep(edge0[2], edge1[2], x[2]));
355+
return vector (linearstep(low[0], high[0], x[0]),
356+
linearstep(low[1], high[1], x[1]),
357+
linearstep(low[2], high[2], x[2]));
358358
}
359359

360-
float smooth_linearstep (float edge0, float edge1, float x_, float eps_) {
360+
float smooth_linearstep (float low, float high, float x_, float eps_) {
361361
float result;
362-
if (edge0 != edge1) {
362+
if (low != high) {
363363
float rampup (float x, float r) { return 0.5/r * x*x; }
364-
float width_inv = 1.0 / (edge1 - edge0);
364+
float width_inv = 1.0 / (high - low);
365365
float eps = eps_ * width_inv;
366-
float x = (x_ - edge0) * width_inv;
366+
float x = (x_ - low) * width_inv;
367367
if (x <= -eps) result = 0;
368368
else if (x >= eps && x <= 1.0-eps) result = x;
369369
else if (x >= 1.0+eps) result = 1;
370370
else if (x < eps) result = rampup (x+eps, 2.0*eps);
371371
else /* if (x < 1.0+eps) */ result = 1.0 - rampup (1.0+eps - x, 2.0*eps);
372372
} else {
373-
result = step (edge0, x_);
373+
result = step (low, x_);
374374
}
375375
return result;
376376
}
377377

378-
color smooth_linearstep (color edge0, color edge1, color x, color eps)
378+
color smooth_linearstep (color low, color high, color x, color eps)
379379
{
380-
return color (smooth_linearstep(edge0[0], edge1[0], x[0], eps[0]),
381-
smooth_linearstep(edge0[1], edge1[1], x[1], eps[1]),
382-
smooth_linearstep(edge0[2], edge1[2], x[2], eps[2]));
380+
return color (smooth_linearstep(low[0], high[0], x[0], eps[0]),
381+
smooth_linearstep(low[1], high[1], x[1], eps[1]),
382+
smooth_linearstep(low[2], high[2], x[2], eps[2]));
383383
}
384-
vector smooth_linearstep (vector edge0, vector edge1, vector x, vector eps)
384+
vector smooth_linearstep (vector low, vector high, vector x, vector eps)
385385
{
386-
return vector (smooth_linearstep(edge0[0], edge1[0], x[0], eps[0]),
387-
smooth_linearstep(edge0[1], edge1[1], x[1], eps[1]),
388-
smooth_linearstep(edge0[2], edge1[2], x[2], eps[2]));
386+
return vector (smooth_linearstep(low[0], high[0], x[0], eps[0]),
387+
smooth_linearstep(low[1], high[1], x[1], eps[1]),
388+
smooth_linearstep(low[2], high[2], x[2], eps[2]));
389389
}
390390

391391
float aastep (float edge, float s, float dedge, float ds) {

0 commit comments

Comments
 (0)