2

Consider the simple shader below (head over to shadertoy.com/new and paste the code to try it out).

Basically, I'm trying to figure out if it is possible to tweak the dot() version to get the exact same result for these two function calls:

smoothstep( 0.0, r * r, dot(d, d) )
smoothstep( 0.0, r,     length(d) )

Two circles are drawn using two well-known methods. Reading tutorials on the net, you learn that you can use the length() function to draw a circle. You also learn that it is quite expensive, so a more optimized version is presented where the dot() function is used instead. (In my world, an optimized version of something should produce the same result.)

Great. But I cannot find an explanation for the relationship between the two. Sometimes the result from dot() is, for some reason, multiplied by 4.0 (see Book of Shaders), giving similar but not identical output.

As you can see, step() yields identical circles whereas smoothstep() does not.

Typical GLSL circles :)

Is it possible to get the exact same output from smoothstep() using some math?

Shader example

float circle_using_length(vec2 position, float radius) {
    vec2 d = position - vec2(0.5);
    return 1.0 - step(radius, length(d));
}

float circle_using_dot(in vec2 position, in float radius) {
    vec2 d = position - vec2(0.5);
    return 1.0 - step(radius * radius, dot(d, d));
}

float smooth_circle_using_length(vec2 position, float radius) {
    vec2 d = position - vec2(0.5);
    return 1.0 - smoothstep(0.0, radius, length(d));
}

float smooth_circle_using_dot(in vec2 position, in float radius) {
    vec2 d = position - vec2(0.5);
    return 1.0 - smoothstep(0.0, radius * radius, dot(d, d) /* magic needed here */);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord/iResolution.x;
    
    vec3 col = vec3(0.0);
    col += circle_using_length(uv + vec2(0.4, 0.05), 0.05);
    col += smooth_circle_using_length(uv + vec2(0.4, 0.2), 0.1);
    
    col += circle_using_dot(uv + vec2(0.2, 0.05), 0.05);
    col += smooth_circle_using_dot(uv + vec2(0.2, 0.2), 0.1);
    
    fragColor = vec4(col,1.0);
}
l33t
  • 18,692
  • 16
  • 103
  • 180
  • You'll get the same result with `smoothstep(0.0, radius, sqrt(dot(d, d)))`. `smoothstep` is not a linear function. See [`smoothstep`](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/smoothstep.xhtml). `smoothstep(0, a, b)` != `smoothstep(0, a*a, b*b)` – Rabbid76 Dec 31 '21 at 08:03

2 Answers2

3
smoothstep(0.0, radius, length(d));

returns the same as

smoothstep(0.0, radius, sqrt(dot(d, d)));

However it is not equal

smoothstep(0.0, radius * radius, dot(d, d));

That is, because smoothstep is not a linear function and therefore smoothstep(0, a, b) is not equal to smoothstep(0, a*a, b*b).

See smoothstep:

t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);

(a*a - 0) / (b*b - 0) is not equal (a - 0) / (b - 0).

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
1

They're different because x2 is not linear with respect to x.

Let's say that x is the radius of the circle. (x/2) is halfway across the circle. Well, (x/2)2 is (x2)/4. This means that when the distance is halfway from the center to the edge, the dot(d, d) version will only act like it is one quarter of the way from the center to the edge.

Using the square of the distance (what you get with dot) is only valid if you're trying to test if a point is within a circle, not where it is within the circle.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • So this means a smoothed circle will never look the same if we use `dot` instead of `length`? – l33t Dec 31 '21 at 00:12