相当于把 Photoshop 的「上移一层 / 下移一层」(准确说是 McCann & Pollard 2009 的 local layering)搬到俯视的 3D 布料和绳子上:点一下时,把场景投影成一张平面区域图,每块区域里是一叠有序的布片;然后暴力枚举所有不穿模的叠放组合(一个小型约束满足问题),挑「逆序对最少」的那个,再用最小二乘把离散顺序还原成连续 z 值,最后交给物理模拟收尾。拖拽时则按鼠标下方的叠放顺序给被拖顶点的 z 夹上下界。
论文里的抽象词,在这篇论文的具体任务中各自指什么:
| 术语 | 在本文中具体是什么 |
|---|---|
| apparent layer | 从正上方往下看时,屏幕某块区域里的一片连通布料(或一段绳子)。不是持久数据结构——每次操作时临时算出来,用完即扔。「apparent」= 看上去的层,而不是模型里存的层。 |
| planar map | 把所有布料边界、折线(或绳子轮廓)投影到屏幕上,切出来的一张 2D 多边形划分图。 |
| region | planar map 中的一个面。区域内部各处「叠了几层、谁在谁上面」都一样(图 7 中的数字就是层数)。 |
| layer | 布料:某 region 里一个连通的 mesh 片段,附带它包含的顶点。绳子:与该 region 相交的一串球体(一个球可同时属于多个 layer)。 |
| list graph | 节点 = 至少有 2 层的 region,节点里存一个从上到下的有序 layer 列表;边 = 两个 region 相邻且有 layer 在原始布料上是连着的。 |
| type I violation | 相邻 region 之间的顺序矛盾:左边区域 A 在 B 上,右边区域 A 的延续却在 B 的延续下面 ⇒ 两片布在交界处互相穿过。 |
| type II violation | 同一 region 内,共享一条折线的两层在顺序里不相邻,即有别的布插进了折缝 ⇒ 折缝处穿模。只在布料系统出现,是本文新加的约束。 |
| swap(打分单位) | 一对 layer 在新顺序中的相对上下关系与原来相反,即一个逆序对(Kendall tau 距离)。n 层的区域最多 n(n−1)/2 个。 |
| fraction | 拖拽算法用的最小单元:布料的一个三角面,绳子的一个球。被拖的 fraction = 与被拖顶点相连的那几个三角面。 |
| boundary drag / intact drag | 拖边界顶点:其余部分靠物理模拟跟随(用来旋转、弯折、对折)。拖内部顶点:整块布一起平移。 |
| shape matching | Müller et al. 2005 的无网格变形模拟:每帧把粒子拉向「最佳刚体变换后的原形状」。这里只用来保持布 / 绳的完整、推开轻微碰撞,不追求物理真实。 |
场景表示很普通:布料是单层三角网格,绳子是一串球。用 shape matching 做简单物理和碰撞,并假设操作前场景没有穿模。两种操作都遵循「临时算出表观层 → 在层上做决策 → 改 z → 物理收尾」。
用户点击某处,系统让最上面那层移到它下面那一层的下方。只换这两层常常不可能:可能在折缝处穿模(图 2g→h,系统自动多换一层),或者必须把交换传播到周围区域才不会互穿(图 2c→d/e/f)。合法结果不止一种时,返回改动最小的那个(图 2c 返回 d)。想要别的结果,就在结果上别的位置再点一下——作者试过用缩略图让用户挑候选,发现小图看不懂、逐个看又烦,不如多点几下快。
更极端的情况:对折两次的餐巾有两种合法结果,返回 swap 最少的那个,再点中心就得到另一个;卷成螺旋的布只有一种合法结果——整卷所有局部层顺序全部反转。
为什么只做「交换顶两层」?① 单击最快;② 在不同位置连续点击就能到达多种构型;③ 深层看不见,本来就很少要动(动深层需要先隐藏上层等额外操作,不在本文范围)。
拖动时 z 自动调整:原来在谁上面 / 下面,继续保持,避免拖着拖着突然穿过去;新碰到的物体,默认从上面盖过(drag-over),按住 shift 从下面钻过(drag-under)。两种拖法:boundary drag(拖边界顶点,只调被拖顶点附近的深度,其余靠模拟跟随)和 intact drag(整体平移,只调「前进前沿」——运动方向朝外的边界边——附近的深度)。
把「改叠放顺序」变成一个离散约束满足问题——每块区域选一个排列,相邻区域不矛盾、折缝里不夹东西——取改动最少的解,再用最小二乘把排列还原成连续 z。
flowchart LR
A["Click"] --> B["Planar map"]
B --> C["List graph"]
C --> D["Per-region perms"]
D -->|"drop type II"| E["BFS combine"]
E -->|"drop type I"| F["Score"]
F -->|"min weighted swaps"| G["Least squares z"]
G --> H["Shape matching"]
原版 local layering 是单遍传播 flip-up / flip-down:先在点击处翻转,再一路把冲突推给邻居。遇到折叠和自遮挡就失效:
与 local layering 的关键区别:列表里的条目是物体片段而不是物体 ID,所以同一块布折过来压住自己时,会在同一 region 里出现两次,自遮挡就自然能表示了。
作者明确知道这是组合爆炸问题(找全局一致的叠放顺序与平折折纸可折性同类,NP-complete,Bern & Hayes 1996),但目标场景一般只有 1–2 层、最多 5–6 层,穷举够快,且比 beam search 更不容易漏掉好解。打分规则也只是初步方案:作者提到可以对谷折 / 山折方向的改变额外罚分(视觉上很显眼),或者按离点击点的距离加权。
每个 layer 一个未知深度 \(z_l\)。解一个最小二乘:同一 region 内上下相邻两层的深度差等于物体厚度 \(t\);不同 region 之间相连的层深度差为 0;再固定一个中心点,去掉整体平移的自由度:
\[ \min_{z}\;\sum_{\text{region } r}\;\sum_{(l\,\text{above}\,m)\in r}\big(z_l - z_m - t\big)^2 \;+\;\sum_{(l,m)\ \text{connected across regions}}\big(z_l - z_m\big)^2 \;+\;\big(z_{c} - z_{c}^{0}\big)^2 \]
然后把层深度写回顶点:布料顶点取所属层的深度,折线上的顶点取两侧复制顶点的平均;绳子的球取所属各层深度的平均。最后跑 shape matching 把形状抚平。
只处理被拖的 fraction(三角面或球),每一帧给它算一个新 z。intact drag 时对前进前沿上的每个顶点各跑一遍同样的算法。
直观理解:这是 Sketch 系统「新物体放在下方物体表面上」规则的推广——不只看正下方是什么,还看它和刚才压着的是不是同一块布的延续,所以拖过一块本来就垫在下面的布时,不会突然钻到它下面去。
| spiral | napkin | scarf | ribbon | tie | |
|---|---|---|---|---|---|
| 顶点数 | 213 | 184 | 90 | 165 | 122 |
| 区域数 | 4 | 12 | 25 | 33 | 45 |
| 层数(各区域合计) | 7 | 24 | 45 | 77 | 107 |
| 合法组合数 | 1 | 2 | 12 | 40 | 294 |
| planar map + list graph (ms) | 719 | 453 | 218 | 422 | 407 |
| 穷举搜索 (ms) | 1.3 | 34 | 10 | 76 | 1453 |
| beam search (ms) | 1.3 | 32 | 0.5 | 4.5 | 5.3 |
复杂度:穷举部分是 \(O\big(\prod_{i\in\text{regions}} N_i!\big)\),只取决于区域数和每区域层数,跟网格分辨率无关;网格面数只影响建 planar map,是线性的。
| Task 1 | Task 2 | Task 3 | |
|---|---|---|---|
| 耗时 (s) | 121.3 ± 41.3 | 47.5 ± 22.0 | 39.2 ± 12.5 |
| 点击次数 | 24.2 ± 8.7 | 10.0 ± 7.2 | 9.4 ± 4.6 |
| 撤销次数 | 0.0 ± 0.0 | 0.4 ± 1.3 | 0.0 ± 0.0 |
结论:多数人很快上手,几乎不撤销;少数人觉得某些交换结果出乎意料,但试几次也都完成了。注意 Task 2/3 平均要点约 10 次,说明「一次点出想要的结果」并不常见——用户是靠在不同位置多点几下来「搜索」的。