games101作业4 记录

作业要求

Bézier Curve(贝塞尔曲线)是一种用于计算机图形学的参数曲线。在本次作业中,你需要实 现 de Casteljau 算法来绘制由 4 个控制点表示的 Bézier 曲线 (当你正确实现该算法时,你可以支持绘制由更多点来控制的 Bézier 曲线)。

你需要修改的函数在提供的 main.cpp 文件中。

• bezier:该函数实现绘制 Bézier 曲线的功能。它使用一个控制点序列和一个 OpenCV:: Mat 对象作为输入,没有返回值。它会使 t 在 0 到 1 的范围内进 行迭代,并在每次迭代中使 t 增加一个微小值。对于每个需要计算的 t,将 调用另一个函数 recursive_bezier,然后该函数将返回在 Bézier  曲线上 t 处的点。最后,将返回的点绘制在 OpenCV ::Mat 对象上。

• recursive_bezier:该函数使用一个控制点序列和一个浮点数 t 作为输入, 实现 de Casteljau 算法来返回 Bézier 曲线上对应点的坐标。

De Casteljau 算法说明如下:

1. 考虑一个 p0 , p1 , ...  pn  为控制点序列的 Bézier 曲线。首先,将相邻的点连接 起来以形成线段。

2. 用 t : (1 − t) 的比例细分每个线段,并找到该分割点。

3. 得到的分割点作为新的控制点序列,新序列的长度会减少一。

4. 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤 1。使用 [0,1]  中的多个不同的 t 来执行上述算法,你就能得到相应的 Bézier 曲线。

评分

•  [5 ] 提交的格式正确,包含所有必须的文件。代码可以编译和运行。

  [20 ] De Casteljau 算法:对于给定的控制点,你的代码能够产生正确的 zier 曲线。

  [5 ] 奖励分数:实现对 zier 曲线的反走样。(对于一个曲线上的点,不只把它对应于一个像素,你需要根据到像素中心的距离来考虑与它相邻的像素的颜色。 )

1. 基于 naive_bezier 函数控制 Bézier 曲线

naive_bezier是框架自带的函数,通过使用多项式公式直接计算 Bézier 曲线上的点。naive_bezier是静态计算曲线点, 在绘制时,没有实时响应控制点的移动,而是基于初始的控制点集进行一次性绘制,当用户拖动控制点时,曲线不会更新,因为绘制的曲线是静态的,且并未每帧重算。

实现方式

  • 对 t ∈ [0,1]  进行小步长迭代。
  • 对每个 t,用公式计算 Bézier 曲线上的一个点:
  • 将计算出的点绘制到图像上。

代码实现:

// 使用多项式公式绘制 Bézier 曲线的函数(非递归方法)
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];

    // 通过从 t=0 到 t=1 的小步长迭代计算 Bézier 曲线上的点
    for (double t = 0.0; t <= 1.0; t += 0.001) 
    {
        // 使用多项式公式计算 Bézier 曲线点
        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;
        
        // 将计算的点标记为红色
        window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
    }
}

运行结果:

2. 基于bezier 函数控制 Bézier 曲线

在确保代码框架一切正常后,就可以开始完成你自己的实现了。注释掉 main 函数中 while 循环内调用 naive_bezier 函数的行,并取消对 bezier 函数的注释。要求将 zier 曲线绘制为绿色

bezier函数使用 de Casteljau 算法 递归动态计算曲线点,每次绘制时都会重新计算 Bézier 曲线点。由于曲线点每帧都根据当前控制点实时计算,所以当用户拖动控制点时,曲线能即时更新。

实现方式

  • 对 t ∈ [0,1]  进行小步长迭代。
  • 对每个 t,调用 recursive_bezier 递归计算 Bézier 曲线上的点。
  • recursive_bezier 的逻辑是:
    • 如果控制点数为 1,则返回该点。
    • 否则,对控制点进行线性插值,生成下一组控制点,递归计算。

关于bezier曲线的定义:给定点 P0 ,P,..., Pn ,则n次贝塞尔曲线由下式给出:

 n次贝塞尔曲线可由如下递归表达:

代码实现:

// 使用递归的 de Casteljau 算法绘制 Bézier 曲线的函数
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{

    // 使用递归算法计算 Bézier 曲线点
    for(double t = 0.0; t <= 1.0; t += 0.001){

        cv::Point2f point = recursive_bezier(control_points,t);

        // 确保点在图像窗口范围内,然后着色
        if(point.x >= 0 && point.x < window.cols && point.y >= 0 && point.y < window.rows){
            
            // 将计算的点标记为绿色
            //在 cv::Vec3b 中,像素的 R、G、B 通道分别存储在索引 [2]、[1] 和 [0] 中:
            window.at<cv::Vec3b>(point.y, point.x)[1] = 255;

        }
    }

}

注意,cv::Vec3b 是一个包含 3 个分量的向量,分别对应 B(蓝色)G(绿色)R(红色)

[1] 表示访问 cv::Vec3b 中的第二个分量,即绿色通道(G)。

在 cv::Vec3b 中:

  • [0] 对应蓝色通道(B)。

  • [1] 对应绿色通道(G)。

  • [2] 对应红色通道(R)

