曲线:几个控制点怎么夹出一条光滑的线
全课题骨架:几何不是"画"出来的,是控制点 + 一条可以反复施加的规则算出来的 ——
而那条规则只做一件事:在两点之间取一点。
前九个课题的输入都是"已经离好的东西"(顶点、像素、瓦片、网格、样例图)。这个课题退到更前面一步:
那些顶点和网格是从哪儿来的? —— 造几何这件事,在本仓一直是个没拆过的黑盒。
它出人意料地简单:整条路只有一条规则,就是"在两点之间取一点"。
把它沿着一排控制点重复 n 次,得到一条曲线(本课);
换到一个网格的邻域上重复,得到细分(第 2 课);在两个方向各重复一遍,得到一张曲面(第 3 课)。
四段路:两个点之间一个数 t(20.1)→ 三个点:规则用两次(20.2)→ 一般的 n(20.3)→
两条接起来时光滑的条件(20.4)。
保真度:示意 · 等价重现 —— 曲线求值、金字塔、Bernstein 形式、分割、升阶、端点切线
都是纯算术(页面现场跑的数与离线定稿 q201.mjs 逐位相同,十条判据都能离线复算);
但画面是本页自己的二维软件光栅(逐像素写帧缓冲再搬上画布),不是硬件顶点着色器产出。
★ 真机上的常见做法是把 lerp / pyramid 放进顶点着色器、每个顶点算一个 t
(本站课题二第 1 课演示过那条管线);本页在 CPU 上算同一份顶点数组,但不声称两者逐位相同。
本页的原理与四张数值表照常可读(画面只是把同一份顶点数组画出来)。
(脚本启动中…)
定点观察:(脚本启动中…)
本帧的读数
(脚本启动中…)
20.1 段 · 两个点之间取一个点
本段只增加一样东西:参数 t(画面里是两个控制点与它们之间那一点;其余控件与各段一致)。 保真度:示意 · 等价重现。造几何最小的情况就是两个点 —— 一条线段。 可"线段上的一点"这件事已经在用那条规则了:把它写成两个端点的一个加权平均, 权重是 t 与 (1−t)。t 不是"第几个点",它是一个比例。
|PA| : |PB| →
那个比值恰好是 t : (1−t):t 不是"序号",是一个比例。
这也是"加权平均"这四个字第一次出现的地方 —— 后面三段全都只是它的重复。| t | 闭式 (1−t)·A + t·B | 实测(lerp) | 差 (px) | |PA| : |PB| |
|---|
算式:lerp(a, b, t) = a + t·(b − a),两个分量各算一次。
把 t = 0 与 1 代进去就回到 A 与 B(这一条叫端点插值,是后面所有段的共同底线);
把 t = 0.5 代进去得到 (A + B)/2。
表① 的"闭式"一列为什么要换一种写法:a + t·(b − a) 与 (1−t)·a + t·b 是同一个量的两种等价写法,
数学上完全相同,但浮点上并不保证逐位相同(乘的顺序不同、舍入点就不同)。这一列如果照抄成
a + t·(b − a),差会恒为 0 —— 那不是验证,是复读。
换过来之后"差"那一列报的才是真东西,而它的读数分两种情形:本表这五档的 t 都是二进小数
(0 / 0.25 / 0.5 / 0.75 / 1),两种写法都算得精确 ⇒ 差恰好是 0;
而把 t 连续扫到 721 档(判据③),就会出现 1e−13 量级的差 —— 那正好是一个坐标(几百 px)的末位。
"差 0"与"差 1e−13"在这里都对,取决于 t 取在哪。
注意 t 的含义:它是"从 A 走到 B 的这个比例",所以 |PA| : |PB| = t : (1−t) ——
这正是"加权平均"里两个权重的来处。但两个点没有"弯"可言:不管 t 怎么取,点永远落在那条线段上。
想要它弯,就得允许"多一个点、再多用一层规则" —— 那是 20.2。
20.2 段 · 三个控制点:把这条规则用两次
本段只增加一样东西:控制点数 2 → 3(规则一个字都不换,只是多施加一层;其余控件一致)。 保真度:示意 · 等价重现。上一段只解决了"两个点之间的比例",可两个点画出来的永远是直线段。 想让它弯,最省事的补法是再加一个控制点,然后问一句:三个点怎么用同一条规则? 答案是把规则用两次:先在 (A,B) 与 (B,C) 上各取一点,再在这两点之间取一点。 两次之后得到的不再是直线 —— 而且它只经过 A 与 C。
| t | 闭式 (1−t)²A + 2t(1−t)B + t²C | 实测(两层金字塔) | 差 (px) | |P(t) − B| (px) |
|---|
算式:把规则用两次,摊开就是 P(t) = (1−t)²A + 2t(1−t)B + t²C ——
三个权重是 0.25 / 0.50 / 0.25(t = 0.5 时),和恰好是 1。
最该记住的一句:t = 0 与 1 代进去,只有 A 与 C 的权重不为零(各为 1),所以曲线经过首末点;
而 B 的权重在 t = 0.5 处最大也只有 0.5,**永远到不了 1** —— 所以曲线不经过 B。
本文档里这叫"逼近样条":控制点是拉力,不是必须落在曲线上的点。
第二条性质也在这里第一次出现:三个权重都非负、加起来是 1,
于是曲线上的点永远是控制点的凸组合 ⇒ 整条曲线待在控制点围成的凸包里(判据②量的就是这一条)。
20.3 段 · 一般的 n:一个三角形的账
本段只增加一样东西:控制点数任意(3 到 7;其余控件一致)。 保真度:示意 · 等价重现。上一段只解决了"多一个点"的做法,可真实造型要用几十个点。 同一件事往上长很直接:n+1 个控制点就把规则用 n 次,画出来是一个三角形阵(金字塔), 行数就是次数。而"用了 n 次"这件事可以一次算完 —— 把系数摊开就是 Bernstein 形式。 两条路的数必须逐位相同,这构成一条真正的交叉验证。
| 控制点数 | t | 金字塔(重复施加) | Bernstein(一次算完) | 差 (px) |
|---|
算式:B(t) = Σᵢ C(n,i)·(1−t)^(n−i)·t^i·Pᵢ,其中 C(n,i) = n!/(i!(n−i)!)。
把 t + (1−t) = 1 代进二项式定理就得到 Σᵢ C(n,i)(1−t)^(n−i)t^i = 1 ——
这就是权重和恒为 1(判据⑤ 实测 4.4e−16),也是凸包性的根。
两条路线必须不共用中间量:一条在几何上一层层取点,一条把系数一次乘完(二项式系数由帕斯卡递推算),
所以它们给出的"最大差 3.46e−13"才叫证据 —— 若两边照抄同一个表达式,差恒为 0,等于没验。
★ 层数这件事值得单独记一句:n+1 个控制点 ⇒ n 层 ⇒ 次数 n。
所以这一段的标题里那个"三角形的账"就是指金字塔 —— 它的两腰(每层的首与尾)在下一段会派上大用场。
20.4 段 · 两条接起来:接缝处要什么条件
本段只增加一样东西:第二条曲线的控制点(画面上是 8 个点;其余控件一致)。 保真度:示意 · 等价重现。上一段把"一条曲线"讲完了,可一条曲线只有 n+1 个控制点, 形状自由度有限(而且改一个点整条都会动)。真实造型是把很多段接起来 —— 于是问题变成:接的地方要满足什么,看起来才不像断了一截? 答案就藏在上一段已经算出来的东西里:端点切线。
B'(0) = n(P₁−P₀)。| 档 | 共线度 |t₁×t₂| / (|t₁||t₂|) | 接缝夹角 (°) | 接缝点位置差 (px) | 只挪 Q₁(84.85 px)后第二段最大位移 (px) |
|---|
算式:三次曲线在 t = 0 处的切向量是 B'(0) = 3(P₁ − P₀)。
把它用在"两段接起来"上:第一段的尾切线是 3(P₃ − P₂),第二段的头切线是 3(Q₁ − Q₀);
而两段共享 P₃ = Q₀。于是两条切线共线的条件就是 P₂、Q₀、Q₁ 三点共线 ——
再要求等距(比 1:1),两条切向量就完全相等,接缝处连"速度"都接上了,这就是 C¹。
顺带的定量结论:只挪 Q₁ 一个点、位移 84.85 px 时,
第二段曲线最多移动 4/9 × 84.85 = 37.71 px(实测 37.71,逐位相同)。
这个 4/9 的来处很好记:三次曲线里 P₁ 的权重是 3t(1−t)²,它在 t = 1/3 取极大 4/9
—— 所以控制点的影响是有上界的,这是"控制点不是曲线上的点"最直白的一个推论。
课末:把"夹"拆成四件事
这一课只做了一件事:把"在两点之间取一点"这条规则用起来。四段各加一个变量,加完全都清楚了:
- 20.1 参数 t:规则本身。
lerp(a,b,t),两个分量各算一次;t 是比例,|PA|:|PB| = t:(1−t)。 - 20.2 控制点数 2 → 3:规则用两次。曲线第一次有了"弯",也第一次不经过中间的控制点(差 170.00 px)。
- 20.3 控制点数任意:规则用 n 次,金字塔行数就是次数;换成一次算完就是 Bernstein 形式。 两条路线逐位相同(3.46e−13),权重和恒为 1(4.4e−16)⇒ 曲线永远在凸包里。
- 20.4 第二条曲线:接缝只看端点切线;C¹ 的条件是
P₂、Q₀、Q₁共线且等距(夹角 0.000000°)。
自检面板
等待脚本…