C++简单实现Bézier曲线

贝塞尔曲线有着很多特殊的性质在图形设计和路径规划中应用都非常广泛。贝塞尔曲线完全由其控制点决定其形状,n个控制点对应着n-1阶的贝塞尔曲线,并且可以通过递归来定义。本篇文章的重点在于对Bézier Curve的理解以及用OpenGL绘制Bézier Curve

〄 Bézier Curve·1阶

给定两个已知坐标的控制点P0,P1,那么1阶贝塞尔曲线可以用一个关于t的参数方程来描述:

很显然,这表示的即是两控制点之间的线段,而每一个则表示线段上的一个点,这个关于的动点我们姑且先称之为贝塞尔动点,贝塞尔动点遍历t\in[0,1]便构成贝塞尔曲线。

⚠️事实上贝塞尔曲线本身跟t是无关的,t只是方便描述曲线上的点而存在的,可以认为是画出贝塞尔曲线的画笔。

〄 Bézier Curve·2阶

给定三个已知坐标的控制点P0,P1,P2,那么这时候有两个线段,我们可以先求出这两个线段上的贝塞尔动点P0',P1',然后求两个贝塞尔动点线段上的贝塞尔动点(三个贝塞尔动点关联同一个t),第三个贝塞尔动点将构成2阶贝塞尔曲线:

那么2阶贝塞尔曲线的参数方程即为

我们可以看到这是一个递归的过程。

〄 Bézier Curve·3阶

同理3阶贝塞尔曲线的方程为

下图是当时的贝塞尔动点线段以及相应的3阶贝塞尔曲线

事实上对于贝塞尔曲线参数方程中每个控制点前的系数都是一个关于的函数,而这些系数函数我们可以类比杨辉三角(贝塞尔曲线的递归过程本质上就是个杨辉三角)

上图只是一个三阶的杨辉三角,我们可以看到每个控制点所在的叶子节点到根结点(最上面的那个点)的路径乘积乘以其所在叶子节点上的数字即为其系数函数。比如说P2,我们随便取一条到根结点的路径(不管哪条路径,乘积都是一样的),将路径上的表达式进行相乘得到t^2(1-t),再乘以其叶子节点3得到3t^2(1-t),对照B3(t)即为P2的系数。

〄 Bézier Curve· n阶

注意到上图中的杨辉三角其实就是的展开式[t+(1-t)]^n,因此我们其实可以直接写出n阶贝塞尔曲线Bn(t)中控制点Pi(0 ≤ i < n)的系数为

上式又称为n阶的波恩斯坦基底多项式。


接下来就到了激动人心的绘制阶段。不过其实理论内容已经差不多了,剩余的只是C++编程,OpenGL运用。

代码里没有什么高深的思想,没有啥优化(概括:懒),就是实现了一个点类,然后不断递归求出新的控制点坐标。当然OpenGL不能绘制连续的曲线,因此我们需要化曲为直。这时候就派上用场了,我将从中均匀地取100个出来分别绘制相应的点,最后将这些点连起来即可。

⚡︎ 算法说明

  1. 实现了一个点类Point,用于存储一个点的坐标(double x,y),并重载了*、+符号,使坐标可以直接与浮点数进行加乘运算

  2. 首先需要用户输入需要绘制的贝塞尔曲线的阶数n,随后输入(n+1)个控制定点的坐标,最后指定t0来绘制出相应的中间迭代控制点

  3. 对每个点的坐标都/100来满足实际绘图坐标的范围(绘制函数传入的坐标范围0\leq|x|,|y|\leq 1)

  4. vector<vector<Point>> control_points存储参数所对应的每一次迭代的控制点坐标,control_points[i]存储的是第次迭代的控制点坐标

  5. P^i_j表示第i次迭代的第j个控制点,那么控制点的迭代公式:

  6.  绘制贝塞尔曲线是化曲为直,首先均匀取了100个t\in [0,1],画出每个t所对应的位于贝塞尔曲线上的点,最后将每个点连起来
  7. vector<vector<Point>> middle_points存储的是每个t迭代过程中的中间控制点坐标以及最后所要连起来的100个点
  8. 绘制过程:t0对应的迭代过程控制点连线→贝塞尔曲线连线→t0在贝塞尔曲线上对应的点

其余就看代码注释吧~


C☺DE

#include <glew.h>
#include <glfw3.h>
#include <vector>
#include <iostream>

using namespace std;

//重载了*,+运算符的点类
class Point
{
public:
    double x, y;

    Point(double _x, double _y) : x(_x), y(_y) {};

    inline Point operator*(const double &t) const
    {
        return Point(t * this->x, t * this->y);
    }

    inline Point operator+(const Point &rhs) const
    {
        return Point(this->x + rhs.x, this->y + rhs.y);
    }
};

int main(void)
{
    int n;
    cout << "Bézier Curve的阶数:";
    cin >> n;

    //control_points存储指定t的每一次迭代控制点,middle_points存储绘制过程中的每一次迭代控制点(t会变)
    vector<vector<Point>> control_points(n + 1), middle_points(n + 1);
    double x, y, t0;
    cout << "请输入" << n + 1 << "个控制点坐标(0 ≤ |x|,|y| ≤ 100):" << endl;
    for (int i = 0; i < n + 1; ++i)
    {
        cin >> x >> y;
        control_points[0].emplace_back(Point(x / 100, y / 100));
    }

    //输入指定t,可绘制出t0时每一次的迭代控制点
    cout << "t0 = ";
    cin >> t0;
    //计算t0时每一次的迭代控制点
    for (int i = 1; i < n + 1; ++i)
        for (int j = 0; j < n + 1 - i; ++j)
            control_points[i].emplace_back(control_points[i - 1][j] * (1 - t0) + control_points[i - 1][j + 1] * t0);
    //求100个t所对应的点
    middle_points[0] = control_points[0];
    for (int k = 0; k < 101; ++k)
    {
        double t = k / 100.0;
        for (int i = 1; i < n; ++i)
        {
            middle_points[i].clear();
            for (int j = 0; j < n + 1 - i; ++j)
                middle_points[i].emplace_back(middle_points[i - 1][j] * (1 - t) + middle_points[i - 1][j + 1] * t);
        }
        middle_points[n].emplace_back(middle_points[n - 1][0] * (1 - t) + middle_points[n - 1][1] * t);
    }
    //开始绘制,初始化glfw库
    if (!glfwInit())
        return -1;
    //创建窗口以及上下文
    GLFWwindow *window = glfwCreateWindow(800, 600, "Bézier Curve", NULL, NULL);
    if (!window)
        glfwTerminate();
    //建立当前窗口的上下文
    glfwMakeContextCurrent(window);
    //循环绘制使其停留在屏幕上
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        //背景颜色
        glClearColor(0.2, 0.1, 0.2, 1);
        glClear(GL_COLOR_BUFFER_BIT);

        //绘制t0时每次迭代的控制点连线
        glLineWidth(5);
        for (int i = 0; i < n; ++i)
        {
            //使每次迭代的控制点连线颜色不同
            glColor3f(1.0 * (n - 1 - i) / (n - 1), 1.0 * i / (n - 1), 0);
            glBegin(GL_LINE_STRIP);
            for (auto &p: control_points[i])
                glVertex2f(p.x, p.y);
            glEnd();
        }
        //绘制贝塞尔曲线
        glColor3f(0, 0, 1);
        glBegin(GL_LINE_STRIP);
        for (auto &p: middle_points[n])
            glVertex2f(p.x, p.y);
        glEnd();

        //绘制出贝塞尔曲线上t0所对应的点
        glColor3f(0, 0.7, 0.7);
        glPointSize(20);
        glEnable(GL_POINT_SMOOTH);
        glBegin(GL_POINTS);
        glVertex2f(control_points[n][0].x, control_points[n][0].y);
        glEnd();

        glfwSwapBuffers(window);
    }
    glfwTerminate();
    return 0;
}

⚛︎ 5 order Bezier curve (t0=0.5)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
zier曲线是一种数学曲线,由法国数学家Pierre Bézier在20世纪60年代提出。它通过控制点和控制向量来定义曲线的形状。Bézier曲线的画线原理如下: 1. 控制点:Bézier曲线由两个或多个控制点组成。对于二次Bézier曲线,有三个控制点,分别称为起点、控制点和终点。对于三次Bézier曲线,有四个控制点,分别称为起点、两个控制点和终点。 2. 连接直线:Bézier曲线的起点和终点之间可以通过直线段连接。这些直线段被称为端点切线。如果起点和终点之间存在多个控制点,则通过这些控制点来调整曲线的形状。 3. 控制向量:每个控制点都有一个关联的控制向量。控制向量定义了曲线在该控制点处的切线方向和长度。调整控制向量的长度和方向可以改变曲线在该位置的弯曲程度和形状。 4. 插值计算:根据Bézier曲线的插值计算公式,通过控制点和控制向量的组合,可以计算出曲线上的每个点的坐标。这些计算基于参数t(取值范围为0到1),通过在控制点之间进行插值计算来确定曲线上的点。 5. 平滑性:Bézier曲线的平滑性由控制点和控制向量的位置关系决定。当控制向量与端点切线相切时,曲线在该位置处是平滑的。通过调整控制向量的位置,可以获得不同程度的平滑性。 总结来说,Bézier曲线通过控制点和控制向量来定义曲线的形状,并使用插值计算来确定曲线上的点。调整控制点和控制向量的位置和长度可以改变曲线的形状和平滑性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值