Useful 1-dimensional functions
January 4, 2023
Clamp #
The clamp function ensures that a value stays within a certain range. This range is usually between $[0, 1]$, but it can be any range. The function is defined as
$$ \text{ clamp(x) } = \max ( \min (x, 1), 0 ) $$
If the value of $x$ is greater than $1$, the min function ensures that the result is $1$. Likewise, if $x$ is less than $0$, the max function ensures that the result is $0$. Combining both functions results in the clamp function. The clamp function has the following graph.
This function is used in a lot of other functions, as we will see with the next function. The range $[0, 1]$ is usually parametrized to $[a, b]$.
Smoothstep #
The smoothstep function is an interpolation and clamping function. It is defined by the $S_1(x)$ cubic Hermite function $3x^2 - 2x^3$, which is shown in black in the plot below.
However, the value of $x$ is clamped between $[0, 1]$ before applying $S_1$, resulting in the smoothstep function which is shown in red in the plot. This is one of the most used functions in for example, computer graphics and animations.
It is usually defined at a starting point $a$ where the function is zero, and an endpoint $b$ where the function is one. This results in the complete definition of the smoothstep function:
$$ \text {smoothstep}(x, a, b) = 3t^2 - 2t^3, $$
where
$$ t = \text{ clamp }\left(\frac{x - a}{b - a}\right). $$
In the second equation $x$ is first scaled between $[0, 1]$ and then clamped.
Abs #
The abs function returns the absolute value of a number. In other words, it always returns a positive number. It is defined as
$$ \text { abs }(x) = |x| = \begin{align}\begin{cases}x \quad & \text{ if}\ x \geq 0 \\ -x \quad &\text{otherwise} \end{cases}\end{align} $$
The graph of the function is rather simple, but don’t let that simplicity deceive you. It is incredibly useful!
Another way of thinking about it is that it duplicates the domain of the function to reflect what is happening on the other half of the domain.
Fract #
The fract function returns the fractional part of a number. This seemingly simple operation leads the fascinating concept of domain repetition. One of the definitions of this function is with the modulo function:
$$ \text{ fract }(x) = \text{mod}(x, 1) $$
The function rises linearly from 0 to 1, before doing it again, and again…
In the next section we will look at an example that combined all of the function that we have looked into.
Repeating pulse #
In this example we will create a function that has a sharp, but smooth, pulse at an interval. This pattern is repeating forever.
To do so, we will begin with the smoothstep function. Instead of having it start at zero, and gradually increase to one, we want to have this the other way around. This can be done by swapping $a$ and $b$ giving:
$$ \text{ smoothstep }(x, 1, 0) $$
The graph below shows what happens if we change the value of $a$ between $[0.1, 1.0]$. Notice how the curve becomes much sharper.
The next thing we will do to shape the pulse is to take the absolute value of $x$ before we apply the smoothstep function:
$$ \text{ smoothstep }(|x|, 1, 0) $$
This mirrors what is happening on the positive $x$ side to the negative side, which results in the pulse shape as can be seen below.
We then offset the function such that the pulse sits at $x=0.5$. This is done by translating the domain, like so:
$$ \text{ smoothstep }(|x-0.5|, 1, 0) $$
All in all, our pulse function now looks like this.
To repeat the pulse an infinite number of times, the fract function is applied to $x$:
$$ \text{ smoothstep }(|\text{fract}(x)-0.5|, 1, 0) $$
Which is the last function that we need to create the pulse function.
Application: Anti-aliased grid #
Using this pulse function turns out to be useful for rendering computer graphics. The use of the smoothstep function helps with drawing a smooth anti-aliased image. As an example, I have implemented the repeating pulse function to render a grid in two dimensions. Note that the width of the pulse is only a few pixels! The image is rotating to emphasize the aliasing.
The shader is defined with the following code:
1mat2 rot(float angle)
2{
3 float c = cos(angle), s = sin(angle);
4 return mat2(c,s,-s,c);
5}
6
7void mainImage( out vec4 fragColor, in vec2 fragCoord )
8{
9 // Normalized pixel coordinates (from 0 to 1)
10 vec2 uv = (2.0*fragCoord-iResolution.xy)/iResolution.y;
11
12 // Rotate the grid to show aliasing.
13 uv *= rot(0.2*iTime);
14
15 // Time varying pixel color
16 vec3 col = vec3(1);
17
18 // Scale the grid so we have more lines.
19 float gridSize = 4.;
20
21 // Grid lines size independent of the scale of uv.
22 float gridLineSize = fwidth(uv.x) * gridSize;
23
24 // Domain repetition and offsetting.
25 vec2 repeatedUV = abs(fract(gridSize * uv) - 0.5);
26
27 // Smoothstep to get the pulse.
28 vec2 grid = smoothstep(gridLineSize, 0.0, repeatedUV);
29
30 // Add the grid line colors to the output color.
31 vec3 gridLineColor = vec3(0.8);
32 col = mix(gridLineColor, col, clamp(grid.x + grid.y, 0.0, 1.0));
33
34 // Output to screen
35 fragColor = vec4(col,1.0);
36}
Note that in GLSL the smoothstep function has the parameters swapped, so instead of the definition in this post, it is smoothstep(a,b,x)
.