From 67e886e9e24880f8618c07feeea8102926ad76ec Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Mon, 19 Aug 2024 08:28:26 -0700 Subject: [PATCH] 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 --- src/doc/languagespec.tex | 41 +++++++++++------------ src/doc/stdlib.md | 34 +++++++++---------- src/include/OSL/dual.h | 32 +++++++++--------- src/shaders/stdosl.h | 70 ++++++++++++++++++++-------------------- 4 files changed, 90 insertions(+), 87 deletions(-) diff --git a/src/doc/languagespec.tex b/src/doc/languagespec.tex index 7c90ed198..68d5b38e9 100644 --- a/src/doc/languagespec.tex +++ b/src/doc/languagespec.tex @@ -3488,42 +3488,43 @@ \section{Pattern generation} performed component-by-component (separately for $x$, $y$, and $z$). \apiend -\apiitem{float {\ce linearstep} (float edge0, float edge1, float x) \\ -\emph{type} {\ce linearstep} (\emph{type} edge0, \emph{type} edge1, \emph{type} x)} +\apiitem{float {\ce linearstep} (float low, float high, float x) \\ +\emph{type} {\ce linearstep} (\emph{type} low, \emph{type} high, \emph{type} x)} \indexapi{linearstep()} -Returns 0 if $x \le {\mathit edge0}$, and 1 if $x \ge {\mathit edge1}$, +Returns 0 if $x \le {\mathit low}$, and 1 if $x \ge {\mathit high}$, and performs a linear -interpolation between 0 and 1 when ${\mathit edge0} < x < {\mathit edge1}$. -This is equivalent to {\cf step(edge0, x)} when {\cf edge0 == edge1}. +interpolation between 0 and 1 when ${\mathit low} < x < {\mathit high}$. +This is equivalent to {\cf step(low, x)} when {\cf low == high}. For \color and \point-like types, the computations are performed component-by-component (separately for $x$, $y$, and $z$). \apiend -\apiitem{float {\ce smoothstep} (float edge0, float edge1, float x) \\ -\emph{type} {\ce smoothstep} (\emph{type} edge0, \emph{type} edge1, \emph{type} x)} +\apiitem{float {\ce smoothstep} (float low, float high, float x) \\ +\emph{type} {\ce smoothstep} (\emph{type} low, \emph{type} high, \emph{type} x)} \indexapi{smoothstep()} -Returns 0 if $x \le {\mathit edge0}$, and 1 if $x \ge {\mathit edge1}$, +Returns 0 if $x \le {\mathit low}$, and 1 if $x \ge {\mathit high}$, and performs a smooth Hermite -interpolation between 0 and 1 when ${\mathit edge0} < x < {\mathit edge1}$. +interpolation between 0 and 1 when ${\mathit low} < x < {\mathit high}$. This is useful in cases where you would want a thresholding function -with a smooth transition. +with a smooth transition. In the degenerate case where ${\mathit high} \le +{\mathit low}$, the return value will be 0 if $x < {\mathit high}$ and 1 if $x +\ge {\mathit high}$, making the behavior identical to `step(high, x)`. The \emph{type} may be any of of \float, \color, \point, \vector, or \normal. For \color and \point-like types, the computations are performed component-by-component. \apiend -\apiitem{float {\ce smooth_linearstep} (float edge0, float edge1, float x, float eps) \\ -\emph{type} {\ce smooth_linearstep} (\emph{type} edge0, \emph{type} edge1, \emph{type} x, \emph{type} eps)} +\apiitem{float {\ce smooth_linearstep} (float low, float high, float x, float eps) \\ +\emph{type} {\ce smooth_linearstep} (\emph{type} low, \emph{type} high, \emph{type} x, \emph{type} eps)} \indexapi{smooth_linearstep()} -This function is strictly linear between ${\mathit edge0}+{\mathit eps}$ and ${\mathit edge1}-{\mathit eps}$ -but smoothly ramps to 0 between ${\mathit edge0}-{\mathit eps}$ and ${\mathit edge0}+{\mathit eps}$ -and smoothly ramps to 1 between ${\mathit edge1}-{\mathit eps}$ and ${\mathit edge1}+{\mathit eps}$. -It is 0 when $x \le {\mathit edge0}-{\mathit eps}$, and 1 if $x \ge {\mathit edge1}+{\mathit eps}$, -and performs a linear -interpolation between 0 and 1 when ${\mathit edge0} < x < {\mathit edge1}$. -For \color and \point-like types, the computations are -performed component-by-component. +This function returns 0 if $x < {\mathit low}-{\mathit eps}$, and 1 if +$x \ge {\mathit high}+{\mathit eps}$, is strictly linear between +${\mathit low}+{\mathit eps}$ and ${\mathit high}-{\mathit eps}$ but smoothly +ramps to 0 between ${\mathit low}-{\mathit eps}$ and ${\mathit low}+{\mathit eps}$ +and smoothly ramps to 1 between ${\mathit high}-{\mathit eps}$ and +${\mathit high}+{\mathit eps}$. For \color and \point-like types, the +computations are performed component-by-component. \apiend diff --git a/src/doc/stdlib.md b/src/doc/stdlib.md index 2fce1e09d..d3e53c870 100644 --- a/src/doc/stdlib.md +++ b/src/doc/stdlib.md @@ -458,36 +458,36 @@ the computations are performed component-by-component (separately for `x`, performed component-by-component (separately for $x$, $y$, and $z$). -`float` **`linearstep`** `(float edge0, float edge1, float x)`
*`type`* **`linearstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`) +`float` **`linearstep`** `(float low, float high, float x)`
*`type`* **`linearstep`** (*`type`* `low`, *`type`* `high`, *`type`* `x`) - : Returns 0 if `x` $\le$ `edge0`, and 1 if `x` $\ge$ `edge1`, and performs a - linear interpolation between 0 and 1 when `edge0` $<$ `x` $<$ `edge1`. - This is equivalent to `step(edge0, x)` when `edge0 == edge1`. For `color` + : Returns 0 if `x` $<$ `low`, and 1 if `x` $\ge$ `high`, and performs a + linear interpolation between 0 and 1 when `low` $<$ `x` $<$ `high`. + This is equivalent to `step(low, x)` when `low == high`. For `color` and `point`-like types, the computations are performed component-by-component (separately for $x$, $y$, and $z$). -`float` **`smoothstep`** `(float edge0, float edge1, float x)`
*`type`* **`smoothstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`) +`float` **`smoothstep`** `(float low, float high, float x)`
*`type`* **`smoothstep`** (*`type`* `low`, *`type`* `high`, *`type`* `x`) - : Returns 0 if `x` $\le$ `edge0`, and 1 if `x` $\ge$ `edge1`, and performs a - smooth Hermite interpolation between 0 and 1 when `edge0` $<$ `x` $<$ - `edge1`. This is useful in cases where you would want a thresholding - function with a smooth transition. + : Returns 0 if `x` $\le$ `low`, and 1 if `x` $\ge$ `high`, and performs a + smooth Hermite interpolation between 0 and 1 when `low` $<$ `x` $<$ + `high`. This is useful in cases where you would want a thresholding + function with a smooth transition. In the degenerate case where + `high` $\le$ `low`, the return value will be 0 if `x` $<$ `high` and 1 if + `x` $\ge$ `high`, making the behavior identical to `step(high, x)`. The *`type`* may be any of of `float`, `color`, `point`, `vector`, or `normal`. For `color` and `point`-like types, the computations are performed component-by-component. -`float` **`smooth_linearstep`** `(float edge0, float edge1, float x, float eps)`
*`type`* **`smooth_linearstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`, *`type`* eps) +`float` **`smooth_linearstep`** `(float low, float high, float x, float eps)`
*`type`* **`smooth_linearstep`** (*`type`* `low`, *`type`* `high`, *`type`* `x`, *`type`* eps) - : This function is strictly linear between `edge0 + eps` and `edge1 - eps` - but smoothly ramps to 0 between `edge0 - eps` and `edge0 + eps` - and smoothly ramps to 1 between `edge1 - eps` and `edge1 + eps`. - It is 0 when `x` $\le$ `edge0-eps,` and 1 if `x` $\ge$ `edge1 + eps`, - and performs a linear interpolation between 0 and 1 when - `edge0` < x < `edge1`. For `color` and `point`-like types, the - computations are performed component-by-component. + : This function returns 0 if `x` $<$ `low-eps`, and 1 if `x` $\ge$ `high + eps`, + is strictly linear between `low + eps` and `high - eps`, but smoothly + ramps to 0 between `low - eps` and `low + eps` and smoothly ramps to 1 + between `high - eps` and `high + eps`. For `color` and `point`-like types, + the computations are performed component-by-component. %## Noise functions diff --git a/src/include/OSL/dual.h b/src/include/OSL/dual.h index a7b5e4063..b3eb8685d 100644 --- a/src/include/OSL/dual.h +++ b/src/include/OSL/dual.h @@ -1311,27 +1311,29 @@ safe_fmod (const Dual& a, const Dual& b) -OSL_HOSTDEVICE OSL_FORCEINLINE float smoothstep(float e0, float e1, float x) { - if (x < e0) return 0.0f; - else if (x >= e1) return 1.0f; - else { - float t = (x - e0)/(e1 - e0); +OSL_HOSTDEVICE OSL_FORCEINLINE float smoothstep(float low, float high, float x) { + 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); } } -// f(t) = (3-2t)t^2, t = (x-e0)/(e1-e0) +// f(t) = (3-2t)t^2, t = (x-low)/(high-low) template -OSL_HOSTDEVICE OSL_FORCEINLINE Dual smoothstep (const Dual &e0, const Dual &e1, const Dual &x) +OSL_HOSTDEVICE OSL_FORCEINLINE Dual smoothstep (const Dual &low, const Dual &high, const Dual &x) { - if (x.val() < e0.val()) { - return Dual (T(0)); - } - else if (x.val() >= e1.val()) { - return Dual (T(1)); - } - Dual t = (x - e0)/(e1-e0); - return (T(3) - T(2)*t)*t*t; + if (x.val() >= high.val()) { + return Dual(T(1)); + } else if (x.val() <= low.val()) { + return Dual(T(0)); + } else { + Dual t = (x - low) / (high - low); + return (T(3) - T(2)*t)*t*t; + } } diff --git a/src/shaders/stdosl.h b/src/shaders/stdosl.h index 98ecbf8da..d32771749 100644 --- a/src/shaders/stdosl.h +++ b/src/shaders/stdosl.h @@ -319,73 +319,73 @@ point step (point edge, point x) BUILTIN; vector step (vector edge, vector x) BUILTIN; normal step (normal edge, normal x) BUILTIN; float step (float edge, float x) BUILTIN; -float smoothstep (float edge0, float edge1, float x) BUILTIN; +float smoothstep (float low, float high, float x) BUILTIN; -color smoothstep (color edge0, color edge1, color x) +color smoothstep (color low, color high, color x) { - return color (smoothstep(edge0[0], edge1[0], x[0]), - smoothstep(edge0[1], edge1[1], x[1]), - smoothstep(edge0[2], edge1[2], x[2])); + return color (smoothstep(low[0], high[0], x[0]), + smoothstep(low[1], high[1], x[1]), + smoothstep(low[2], high[2], x[2])); } -vector smoothstep (vector edge0, vector edge1, vector x) +vector smoothstep (vector low, vector high, vector x) { - return vector (smoothstep(edge0[0], edge1[0], x[0]), - smoothstep(edge0[1], edge1[1], x[1]), - smoothstep(edge0[2], edge1[2], x[2])); + return vector (smoothstep(low[0], high[0], x[0]), + smoothstep(low[1], high[1], x[1]), + smoothstep(low[2], high[2], x[2])); } -float linearstep (float edge0, float edge1, float x) { +float linearstep (float low, float high, float x) { float result; - if (edge0 != edge1) { - float xclamped = clamp (x, edge0, edge1); - result = (xclamped - edge0) / (edge1 - edge0); + if (low != high) { + float xclamped = clamp (x, low, high); + result = (xclamped - low) / (high - low); } else { // special case: edges coincide - result = step (edge0, x); + result = step (low, x); } return result; } -color linearstep (color edge0, color edge1, color x) +color linearstep (color low, color high, color x) { - return color (linearstep(edge0[0], edge1[0], x[0]), - linearstep(edge0[1], edge1[1], x[1]), - linearstep(edge0[2], edge1[2], x[2])); + return color (linearstep(low[0], high[0], x[0]), + linearstep(low[1], high[1], x[1]), + linearstep(low[2], high[2], x[2])); } -vector linearstep (vector edge0, vector edge1, vector x) +vector linearstep (vector low, vector high, vector x) { - return vector (linearstep(edge0[0], edge1[0], x[0]), - linearstep(edge0[1], edge1[1], x[1]), - linearstep(edge0[2], edge1[2], x[2])); + return vector (linearstep(low[0], high[0], x[0]), + linearstep(low[1], high[1], x[1]), + linearstep(low[2], high[2], x[2])); } -float smooth_linearstep (float edge0, float edge1, float x_, float eps_) { +float smooth_linearstep (float low, float high, float x_, float eps_) { float result; - if (edge0 != edge1) { + if (low != high) { float rampup (float x, float r) { return 0.5/r * x*x; } - float width_inv = 1.0 / (edge1 - edge0); + float width_inv = 1.0 / (high - low); float eps = eps_ * width_inv; - float x = (x_ - edge0) * width_inv; + float x = (x_ - low) * width_inv; if (x <= -eps) result = 0; else if (x >= eps && x <= 1.0-eps) result = x; else if (x >= 1.0+eps) result = 1; else if (x < eps) result = rampup (x+eps, 2.0*eps); else /* if (x < 1.0+eps) */ result = 1.0 - rampup (1.0+eps - x, 2.0*eps); } else { - result = step (edge0, x_); + result = step (low, x_); } return result; } -color smooth_linearstep (color edge0, color edge1, color x, color eps) +color smooth_linearstep (color low, color high, color x, color eps) { - return color (smooth_linearstep(edge0[0], edge1[0], x[0], eps[0]), - smooth_linearstep(edge0[1], edge1[1], x[1], eps[1]), - smooth_linearstep(edge0[2], edge1[2], x[2], eps[2])); + return color (smooth_linearstep(low[0], high[0], x[0], eps[0]), + smooth_linearstep(low[1], high[1], x[1], eps[1]), + smooth_linearstep(low[2], high[2], x[2], eps[2])); } -vector smooth_linearstep (vector edge0, vector edge1, vector x, vector eps) +vector smooth_linearstep (vector low, vector high, vector x, vector eps) { - return vector (smooth_linearstep(edge0[0], edge1[0], x[0], eps[0]), - smooth_linearstep(edge0[1], edge1[1], x[1], eps[1]), - smooth_linearstep(edge0[2], edge1[2], x[2], eps[2])); + return vector (smooth_linearstep(low[0], high[0], x[0], eps[0]), + smooth_linearstep(low[1], high[1], x[1], eps[1]), + smooth_linearstep(low[2], high[2], x[2], eps[2])); } float aastep (float edge, float s, float dedge, float ds) {