B-splines in C2
Jon Maiga, 2020-06-29
After finishing a larger project I wanted to focus on something smaller and randomly picked up an old project where I used a b-spline setup to generate pretty smooth noise. For my own understanding, this post will go through the basics of b-splines and how to calculate certain coefficients.
B-splines use piecewise cubic polynomials that each interpolate between two consecutive points. To do this it requires additionally two neighboring points. We are only going to consider points with a constant space between them, or in the bigger picture, uniform b-splines.
I will denote the piecewise cubic curve that interpolates from Pi to Pi+1 by Ci(u) where 0≤u≤1. Note that I am using the term “interpolate” wrong since the curve is not necessarily passing through the control points.
What makes b-splines smooth is that we can get any wanted continuity, for C2 we have:
- Positional continuity, interpolating will assure that neighbor points align C1(1)=C2(0)
- Tangential continuity, interpolating will assure that the slopes of neighbor points align C1′(1)=C2′(0)
- Curvature continuity, interpolating will assure that the curvature of neighbor points align C1′′(1)=C2′′(0)
To interpolate between the points P1 and P2 in C2 we additionally require the point to the left of P1 and right of P2, so we have to consider the four points P0,P1,P2,P3.
By using more control points and higher degree of the polynomials we can achieve any smoothness. For example, for C3 continuity requires fourth degree polynomials and five control points. This investigation will only consider C2.
The form of the cubic curve is C1(u)=a(u)P0+b(u)P1+c(u)P2+d(u)P3 where a(u),b(u),c(u),d(u) are weight functions for each control point those weight functions are called basis functions, which explains the name b(asis)-splines.
Basis functions
Each basis function takes a 0≤u≤1 and outputs the weight of the corresponding point. The general form of our cubic basis function is
B(u)=k3u3+k2u2+k1u+k0
Here we will focus on one piecewise function, C1. Since we have four control points, P0,P1,P2,P3 we get four corresponding basis functions
a(u)=a3u3+a2u2+a1u+a0
b(u)=b3u3+b2u2+b1u+b0
c(u)=c3u3+c2u2+c1u+c0
d(u)=d3u3+d2u2+d1u+d0
Each basis function will output the weight between 0 and 1, multiplying this with the corresponding point and summing all those gives us our cubic curve between the points P1 and P2.
C1(u)=a(u)P0+b(u)P1+c(u)P2+d(u)P3
Deriving the basis coefficients
Now we need to figure out what the 16 coefficients are (a0−a3,...,d0−d3). We have 16 unknown coefficients, so we will try to setup and solve a system of 16 equations in this step.
We already stated three constraints for the C2 guarantee,
C1(1)=C2(0)
C1′(1)=C2′(0)
C1′′(1)=C2′′(0)
Expanding the first of the three constraints we get
a(1)P0+b(1)P1+c(1)P2+d(1)P3+0⋅P4=0⋅P0+a(0)P1+b(0)P2+c(0)P3+d(0)P4
We can write this as
a(1)P0=0⋅P0
b(1)P1=a(0)⋅P1
c(1)P2=b(0)⋅P2
d(1)P3=c(0)⋅P3
0⋅P4=d(0)⋅P4
Now we cancel the points and expands each basis function:
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧a3+a2+a1+a0=0b3+b2+b1+b0=a0c3+c2+c1+c0=b0d3+d2+d1+d0=c00=d0
We end up with 5 equations, so 11 to go! We repeat the exact same procedure for the first and second derivatives and we get 10 additional equations. First we take the derivatives, e.g. for a(u) we get:
a′(u)=3a3u2+2a2u+a1
a′′(u)=6a3u+2a2
Then take the same steps as for before and from the first derivatives we end up with
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧3a3+2a2+a1=03b3+2b2+b1=a13c3+2c2+c1=b13d3+2d2+d1=c10=d1
And from the second derivatives
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧6a3+2a2=06b3+2b2=2a26c3+2c2=2b26d3+2d2=2c20=2d2
Now we have 15 equations, but 16 unknowns so we need to add another. For the last one, we will add a new constraints that will keep our cubic in check, namely the sum of the weights of the of the basis functions should always equal to one, a(u)+b(u)+c(u)+d(u)=1. To achieve this we can consider the case when u=0, then we are only left with a0+b0+c0+d0 so clearly this has to add up to 1.
{a0+b0+c0+d0=1
After adding this last equation we can finally solve the equation system and retrive the coefficients. I was lazy and put it into Mathematica and got the following back
[a3,…,a0]=61[−1,3,−3,1]
[b3,…,b0]=61[3,−6,0,4]
[c3,…,c0]=61[−3,3,3,1]
[d3,…,d0]=61[1,0,0,0]
So we end up with the following set of cubics
a(u)=61(−u3+3u2−3u+1)=61(1−u)3
b(u)=61(3u3−6u2+4)
c(u)=61(−3u3+3u2+3u+1)
d(u)=61(u3)
and finally plugging this into C gives us
Ci(u)=61((1−u)3Pi−1+(3u3−6u2+4)Pi+(−3u3+3u2+3u+1)Pi+1+u3Pi+2)
Result
Plotting the individual basis functions in mathematica, you can see how the individual basis functions seamlessly goes from one another, a(0)=b(1), b(0)=c(1), c(0)=d(1) and d(0)=a(1). One can also guess that the sum of all four functions at any u is 1, just like our constraint wanted.

Here I am plotting three piecewise curves, to verify that it behaves as expected.

What is next?
The idea is to use b-splines to smoothen noise and see how it holds up against more common methods such as Perlin and Simplex noise. I decided to split each part into different posts and bring it all together in the last.
I used Uniform Cubic B-Spline Curves as a reference, it’s gives a really friendly walk-through!
Written with StackEdit.