-
Notifications
You must be signed in to change notification settings - Fork 368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: Fix specification clarity of smoothstep #1851
base: main
Are you sure you want to change the base?
Conversation
There would still be ambiguity when
This implementation would return
Both implementations comply with the same updated specification as proposed, and yet - they would return different results. |
It was clearly the case that the prior Oh, I see, you're talking about cases where they intentionally pass high < low? |
Would it help if the spec explicitly spelled out the code equivalent to show the order of comparisons?
I think the desired behavior when low==high, matching step() that has only one threshold value, was the clear choice. I admit that I haven't thought as carefully about what is the best behavior when somebody passes the nonsensical high < low. Is the above code adequate? I'm thinking maybe not. How about switching the order of comparisons:
I think that makes it monotonic no matter what the relative order of low and high, right? We can say in this case that if high < low, it also falls back to matching An alternative specification would be to implicitly swap low and high if low > high. In other words, you just have two thresholds, specified in either order, and it's 0 when lower than the lowest threshold, and interpolates to 1 as it goes from the lower to the higher edge threshold. I like the elegance of that, but I don't see how to do it without adding expense, since it would require an additional conditional to detect the low>high case. |
Swapping would not only make it more expensive, it would also make it inconsistent - the "non sensical" case(s) occur naturally and implicitly when using image texture maps (even very simple ones). You could make an argument that this function is not aimed to be used with images, but that would not be a very strong argument. I am not sure there necessarily even is a right or wrong ordering (I haven't thought hard on that and have no stake in it), what matters is that the specification states whichever is chosen to be the desired behavior, as opposed to leaving it to interpretation or arbitrary implementation choice. |
Yeah, I think the simplest solution is just to test |
Updated: Swapped the relative order of the >=high and <low tests in order to make the high<low case also be sane. |
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]>
Comments? |
Last chance for objections... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside from a typo in stdlib.md, LGTM
: 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be x
low
to match the .tex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still feel the need to point out that any phasing of the form " returns 0 if x something low, and 1 if x something high" is going to remain ambiguous, because these are 2 separate checks against 2 separate inputs(!) which necessarily makes it possible for both conditions to be true - regardless of what comparison is used - at which point the phrasing is not specifying what should happen (which of the 2 satisfied conditions should determine the output).
Following a conversation on ASWF slack this PR was posted to OSL refining the edge cases for `smoothstep()`. AcademySoftwareFoundation/OpenShadingLanguage#1851 This PR aligns the GLSL/MSL code with this change.
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
andif 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: