GAMES101作业解答-作业04-贝塞尔曲线 (Bézier Curve)

GAMES101作业解答-作业04-贝塞尔曲线 (Bézier Curve)

1. 什么是贝塞尔曲线

  • 在数学的数值分析领域中,贝塞尔曲线(英语:Bézier curve)是计算机图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝兹曲面,其中贝兹三角是一种特殊的实例。
  • 贝塞尔曲线于1962年,由法国工程师皮埃尔·贝兹(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由保尔·德·卡斯特里奥于1959年运用德卡斯特里奥算法开发,以稳定数值的方法求出贝塞尔曲线。以上来自维基百科维基

2. 贝塞尔曲线分类

  • 贝塞尔曲线的构建可分为:线性贝塞尔曲线、二次、三次贝塞尔曲线、四次以及五次贝塞尔曲线,和推广到一般的贝塞尔曲线表达式,去模拟和表示更加复杂的曲线。贝塞尔曲线的详细讲解可看这个blog地址,作者写得很好。

3. 作业要求

  • 就是在屏幕空间中通过鼠标去画点,Bézier 曲线是一种用于计算机图形学的参数曲线。在本次作业中,你需要实现 de Casteljau 算法来绘制由 4 个控制点表示的 Bézier 曲线 (当你正确实现该 算法时,你可以支持绘制由更多点来控制的 Bézier 曲线)。

4. 问题求解

// 原来代码里面的实现方法 
void naive_bezier(const std::vector<cv::Point2f> &points, cv::Mat &window) 
{
    auto &p_0 = points[0];
    auto &p_1 = points[1];
    auto &p_2 = points[2];
    auto &p_3 = points[3];
    auto &p_4 = points[4];

    for (double t = 0.0; t <= 1.0; t += 0.001) 
    {
        // 四次贝塞尔曲线
//        auto point = std::pow(1 - t, 3) * p_0 + 3 * t * std::pow(1 - t, 2) * p_1 +
//                 3 * std::pow(t, 2) * (1 - t) * p_2 + std::pow(t, 3) * p_3;
        // 五次贝塞尔曲线
        auto point = std::pow(1 - t, 4) * p_0 + 4 * t * std::pow(1 - t, 3) * p_1 +
                     6 * std::pow(t, 2) * std::pow((1 - t),2) * p_2 + 4*std::pow(t, 3) *(1-t)*p_3+std::pow(t,4)*p_4;
        window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
    }
}
  • 所画结果为(我换成了5次贝塞尔曲线):
// 利用递归实现贝塞尔曲线
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
    // recursive Bezier algorithm. 递归贝塞尔曲线实现算法
    for (double t = 0.0; t <= 1.0; t += 0.0001)
    {
        cv::Point2f point = recursive_bezier(control_points, t);
        window.at<cv::Vec3b>(point.y, point.x)[1] = 255;

        //anti-aliasing
        float x = point.x - std::floor(point.x);
        float y = point.y - std::floor(point.y);
        int x_flag = x < 0.5f ? -1 : 1;
        int y_flag = y < 0.5f ? -1 : 1;

        // 距离采样点最近的4个坐标点
        cv::Point2f p00 = cv::Point2f(std::floor(point.x) + 0.5f, std::floor(point.y) + 0.5f);
        cv::Point2f p01 = cv::Point2f(std::floor(point.x + x_flag * 1.0f) + 0.5f, std::floor(point.y) + 0.5f);
        cv::Point2f p10 = cv::Point2f(std::floor(point.x) + 0.5f, std::floor(point.y + y_flag * 1.0f) + 0.5f);
        cv::Point2f p11 = cv::Point2f(std::floor(point.x + x_flag * 1.0f) + 0.5f, std::floor(point.y + y_flag * 1.0f) + 0.5f);

        std::vector<cv::Point2f> vec;
        vec.push_back(p01);
        vec.push_back(p10);
        vec.push_back(p11);

        // 计算最近的坐标点与采样点距离
        cv::Point2f distance = p00 - point;
        float len = sqrt(distance.x * distance.x + distance.y * distance.y);

        // 对边缘点进行着色
        for(auto p:vec)
        {
            // 根据距离比, 计算边缘点影响系数
            cv::Point2f d = p - point;
            float l = sqrt(d.x * d.x + d.y * d.y);
            float percnet = len / l;

            cv::Vec3d color = window.at<cv::Vec3b>(p.y, p.x);
            // 此处简单粗暴取最大值
            color[1] = std::max(color[1], (double)255 * percnet);
            window.at<cv::Vec3b>(p.y, p.x) = color;
        }
    }

}

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    // 数学方法
    // int n = control_points.size();
    // cv::Point2f point = {0.0f, 0.0f};
    // for (int i = 0; i < n; i++)
    // {
    //     int c = factorial(n - 1) / (factorial(i) * factorial(n - 1 - i));
    //     point += c * std::pow(t, i) * std::pow(1 - t, n - 1 - i) * control_points[i];
    // }

    // 递归方法
    if(control_points.size() == 2)
    {
        return control_points[0] + t * (control_points[1] - control_points[0]);
    }

    std::vector<cv::Point2f> vec;
    for (int i = 0; i < control_points.size() - 1; i++)
    {
        vec.push_back(control_points[i] + t * (control_points[i + 1] - control_points[i]));
    }

    return recursive_bezier(vec, t);
}
  • 所画结果为:
  • 两者同时画,显示的结果为黄色曲线,如下:
posted @ 2020-12-18 16:50  levelly  阅读(1382)  评论(0)    收藏  举报