Skip to content

Commit

Permalink
docs: Fix specification clarity of smoothstep
Browse files Browse the repository at this point in the history
It was pointed out on Slack by Arnon Marcus that the spec's
description of smoothstep was ambiguous about the behavior when
edge0==edge1==x: does it return 0 or 1 here?

The implementation returned 1, which I think is the "correct" behavior
in the sense that when edge0==edge1, the results should match the
step() function with that edge.

* Fix the descriptions in the spec to match the implementation and
  what I think is the best behavior.

* Also change paremter names edge0/edge1 to low/high, which I think
  is much more clearly self-documenting.

Signed-off-by: Larry Gritz <[email protected]>
  • Loading branch information
lgritz committed Aug 19, 2024
1 parent 9f4aa5e commit c95f67a
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 70 deletions.
37 changes: 18 additions & 19 deletions src/doc/languagespec.tex
Original file line number Diff line number Diff line change
Expand Up @@ -3488,23 +3488,23 @@ \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 < {\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.

Expand All @@ -3513,17 +3513,16 @@ \section{Pattern generation}
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


Expand Down
30 changes: 14 additions & 16 deletions src/doc/stdlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,36 +458,34 @@ 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)` <br> *`type`* **`linearstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`)
`float` **`linearstep`** `(float low, float high, float x)` <br> *`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)` <br> *`type`* **`smoothstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`)
`float` **`smoothstep`** `(float low, float high, float x)` <br> *`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
: Returns 0 if `x` $<$ `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.

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)` <br> *`type`* **`smooth_linearstep`** (*`type`* `edge0`, *`type`* `edge1`, *`type`* `x`, *`type`* eps)
`float` **`smooth_linearstep`** `(float low, float high, float x, float eps)` <br> *`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
Expand Down
70 changes: 35 additions & 35 deletions src/shaders/stdosl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit c95f67a

Please sign in to comment.