// 使用 de Casteljau 算法递归计算 Bézier 曲线点的函数
cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    // 递归的终止条件:当控制点数量减少到仅剩一个点时,这个点就是 Bézier 曲线在特定参数t下的结果点。
    if(control_points.size() == 1){
        return control_points[0];
    }
    
    // 用于存储中间点的向量
    std::vector<cv::Point2f> next_points;

    // 通过线性插值计算相邻控制点之间的新点
    for(size_t i=0; i < control_points.size()-1; i++){

        cv::Point2f point = (1-t) * control_points[i]+t * control_points[i+1];
        next_points.push_back(point);

    }

    // 递归计算 Bézier 曲线点
    return recursive_bezier(next_points, t);

}

 运行结果:

通过拖动控制点可以控制曲线。 

放大后可以看到明显的锯齿:

naive_bezier和bezier对比

特性

naive_bezier

bezier

算法类型

静态公式计算

动态递归计算

计算复杂度

O(n)O(n)O(n)(与步长 ttt 的取值相关)

O(n2)O(n^2)O(n2)(递归带来额外开销)

实时性

直接公式计算效率高

递归开销较高,性能稍逊

动态控制点响应

需要额外调整主循环逻辑来响应控制点拖动

每帧动态重新计算,天然支持控制点变化

代码复杂度

简单清晰,便于理解

使用递归,逻辑较复杂

扩展性

仅适用于 4 个控制点的固定 Bézier 曲线

支持任意数量的控制点

3. Anti-aliasing (反走样)

核心问题

在渲染 Bézier 曲线时,如果只为曲线点赋予颜色,而忽略周围像素的影响,可能会产生锯齿现象,即曲线看起来不平滑。

Anti-aliasing 的目标

根据 Bézier 点与像素中心的距离,调整周围像素的颜色强度。让曲线的颜色在周围像素中逐渐过渡,从而减轻锯齿感。

Anti-aliasing 的思路

1.计算 Bézier 曲线点

遍历曲线的参数 t,精确生成曲线上的点。

2.影响区域:

让 Bézier 点对其周围 3×3像素区域产生影响。

Bézier 点point到9个邻域像素中心的最大距离是 3/√2(point在中心像素的某个顶点),最小距离是0( point与像素中心重合) 。

3.距离权重

根据 Bézier 点与像素中心的距离,分配颜色强度(距离越近,颜色越亮)。

4.归一化处理:

对距离进行归一化,将其映射到 [0, 1],确保颜色强度计算在固定范围内。

距离越小,强度越高。当 normalized_distance = 0(像素距离 Bézier 点最近),颜色强度为最大值 255。

距离越大,强度越低。当 normalized_distance = 1(像素距离 Bézier 点最远),颜色强度为最小值 0。

5.更新策略:

使用较高颜色强度更新像素,防止覆盖已有的较亮颜色。

代码实现:

// 使用递归的 de Casteljau 算法绘制 Bézier 曲线的函数
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
    // 遍历参数 t,从 0 到 1,以步长 0.001 逐步生成 Bézier 曲线上的点
    for(float t = 0.0f; t <= 1.0f; t += 0.001f){

        // 使用递归 de Casteljau 算法计算参数 t 对应的 Bézier 曲线点
        cv::Point2f point = recursive_bezier(control_points, t);

        // 遍历 Bézier 点周围的 3x3 区域像素
        for (int i = -1; i <= 1; i++) {
            for (int j = -1; j <= 1; j++) {

                // 计算邻近像素的中心点坐标
                // 使用 std::floor 取整,然后加 0.5 得到像素中心
                float pixel_center_x = std::floor(point.x + i) + 0.5f;
                float pixel_center_y = std::floor(point.y + j) + 0.5f;

                // 计算 Bézier 点与当前像素中心的欧几里得距离
                float distance = std::sqrt(std::pow(point.x - pixel_center_x, 2) + std::pow(point.y - pixel_center_y, 2));

                // 最大距离为 3/√2(覆盖 3x3 像素区域的对角线距离)
                float max_distance = 3.0f / std::sqrt(2.0f);
                // 将距离归一化到 [0, 1] 的范围
                float normal_distance = distance / max_distance;

                // 计算颜色强度,使用距离归一化后的反比例关系
                // 距离越近,强度越高,最大值为 255
                float ratio = std::max(0.0f, 1.0f - normal_distance);
                float color_intensity = 255.0f * ratio;

                // 计算当前像素的整数坐标
                int py = std::floor(point.y + j);
                int px = std::floor(point.x + i);

                // 检查像素坐标是否在图像窗口范围内,避免越界
                if(px >= 0 && px < window.cols && py >= 0 && py < window.rows) {   
                    // 如果当前像素的绿色通道值小于计算的颜色强度,则更新为更大的值
                    // 确保只更新更高的颜色值,减少不必要的操作。
                    window.at<cv::Vec3b>(py, px)[1] = std::fmax(color_intensity, window.at<cv::Vec3b>(py, px)[1]);
                }
            }
        }
    }
}

运行结果:

放大后也比较顺滑:

通过增加控制点可以尝试绘制更多的 Bézier 曲线。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值