把四面体每个面切成四个小三角形,它会变圆吗?如果所有新增点仍在原来的面上,答案是否定的:面数增加,几何外形却没有改变。曲面细分还必须更新顶点位置,而且更新规则要区分内部和边界。

本篇复用第 12 篇的索引网格与半边邻接,主实现选择三角网格 Loop 细分。单三角形检验边界,闭合四面体检验内部规则,两组都实际执行多次更新。第 13 篇的曲线说明了加权平均可以改变形状;这里还要保证相邻面共享同一个新增顶点。

一轮细分有两件事

对旧三角形 (a,b,c),先在三条边上各生成一个新顶点,编号记为 ab、bc、ca。四个子面依次写为 (a,ab,ca)、(b,bc,ab)、(c,ca,bc)、(ab,bc,ca)。这个顺序保留旧面的绕序。

拓扑拆分之外,还有位置更新。旧顶点 a 的新位置不是旧坐标的别名;边点 ab 也不一定是边的中点。所有位置都根据旧层网格计算,之后一起成为新层的顶点数组。

若一边遍历一边改写旧坐标,稍后访问的顶点就会混合新旧两层数据。输出不仅偏离规则,还可能依赖遍历顺序。subdivision.hpploop_step 复制位置数组用于输出,所有加权输入都明确读取 mesh.positions,不在旧网格上原地平滑。

每条无向边只产生一个新顶点。代码用与半边表等长的 edge_vertex 保存编号;内部边第一次生成新点后,把编号同时写到 h 和 twin 的槽位。第二个邻面于是复用同一索引,而不是创建一个坐标相同、拓扑却断开的副本。

内部规则怎样使用一环

设内部旧顶点 v 有 n 个邻点 vᵢ。当前采用 PBRT 第三版 §3.8 所实现的权重版本:n=3 时 β=3/16,其余内部价数 β=3/(8n)。新位置为:

v=(1nβ)v+βi=0n1vi.v'=(1-n\beta)v+\beta\sum_{i=0}^{n-1}v_i.

n 是相邻顶点数。规则内部价数 n=6 时,β=1/16,中心权重为 5/8。价数为 3 时中心权重是 7/16,不能把 n=6 的固定权重直接用于四面体。

这里明确采用教材注明的 Hoppe 等人的改进权重,不把所有称为 Loop 的权重表达式视为同一个实现。浮点代码直接使用这些分数,实验没有通过调参数去迎合一张目标图。

对内部边 (a,b),两侧三角形的对顶点记为 c、d,新边点为:

e=38(a+b)+18(c+d).e=\frac38(a+b)+\frac18(c+d).

四个权重和为 1。平面输入仍保持在原平面上,整体平移输入也应只导致输出同样平移。只取 (a+b)/2 虽然能把边切开,却漏掉了两侧面的几何信息,不是这里使用的内部边规则。

当前 Mesh 已保证每条内部边恰有两个反向半边、每个顶点具有单一扇区,故可以沿 next.next 找到三角形对顶点。这个前提来自第 12 篇构造器;若直接破坏公开的半边数组,细分函数不会额外提供任意损坏输入的安全解析。

边界必须采用另一组规则

边界边只有一个邻面,内部公式中的第二个对顶点不存在。当前选择新边界点为中点 e=(a+b)/2;旧边界顶点只用两侧边界邻点 l、r 更新为:

v=34v+18(l+r).v'=\frac34v+\frac18(l+r).

第 12 篇的一环对边界返回开放链,首尾恰好是这两个边界邻点。链中间的内部邻点不参与这个更新。代码通过缺少 twin 的出边标记边界顶点,再读取 ring.front()ring.back()

取单三角形 (0,0,0)、(1,0,0)、(0,1,0)。第一轮中,原点移到 (1/8,1/8,0),边 (0,1) 的新点是 (1/2,0,0)。程序对这两个位置逐项检查,而不是只检查顶点数量。

图像来自实际 CPU 光栅器,每块 256×256,从左到右为第 0、1、2 层;相机、正交视野和缩放不变。两种颜色按面编号交替,仅便于识别覆盖,不表示曲率或误差。

轮廓的角被平滑且边界向内移动,正是该边界规则的结果。“有边界处理”不等于“保持原多边形边界位置”。若建模需要固定角点、锐边或折痕,需要额外的特征标记及对应规则;本实现不把这些不同需求混成一个默认选项。

两轮中的所有顶点都检查 z=0、x≥0、y≥0、x+y≤1,确认仍在原三角形凸包中。这个有限样例验证平面与范围约束,没有证明任意复杂控制网格都不发生自交。

数量关系怎样发现重复边点

每条旧边增加一个顶点,每个旧面变成四个子面。旧边各拆为两段,每个旧三角形内部再增加三条边,因此一轮的计数应满足:

V=V+E,E=2E+3F,F=4F,B=2B.V'=V+E,\quad E'=2E+3F,\quad F'=4F,\quad B'=2B.

B 是边界无向边数。由这些关系可得 V′−E′+F′=V−E+F,欧拉示性数不变。它仍不是流形性的充分条件,所以输出重新通过 Mesh 构造器的一环、重复面、绕序和退化检查。

