Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.6 KiB
6.6 KiB
UV 减岛优化:--max-overlap 参数 + 减面前网格清理
日期:2026-07-21
范围:Tools/ModelTranslator/bl_decimate.py、Tools/ModelTranslator/model_decimate.py
背景与动机
减面工具的 seam 角度扫描(66→55→45→35°)在质量门(翻转 ≤2%、重叠 ≤8%)失败时下探角度抢救。角度越低 → seam 越多 → UV 岛越碎。实测 gargoyle 3000 面走到 seam@35,产生 686 个碎岛、利用率仅 38.2%。
关键事实:
- UVPackmaster 只排布、不合并岛,无法减少岛数。
- 低利用率是岛数过多的症状——每岛一圈 padding,岛越碎、总周长越大、留白越多。
- 两个问题同源同解:减少 seam/岛数(展开阶段),利用率随之回升。
用户诉求:岛数优先,愿意用少量重叠/利用率换更少的岛。
目标
- 让更高的 seam 角度(岛更少)能通过质量门被选中——通过可运行时覆盖的重叠容忍(新增
--max-overlap)。翻转门限保持严格。 - 减面前清理源网格(焊接重合点 + 去退化面),给 collapse 更干净的流形,从源头减少被迫加的 seam。
- 默认行为不变;减岛为显式开启。
非目标(YAGNI)
- 不改 UVPM 排布逻辑(它减不了岛)。
- 不放宽翻转门限
UV_FLIP_TOL(翻转无法靠排布修复)。 - 不改减面后低模(清理只在减面前的高模上做,避免改变目标面数)。
- 不改
model_bake.py/model_translator.py/uv_preview.py。 - 不做减面后的碎岛后处理/岛合并(无通用可靠算子)。
设计
1. --max-overlap 参数穿透
重叠容忍从模块常量升级为可运行时覆盖,翻转门限不动。
bl_decimate.py:
uv_gate_ok(flipped, overlap, overlap_tol=UV_OVERLAP_TOL):return flipped <= UV_FLIP_TOL and (overlap or 0.0) <= overlap_tol。加默认参数,现有调用与测试(uv_gate_ok(0.0, 0.0)等)不受影响。pick_best_candidate(candidates, overlap_tol=UV_OVERLAP_TOL):把overlap_tol透传给uv_gate_ok。_do_unwrap(obj, mode, warnings, overlap_tol=UV_OVERLAP_TOL):扫描循环内uv_gate_ok(m["flipped"], m["overlap"], overlap_tol)与pick_best_candidate(candidates, overlap_tol)均用它。main():argv 增加第 5 个可选位置参数 max_overlap;解析overlap_tol = float(argv[4]) if len(argv) > 4 and argv[4] else UV_OVERLAP_TOL;传入_do_unwrap。
model_decimate.py:
- 新增
--max-overlap(type=float,默认None)。 - 传参:
run_blender_script(..., [args.input, out_fbx, str(args.tris), args.unwrap, "" if args.max_overlap is None else str(args.max_overlap)])(第 5 位;None → 空串,Blender 侧回落到内置默认)。 - 校验:
--max-overlap若给定需在 (0, 1] 内,否则ap.error。
日志与 mode 标注不变。放宽后,若 seam@55 重叠 12% ≤ 15%,即被选中,岛数远少于 seam@35。
2. 减面前网格清理
bl_decimate.py 新增 _clean_mesh(obj, warnings),在 main() 的 obj = join_meshes(meshes) 之后、_triangulate(obj) 之前调用:
- 进入 EDIT、全选
- remove_doubles(threshold=weld) # 焊接重合点
- dissolve_degenerate() # 去零面积/退化面
- 回 OBJECT
- 焊接阈值相对局部包围盒对角线:新常量
REL_WELD = 1e-4(对角线的 0.01%)。用obj.bound_box(局部坐标 8 角)算对角线diag,weld = diag * REL_WELD。避免 cm/m 单位差异导致绝对阈值焊过头或焊不动。diag <= 0(退化输入)时跳过焊接、记警告。 - 两步各自 try/except(
RuntimeError):失败记警告、跳过、不中断。 - 清理在三角化前做(remove_doubles/dissolve_degenerate 对任意网格有效;更干净的流形让后续三角化+collapse 少产狭长三角)。
3. 数据流
FBX → join → _clean_mesh(焊接+去退化) → triangulate → decimate(±3%)
→ _do_unwrap(overlap_tol=max_overlap or 8%):
seam 66→55→45→35(各 unwrap+stretch+均衡,用 overlap_tol 判门)
首个过门即选(角度高→岛少);pick_best_candidate(overlap_tol) 兜底择优
全失败 → smart
→ UVPM 一次 → 抽 UV 几何 sidecar → export fbx
→ MT_SUMMARY(json)
错误处理
| 失败点 | 处理 |
|---|---|
remove_doubles / dissolve_degenerate 不可用 |
try/except RuntimeError 记警告,跳过该步 |
| 包围盒对角线 ≤ 0 | 跳过焊接,记警告 |
--max-overlap 越界 (0,1] |
model_decimate 侧 ap.error 提前拒绝 |
| max_overlap 空/缺省 | Blender 侧回落 UV_OVERLAP_TOL(默认 8%),行为不变 |
原则:清理与门限放宽均为可选增强,单点失败降级不中断,FBX 必产出。
测试与验证
- 纯函数 TDD(
tests/test_bl_decimate.py):uv_gate_ok带overlap_tol:默认值下同现状;显式overlap_tol=0.15时 overlap=0.12 通过、0.16 不过;翻转仍按UV_FLIP_TOL严格(overlap_tol 放宽不影响翻转判定)。pick_best_candidate带overlap_tol:放宽后原本不过门(overlap 介于 8%~15%)的候选变为过门并可被选中;无参时同现状。
- 现有单测不回归:
python -m unittest tests.test_bl_decimate tests.test_uv_preview tests.test_unity_assets。 - 端到端:
python model_decimate.py src/gargoyle.fbx --tris 3000 --max-overlap 0.15:对比基线(686 岛 / 38.2% / seam@35 / 重叠 6.3%)——期望走更高角度、岛数明显下降、利用率上升;产出 FBX + UV 观察图。python model_decimate.py src/gargoyle.fbx --tris 3000(无--max-overlap):确认默认行为不回归(仍 8% 门限)。注意网格清理默认开启,默认跑的指标可能与历史基线略有不同——如变化记录并说明。
验收标准
--max-overlap 0.15下 gargoyle 3000:UV 岛数显著低于 686 且利用率高于 38.2%;mode反映实际生效的更高角度(如seam@55/seam@45)。- 默认路径(无
--max-overlap):翻转门限严格不变;流程不崩,FBX + 观察图照常产出。 - 网格清理任一算子不可用时降级不中断。
- 现有单测全绿;新增纯函数测试通过。
实现注意
_do_unwrap/pick_best_candidate/uv_gate_ok的新参数一律 keyword-default,保持向后兼容与现有测试不改。- argv 第 5 位为空串时必须回落默认,不能
float("")崩溃。 _clean_mesh改变面数(去退化会略减面),但发生在 decimate 之前,decimate 的 ±3% 修正逻辑照常把面数拉到目标,无影响。