计算机图形学 13:少量控制点怎样生成曲线
把一条拱形曲线左侧的控制点提高,右半段会不会改变?如果两段曲线在同一点接上,而且切线方向相同,沿曲线运动的物体会不会突然变速?这两个问题分别涉及控制点的影响范围和参数的含义,仅凭曲线看起来平滑无法回答。
本篇用四个控制点构造三次 Bézier 曲线,实际生成移动前后的图像,再用独立多项式检查求值结果。坐标沿用第 01 篇的向量约定,图像沿用累计 C++17 工程;这里的二维曲线嵌在 z=0 平面,不经过透视相机。
从线性插值到曲线
两点之间的线性插值是 L(t)=(1−t)A+tB,t∈[0,1]。四个点 P₀、P₁、P₂、P₃ 可以先两两插值,得到三个点;再对这三个点插值,得到两个点;最后再插值一次,得到曲线上对应 t 的一个点。
用 Pᵢ⁽ʳ⁾ 表示第 r 层的结果,de Casteljau 递推为:
n 是次数,因此 n+1 个控制点需要 n 层插值。每次混合的是上一层相邻两点,同一次求值中所有层都使用同一个 t。三次曲线需要 3+2+1=6 次向量插值,不是把三个控制线段分别画出来后接在一起。
累计代码 curve.hpp 的 bezier 先复制控制点,再逐层缩短有效数组。每层从左向右更新,右邻点尚未覆盖,因此无需存储整个三角形。时间复杂度 O(n²),工作空间 O(n)。当前接口限制为 1–65 个有限控制点及有限的 t∈[0,1];这是教学实现的输入上限,不是 Bézier 曲线的数学限制。
展开三次递推得到:
这四个 Bernstein 权重在区间内非负,而且和为 1。曲线因此位于控制点的凸包内。P₀ 与 P₃ 会被曲线经过,内部控制点一般不会被经过;把控制点当作必须穿过的插值节点,会给造型工具提出另一种需求。
递推算法与展开式表达同一条曲线,但程序验证不应把同一段递推再执行一次就当作独立答案。下面选择可手算展开式的控制点,比较两种求值路径。
提高一个控制点,改变了哪些位置
实验固定 P₀=(0,0)、P₁=(1,2)、P₂=(2,2)、P₃=(3,0),所有 z 均为 0。代入并合并多项式,得到 B(t)=(3t,6t(1−t))。端点是 (0,0) 和 (3,0),参数中点为 (1.5,1.5)。
图由 curve_check 实际生成:蓝点是控制点,灰线是控制多边形,橙线连接 1001 个等参数样本。480×480 图像采用 x像素=40+120x、y像素=440−120y,因此数学坐标 y 向上,图像行号向下。颜色是直接指定的诊断码值,不参与光照或线性颜色平均。
现在只把 P₁ 改成 (1,3)。展开式中只有 P₁ 项变化,所以每个参数处的位移为:
两个端点不变,中点的 y 增加 3×0.5×0.5²=0.375,变为 1.875。位移权重在整个开区间 (0,1) 都大于零,故右半段也发生变化,只是靠近终点时变化很小。图像右端附近看起来相似,不能据此声称影响只在左侧。
实际程序在 t=i/1000、i=0…1000 处检查位置。原曲线与独立多项式的最大欧氏误差为 8.8991×10⁻¹⁶;移动前后差值与上述位移式的最大误差为 6.6613×10⁻¹⁶。按距离大于 10⁻¹² 计数,1001 个样本中恰好 999 个改变。有限样本检查验证这次实现,整个开区间的结论则来自权重公式。
曲线图只是用 1000 条短线段近似连续曲线。固定采样数没有给出任意输入的屏幕误差保证;尖锐弯曲或放大视图需要更密采样,或者按平坦度细分。当前图像展示控制点影响,不把固定采样当作通用曲线光栅算法。
接上、同向和同速是不同条件
对 n 次 Bézier 求导,可得到由 n 个差分控制点 n(Pᵢ₊₁−Pᵢ) 定义的 n−1 次 Bézier。bezier_derivative 复用同一求值器计算这条导数曲线。端点尤其简单:
基线的起点导数是 (3,6),终点导数是 (3,−6)。前者沿第一条控制边,后者沿最后一条控制边。导数包含长度,归一化之后只剩方向;提前归一化会丢失判断参数速度所需的信息。
让第二条三次曲线 Q 的起点固定为 Q₀=P₃=(3,0),Q₂=(5,−1)、Q₃=(6,0),只改变 Q₁。两段各自都使用单位参数区间,并顺次映射到全局区间 [0,1] 和 [1,2]。实际检查结果如下:
| Q₁ | 第二段起点导数 | C⁰ 位置连续 | G¹ 同向切线 | C¹ 一阶导数连续 |
|---|---|---|---|---|
| (4,0) | (3,0) | 是 | 否 | 否 |
| (5,−4) | (6,−12) | 是 | 是 | 否 |
| (4,−2) | (3,−6) | 是 | 是 | 是 |
C⁰ 只要求前段终点等于后段起点。对这里非零的端点导数,G¹ 要求后段导数是前段导数的正倍数;程序检查叉积接近零且点积为正,因而不会把掉头的反向共线误算成同向。零导数情形不能直接套用这个判据,需要检查更高阶行为,本实验没有把它算作已验证情形。
C¹ 还要求这两个导数向量相等。表格第二行虽然没有方向折角,但参数均匀推进时,接点后的速度大小变为此前的两倍。这是参数运动不连续,不是位置断开。
如果两段运动分别耗时 T₁、T₂,物理速度应为 B′/T₁、Q′/T₂。改变时间映射就可能改变速度连续性,而不改变空间中的曲线形状。因此,讨论 C¹ 时必须说明比较哪个参数;讨论动画速度时必须把参数与时间的关系写出来。
为什么等参数取样不是匀速运动
本例基线导数是 (3,6−12t),速度大小为 √(9+(6−12t)²)。它在 t=0.5 最小,在两端较大。让 t 每帧增加相同数值,物体会在中间较慢、两端较快。
程序又取 11 个 t=0,0.1,…,1 的点,统计相邻点的 10 条弦长。最小值为 0.3059411708,最大值为 0.6177378085,约相差两倍。弦长不是精确弧长,但已经足以反驳“每次参数增量相同,所以每次距离相同”。
若需要沿曲线近似匀速运动,可以累积细分折线长度,建立距离到参数的查找表,再按目标距离反查 t。查找表精度依赖细分误差,不能把它称为精确弧长参数化。本篇尚未实现这一步,动画章节会重新处理时间输入。
B 样条怎样限制控制点的影响
一条高次 Bézier 的内部控制点会影响整段参数范围。复杂轮廓若仍由单条曲线表示,修改一个位置可能使远处也变化。B 样条使用分段多项式基函数及节点向量,在不持续提高次数的前提下增加控制点。
其形式仍是 C(u)=ΣNᵢ,ₚ(u)Pᵢ,其中 p 是次数,u 是曲线参数,节点向量规定参数区间的分段方式。与单段 Bézier 的全局权重不同,基函数 Nᵢ,ₚ 的支撑限于 [uᵢ,uᵢ₊ₚ₊₁);在约定端点处按相应极限处理。移动 Pᵢ,只可能改变这段支撑范围内的曲线,支撑外对应权重为零。
“局部”指参数支撑范围,不是以控制点为圆心的某个欧氏距离。节点并不等于曲线上的采样点,控制点也不必被曲线经过。节点重复度会影响连接处的光滑程度,因此 B 样条并不意味着任何节点配置都处处同样平滑。
本篇对 B 样条只建立表示与局部支撑概念,没有实现 B 样条求值器,也没有把 Bézier 的两张实验图当作 B 样条结果。下一篇转向三角网格细分:同样用邻域加权生成新位置,但输入连接关系、更新规则与验收不变量都不同。
复现实验与练习
仓库根目录执行:
1 | |
程序输出 curve-before.ppm 与 curve-after.ppm,数值和命令日志见 writing-plans/computer-graphics/evidence/13-cpu.txt。实验不使用随机数,没有 GPU 或性能测量。四个拒绝样本覆盖负参数、超出区间参数、NaN 参数和空控制点;这些是明确的接口失败,不以静默钳制代替。
练习一:把 P₁ 提高 2 个单位,参数中点增加多少?影响最大的位置是否就是中点?
答案:中点增加 2×3/8=0.75。对权重 w(t)=3t(1−t)² 求导,w′(t)=3(1−t)(1−3t),内部最大值在 t=1/3,权重为 4/9,因此最大位移是 8/9。控制点影响最大的位置不必等于参数中点。
练习二:接续表格第二行的两段曲线,第一段耗时 1 秒,第二段应耗时多少秒才能让接点速度连续?若两个导数反向共线,仅调整正的时长能否修复?
答案:第二段耗时 2 秒,(6,−12)/2=(3,−6)/1。正时长只缩放导数长度,不反转方向,所以反向共线不能靠这种时间缩放修复。
系列导航与资料
前篇:12:三角网格怎样保存邻接。系列入口。下一篇:14:粗网格怎样变成曲面。
Michigan Tech 曲线课程的 de Casteljau 算法,Actual Computation 与 A Recurrence Relation,核对递推层次与迭代求值;Bézier 导数,核对差分控制点、端点导数与 G¹/C¹ 接续条件;B 样条曲线性质,第 5 项核对局部修改支撑区间。三页于 2026-09-20 实际读取,独立样例与运行记录保存在本系列工程中。






