Bezier Curves

Bezier Curves are very useful and have many applications, but what do we need to know to implement one ourselves? Well, you can keep reading, or you can just download the unity package that I made.

Lerp (Linear Interpolation)

Before we start we need to know what Lerp is. If you want to understand it more mathematically you can check the description in wikipedia. It’s very simple. Lerp is a function which takes three parameters – a, b and t, where a is start value, b is end value, and t is time (between 0 and 1). The function is defined like this

Lerp

In code it looks like this

float Lerp(float a, float b, float t)
{
    return (1f - t) * a + t * b;
}

Note that we can use the same logic if we want to lerp vectors

Vector3 Lerp(Vector3 a, Vector3 b, float t)
{
    return (1f - t) * a + t * b;
}

Bezier Curves

Now that we know what lerp is we can start.
A bezier curve is also defined by a function, but a function of higher degree (cubic to be precise). For a cubic curve we need 4 points (control points). These 4 points control the shape of the curve. Lets call the points p0, p1, p2 and p3. p0 is called start point, p1start tangent, p2end tangent, and p3end point. Lets imagine that the points are positioned like this:

Control Points

The slider at the bottom represents the t value.
In order to construct the function we are going to go through some steps. In each step we are going to make some lerps, and at the end we will combine these lerps.

Step One

We are going to make 3 lerps – between p0 and p1, between p1 and p2, and between p2 and p3.

Vector3 a = Lerp(p0, p1, t);
Vector3 b = Lerp(p1, p2, t);
Vector3 c = Lerp(p2, p3, t);

It looks like this

Lerp One

Now lets draw lines between a and b, and between b and c

Lerp One Plus Lines

Step Two

Again we are going to make some lerps – this time between a and b, and between b and c

Vector3 d = Lerp(a, b, t);
Vector3 e = Lerp(b, c, t);

Lerp Two

Now lets draw a line between d and e

Lerp Two Plus Lines

Step Three

Now all we need to do is to make one last lerp between d and e, so we can find the point on the curve at any given time t.

Vector3 pointOnCurve = Lerp(d, e, t);

Lets draw the point

Lerp Three

And now let’s draw the bezier curve

Draw Curve

The Function

Now if we combine all these lerps, we get the final function that defines any bezier curve by 4 points and a t value

Vector3 GetPointOnBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
    Vector3 a = Lerp(p0, p1, t);
    Vector3 b = Lerp(p1, p2, t);
    Vector3 c = Lerp(p2, p3, t);
    Vector3 d = Lerp(a, b, t);
    Vector3 e = Lerp(b, c, t);
    Vector3 pointOnCurve = Lerp(d, e, t);

    return pointOnCurve;
}

However this function is not very cheap. With a little math we can simplify the function and thus improve the performance.

Optimizations

Mathematically the function looks like this

Point On Curve

With a little calculations on paper we can simplify it to this

Optimized

So the final function in code can be written like this

Vector3 GetPointOnBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
    float u = 1f - t;
    float t2 = t * t;
    float u2 = u * u;
    float u3 = u2 * u;
    float t3 = t2 * t;

    Vector3 result =
        (u3) * p0 +
        (3f * u2 * t) * p1 +
        (3f * u * t2) * p2 +
        (t3) * p3;

    return result;
}

Bezier Splines (Bezier Paths)

What if we want to create a more complex curve?
There are two options:

  • Use a higher degree function
  • Combine (chain) cubic bezier curves

The first option is not very good, because every time we increase the degree of the function, we create more job for the CPU (we increase the number of calculations).
The second one is more widely used, and it can be explained like this:

Bezier Spline

p3 is the end of the first bezier curve and the start of the second one, and so on.

Advertisements

8 thoughts on “Bezier Curves”

  1. Hey!

    I’m using your CubicBezierCurve scripts but I’ve come across a problem. You see, I’m using these scripts to make camera rails, for cutscenes and also for corridors that follow the player.

    The problem I’ve come across is that the normalisedTime on the curve slows down as it approaches the end points. Similarly, when the handles are zero’d out (x = 0, y = 0, z = 0) every point along the curve causes the normalisedTime to slow drastically as it reaches its point.

    My grasp of mathematics is very flimsy to say the least, so I’m struggling to find a way to re-create your scripts without this slowing and accelerating problem.

    Any help would be very much appreciated as I’m seriously stuck for solutions…

    Thank you!

    1. Can you tell me how exactly you are interpolating the curve? The normalizedTime can’t really slow down, I don’t think I understand your question. 0 is the start point of the curve, and 1 is the end point of the curve.

      1. I believe I need equidistant points on the curve.

        That is, 0.2f normalisedTime is equidistant from 0.3f normalisedTime as 0.0f is from 0.1f normalisedTime.

      2. Is it possible to leave a picture with a comment or something? Perhaps an e-mail address? It’d be much easier to explain if I could use visuals.

  2. Hi Denis,

    cool stuff! Thank you.

    I’m having a problem similar to Bezier curves. I require a dynamic function that calculate from Linear to SmootherStep. The linear is MathF.Lerp(a,b,t) and the SmootherLerp()

    float SmootherStep(a, b, t) {
    t = t * t * t * (t * (6 * t – 15) + 10);
    return lerp(a, b, t);
    }

    The new function , call it maybe SmootherDynamic(a,b,t,n), should interpolate from Linear to the SmootherLerp() by parameter n. I have reworked this to:

    float SmootherDynamic(p0, p3, t, x) {
    x = (1 – x) * (p3 * (1.0 / 3.0));
    var p1 = x;
    var p2 = p3 – x;

    var a = lerp(p0, p1, t);
    var b = lerp(p1, p2, t);
    var c = lerp(p2, p3, t);
    var d = lerp(a, b, t);
    var e = lerp(b, c, t);
    var pointOnCurve = lerp(d, e, t);
    return pointOnCurve;
    }

    If n > 1 (max 2) the result starts to badly override. I would like to have something like this:
    https://forum.unity.com/attachments/c-3st7cxcaehfbe-jpg.277611/

    Any ideas about? I want to use it for splines, but they should not override.

    Thanks and regards, Roger

    1. Hmm, I don’t understand what you want to achieve mate.
      The SmootherStep takes the t parameter and remaps it, but the remapping is incorrect I think, because for t=-1 it remaps to -31, and when t=1 it remaps to 1.
      The lerp, however, expects values in range [-1, 1]. The remapping must also be in that range.
      Also, what exactly are you using the SmootherStep function for?

      EDIT: Actually, nevermind! It’s in range [0, 1], I am talking total nonsense.

      1. Hi,

        I want to have a smooth change from Lerp(Linear) to SmootherStep() by an extra parameter (,,,x).

        SmootherDynamic(0, 1, 0.25, 0) should results like linear function like Lerp() = 0.25
        SmootherDynamic(0, 1, 0.25, 1) should results as SmootherStep() = 0.15625

        The GetPointOnBezierCurve(p1, p2, p3, p4, t) describes the shortest way between the given points based on a linear lerp(). But in some cases I’d like to have a “longer way” between these given parameter p1, p2, p3, p4, BUT without changing the parameter p1, p2, p3, p4 itself.

        For example: This is exactly the difference between x=0.003 Michael Schumacher on the perfect way and other more bad drivers following the static Bezier way points x=0.0156.

        So, I want to have a smooth change from Lerp(Linear) to SmootherStep() by an extra parameter.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s