127 lines
6.7 KiB
Markdown
127 lines
6.7 KiB
Markdown
# `--reducer quad` 升级为可靠四边重拓扑 设计
|
||
|
||
日期:2026-07-23
|
||
|
||
## 背景与动机
|
||
|
||
真机排查(gargoyle 500k、supergirl 1M)确认了现有 `--reducer quad` 的三个问题:
|
||
|
||
1. **高密度输入必破洞**:QR 引擎从很高密度网格(500k~1M 面)激进减面时,非确定性地打出
|
||
孔洞/非流形边,水密门(零容忍)几乎总判破损 → 回退 collapse。实测把高模先 collapse 到
|
||
**中等密度(~50k)** 再喂 QR,输出即干净。
|
||
2. **导出前被三角化**:`bl_decimate.main` 在 quad 成功路径也调 `_triangulate`,导致"quad 后端"
|
||
的 FBX 里其实是三角面,四边拓扑被毁——与"要四边"矛盾。
|
||
3. **零容忍门太严**:有机角色(supergirl)QR 常只留 1 个薄部位小洞(约 3 开边),门把这个
|
||
近乎干净的 QR 一票否决。补掉小洞即净。
|
||
|
||
用户决定:**改造现有 `--reducer quad`**,把"预 collapse + QR + 补小洞 + 保四边"整套配方内建,
|
||
让 quad 后端真正可用。`collapse` 后端与默认值(quad)不变。
|
||
|
||
## 约束
|
||
|
||
- 不加外部依赖。
|
||
- `collapse` 后端行为**零变化**;`--reducer` 默认仍为 `quad`。
|
||
- QR 任何灾难性失败仍**自动回退 collapse**,绝不中断(保留现有回退契约)。
|
||
- 不 stage 用户脏工作区(Unity/src/产物)。
|
||
- 纯策略(cap 判断、软门)与 Blender 操作分离,纯逻辑 TDD。
|
||
|
||
## 新流程(`--reducer quad`)
|
||
|
||
`bl_decimate.main()` 的 quad 分支改为:
|
||
|
||
1. **预 collapse**:`_clean_mesh` + `_triangulate` 后,若面数 > `qr_input_cap`(默认 50000),
|
||
先 `_decimate` 到 cap + `_triangulate`;≤ cap 则跳过。给 QR 一个不炸洞的中等密度输入。
|
||
2. **QR 重拓扑**:`_remesh_quad` 导出预处理网格 → `xremesh.exe`(`ExactQuadCount=1`,
|
||
目标 `tris_to_target_quads(--tris)` 四边)→ 导回。
|
||
3. **补小洞**:QR 导回后 `fill_holes(sides <= QR_HOLE_MAX_SIDES=6)`,补掉薄部位零星小洞。
|
||
4. **软水密门**:补洞后比对输入基线——`topology_acceptable(src_b, src_nm, out_b, out_nm,
|
||
tol=QR_CATASTROPHIC_TOL)`;`out_b <= src_b + tol and out_nm <= src_nm + tol` 则接受
|
||
(保留四边);否则判灾难性破碎,弃用 QR **回退 collapse**(标 `quad->collapse`)。
|
||
5. **保四边导出**:quad 成功路径**不再 `_triangulate`**,`_do_unwrap` 与导出直接作用于四边网格
|
||
(FBX/Cycles 烘焙/Unity 均支持四边;Unity 导入自动三角化)。
|
||
|
||
collapse 路径与 quad->collapse 回退路径**仍三角化**(collapse 本就产三角)。
|
||
|
||
## 架构与接口
|
||
|
||
### `qr_bridge.py`(纯逻辑,不 import bpy)
|
||
|
||
- 常量:`QR_INPUT_CAP = 50000`、`QR_HOLE_MAX_SIDES = 6`、`QR_CATASTROPHIC_TOL = 12`。
|
||
- 修改 `topology_acceptable(src_boundary, src_nonmanifold, out_boundary, out_nonmanifold,
|
||
tol=0)`:加 `tol` 参数(默认 0,向后兼容现有调用/测试),判定改为
|
||
`out_boundary <= src_boundary + tol and out_nonmanifold <= src_nonmanifold + tol`。
|
||
- 新增 `precollapse_target(face_count, cap=QR_INPUT_CAP) -> int | None`:`face_count > cap`
|
||
返回 `cap`,否则 `None`(跳过预 collapse)。
|
||
|
||
### `bl_decimate.py`(Blender 内)
|
||
|
||
- 新增 `_fill_small_holes(obj, max_sides)`:编辑模式全选 → `bpy.ops.mesh.fill_holes(sides=max_sides)`
|
||
→ 回物体模式。异常降级不中断。
|
||
- 修改 `_remesh_quad(obj, target_tris, warnings)`:导回 retopo 后
|
||
(a) `_fill_small_holes(retopo, qr_bridge.QR_HOLE_MAX_SIDES)`;
|
||
(b) 用软门 `topology_acceptable(..., tol=QR_CATASTROPHIC_TOL)` 判定;
|
||
(c) 接受则删原 obj、retopo 设为 active、return True(**不三角化**);拒绝则弃 retopo、
|
||
恢复 obj 选中态、return False。其余(引擎发现/子进程/轮询/选择态恢复)不变。
|
||
- 修改 `main()`:
|
||
- 解析 `qr_input_cap = int(argv[6]) if len(argv) > 6 else QR_INPUT_CAP`。
|
||
- quad 分支:`_remesh_quad` 前按 `precollapse_target` 预 collapse;成功后**去掉**
|
||
`_triangulate(obj)`(保四边)。`orig`(tris_before)仍取预 collapse 前的高模面数。
|
||
|
||
### `model_decimate.py`(CLI)
|
||
|
||
- 加 `--qr-input-cap`(type int,default `QR_INPUT_CAP`),透传为 Blender argv 第 7 位(index 6)。
|
||
- 校验 `> 0`。摘要打印沿用(`后端` 已有)。
|
||
|
||
## 数据流
|
||
|
||
```
|
||
FBX --import--> join --clean--> triangulate --orig
|
||
reducer=quad:
|
||
面数 > cap? --yes--> collapse 到 cap --triangulate
|
||
_remesh_quad: export --xremesh(exact)--> retopo --import
|
||
fill_holes(<=6) --> 软门(tol) 过? --yes--> 保留四边(不三角化)
|
||
--no--> 弃用, 回退 collapse(三角)
|
||
reducer=collapse 或 quad->collapse: decimate 循环(三角)
|
||
--> _do_unwrap(smart/seam) --> export(四边或三角) + MT_SUMMARY{reducer,...}
|
||
```
|
||
|
||
## 错误处理
|
||
|
||
| 情况 | 处理 |
|
||
|---|---|
|
||
| 引擎缺失/超时/报错/无输出/导回无 mesh | warning,回退 collapse(现有) |
|
||
| fill_holes 异常 | warning,跳过补洞,继续走软门 |
|
||
| 补洞后仍灾难性破损(超 tol) | warning,弃 QR 回退 collapse |
|
||
| 预 collapse 后 QR 仍失败 | 回退 collapse(在已预collapse的网格上继续减到目标) |
|
||
|
||
回退后 `reducer` 标 `quad->collapse`,绝不中断。
|
||
|
||
## 已知取舍
|
||
|
||
- 预 collapse 会在 QR 前损失部分细节 → 低模形状比"直接 QR"略有偏差(实测形变从 ~0.015%
|
||
升到 ~0.06%,量级仍很小)。**烘焙细节来自原始高模**(model_bake 用 high FBX),故低模这点
|
||
偏差对最终贴图影响小;换来的是 QR 不破洞、能保四边。取舍值得。
|
||
- 补洞产生的少量 n-gon 补丁(≤6 边)与 QR 偶发三角使输出"以四边为主",非 100% 纯四边。
|
||
|
||
## 测试
|
||
|
||
- **`tests/test_qr_bridge.py`**:
|
||
- `topology_acceptable` 加 `tol`:`(0,0,3,0,tol=12)->True`、`(0,0,13,0,tol=12)->False`、
|
||
默认 `tol=0` 时 `(0,0,1,0)->False`(现有语义不变)。
|
||
- `precollapse_target`:`(1000000,50000)->50000`、`(40000,50000)->None`、边界 `(50000,50000)->None`。
|
||
- **回归**:`python -m unittest discover -s tests` 全绿(现有测试含 `topology_acceptable` 旧调用
|
||
仍按 tol=0 通过)。
|
||
- **真机 smoke**:
|
||
- `model_decimate supergirl.fbx --tris 10000 --reducer quad`:backend=`quad`、输出以四边为主
|
||
(quad 数 >> tri)、开边/非流形 ≤ 输入基线+tol、有 UV。
|
||
- `gargoyle.fbx --tris 10000 --reducer quad`:同上或合理 `quad->collapse`。
|
||
- `--reducer collapse` 回归:面数、拓扑与升级前一致(零变化)。
|
||
- 强制回退(`QR_ENGINE` 指错):`quad->collapse`、输出正常。
|
||
|
||
## 明确不做(YAGNI)
|
||
|
||
- 不做硬边引导/直边保真(实测无效,另属课题)。
|
||
- 不暴露 fill-holes sides、catastrophic tol 为 CLI(内置常量足够;只暴露 `--qr-input-cap`)。
|
||
- 不改 model_bake/model_translator(四边低模它们已支持)。
|
||
- 不追求 100% 纯四边(少量 n-gon/tri 可接受)。
|