实际运行得到:

网格与层数 V E F B
单三角形 0 3 3 1 3
单三角形 1 6 9 4 6
单三角形 2 15 30 16 12
四面体 0 4 6 4 0
四面体 1 10 24 16 0
四面体 2 34 96 64 0
四面体 3 130 384 256 0

如果两侧面各自创建边点,V′通常会超过 V+E,共享位置也会变成裂开的拓扑。只检查 F′=4F 抓不到这种错误。实验每一轮同时检查四个计数式及连接关系,不能靠其中一项替代其余项。

四面体为什么明显缩小

闭合实验的四个位置为 (1,1,1)、(−1,−1,1)、(−1,1,−1)、(1,−1,−1)。网格关于原点对称,各点价数为 3。其余三个顶点之和恰好是 −v,所以旧顶点第一轮应变成:

v=716v+316(v)=14v.v'=\frac7{16}v+\frac3{16}(-v)=\frac14v.

对任一旧边,其余两点之和为 −(a+b),因而新边点为 (a+b)/4。程序分别核对所有四个旧点和第一条边点,提供独立于遍历代码的手算基准。

四块图像仍是固定相机和固定缩放。表面采用几何面法线与同一方向光,在线性域计算明暗,再使用累计 sRGB 输出;没有换成平滑法线来掩盖面片。因此后两层仍能看到较细的平面明暗差异。

顶点到原点的最大距离,四层分别为 1.7320508076、0.5、0.3576725982、0.3471236704。第一轮的新边点比更新后的旧顶点离原点更远,所以最大值为 0.5,不是 √3/4。图像中的收缩有数值依据,不能解释成相机缩放变化。

这种规则把控制网格解释为定义曲面的控制数据,没有要求保留原体积或逐点贴合输入表面。若任务是给已有三角面加密但保持外形,应选择纯拓扑细分;若任务是平滑造型,收缩是否可接受则取决于建模意图。

实验还把每层输入整体平移 (2,−3,4),比较“平移后细分”和“细分后平移”。三次最大位置误差均为 0,得益于该样例中坐标及权重的二进制可表示性。其他数据不能据此宣称零浮点误差。每个生成面也检查法线朝外,避免内外方向错误影响照明判断。

有限网格和极限曲面不能混称

程序固定执行两轮边界细分、三轮闭合细分后停止。输出仍是有限的三角网格,不是已经直接求值的极限曲面。增加层数使面数按 4 的幂增长,也会增加存储、邻接验证和光栅化工作。

PBRT 的实现还包含把顶点投到极限位置以及计算极限切向的步骤;当前代码没有执行这两步。因而这里的第 0 层就是原始控制网格,不能与教材中已投影到极限位置的同名展示层数直接比较。

现有 Mesh 为每个顶点扫描半边表建立一环,适合本实验 256 面的上限,不是大规模细分库。提升规模时应改善邻接入口与遍历成本,但应继续保留本篇的权重和拓扑验收。

Catmull-Clark 是另一种常见细分方案:它以面点、边点和更新后的旧顶点生成子四边形,适合以四边形为主的控制网格;Loop 要求三角面,并在每轮保留三角面。本系列当前数据结构固定三角形,主实现选 Loop,Catmull-Clark 只作路线对照。不能先随意把四边形三角化,再声称与原 Catmull-Clark 曲面完全相同;连接关系已参与定义结果。

复跑与练习

1
2
make -C examples/computer-graphics check
examples/computer-graphics/build/subdivision_check examples/computer-graphics/build

代码在 subdivision.hppsubdivision_check.cpp。输出 loop-boundary.ppmloop-closed.ppm,PNG 由这些实际结果转换;原始数值记录在 writing-plans/computer-graphics/evidence/14-cpu.txt。没有随机采样、GPU 执行或性能对比。

练习一:一个由两个三角形组成的方形有 V=4、E=5、F=2、B=4。执行一轮后四项数量各是多少?两轮后的面数是多少?

答案:一轮 V=9、E=16、F=8、B=8,欧拉示性数仍为 1;两轮面数为 32。内部共享对角线只新增一个点,不能按每面三条边各新增一次。

练习二:单三角形顶点 v=(0,0),两个边界邻点为 (1,0)、(0,1)。若边界规则被误写成“保持旧顶点不动”,计数式会不会失败?需要哪项验收区分?

答案:计数仍可能全部通过,因为位置错误没有改变连接。应检查更新位置为 (1/8,1/8),并用固定视野的轮廓图辅助观察。拓扑数量和几何位置是两种独立验收。

系列导航与资料

前篇:13:少量控制点怎样生成曲线系列入口。后篇:15:怎样减少面数又控制形变

PBRT 第三版 §3.8 Subdivision Surfaces,核对 beta、weightOneRing、weightBoundary、边点与子面构造,并区分后续极限位置步骤。Pixar OpenSubdiv 官方说明,Subdivision Schemes 与 Boundary Interpolation Rules,核对 Loop/Catmull-Clark 的输入类型和边界规则并非唯一默认。两份材料于 2026-09-20 实际读取;本篇数值表与图像来自独立累计工程。