Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
114 lines
6.6 KiB
Markdown
114 lines
6.6 KiB
Markdown
# UVPM 排布利用率优化:开启旋转 + 启发式搜索
|
||
|
||
日期:2026-07-21
|
||
范围:`Tools/ModelTranslator/bl_decimate.py`(`_uvpm_repack`)、`README.md`
|
||
|
||
## 背景与动机
|
||
|
||
减岛优化后 gargoyle 3000 面走 seam@45、390 岛,但 UV 利用率仅 **52.3%**——约 48% 为空白(烘焙时填黑,用户观感为"错误的黑色块")。
|
||
|
||
根因定位(已查 `_uvpm_repack` `bl_decimate.py:286-314`):
|
||
- 当前 UVPM 调用只设 `pixel_margin`(4px@2048),**旋转/启发式搜索全用 UVPM4 默认值**:`bpy.ops.uvpackmaster4.pack(mode_id='pack.single_tile', pack_op_type='0')`。
|
||
- gargoyle 减面后 UV 由大量**又长又斜的薄条岛**(尖刺/薄翅缘几何)主导。薄斜条在缺乏精细旋转对齐时外接矩形浪费极大——这是 packer 效率的天敌。
|
||
- UVPM 的旋转 + 启发式迭代搜索正是为此类不规则岛设计的,当前未启用。
|
||
|
||
用户选择:**只调 UVPM 排布**(不碰边距/烘焙),启发式耗时可接受(离线工具,设时间上限)。
|
||
|
||
## 目标
|
||
|
||
开启 UVPM4 的旋转与启发式搜索,把薄条岛塞得更紧,**明显提升 gargoyle 3000 的 UV 利用率**,且不退化其他指标(岛数、翻转、重叠不变差)。
|
||
|
||
## 非目标(YAGNI)
|
||
|
||
- 不改像素边距 `pixel_margin`(4px)与 `model_bake` 的烘焙 padding(避免渗色,用户明确排除)。
|
||
- 不改展开/减岛/清理逻辑(`_do_unwrap`、`_clean_mesh`、`--max-overlap` 均不动)。
|
||
- 不动 `average_islands_scale`(纹素均匀,保留)。
|
||
- 不追求硬性利用率数字(薄条几何天然受限),只求"明显上升"。
|
||
|
||
## 设计
|
||
|
||
### 1. UVPM4 属性探针(实现首步 spike)
|
||
|
||
因 UVPM4 属性名/默认值随版本可能不同,先用一次性脚本确认,避免猜 API:
|
||
|
||
- 启用 UVPM4(复用 `_uvpm_enable` 的 headless GPU 补丁思路)。
|
||
- 枚举 `bpy.context.scene.uvpm4_props.default_main_props` 中旋转/启发式相关属性:名字、默认值、类型/取值范围(候选:`rotation_enable`、`rotation_step`、`rotation_step_value`、`heuristic_enable`、`heuristic_search_time`、`heuristic_max_wait_time` 等)。
|
||
- 在测试网格(UV 球 + smart_project)上开启这些属性跑一次 `uvpackmaster4.pack`,确认 headless 不报错、返回 `FINISHED`。
|
||
- 产出:确切属性名与合理取值,供第 2 节定稿;脚本用完删除、不入库。
|
||
|
||
### 2. `_uvpm_repack` 开启旋转 + 启发式
|
||
|
||
新增防御式辅助与常量,在 `_uvpm_repack`(`bl_decimate.py:294-305`)的 `pixel_margin` 设置之后、`pack` 调用之前设置属性:
|
||
|
||
```python
|
||
# 常量(值以探针结论为准,示意)
|
||
UVPM_ROTATION_STEP = <探针定> # 旋转步进(度),更细利于薄条对齐
|
||
UVPM_HEURISTIC_TIME = <探针定> # 启发式搜索秒数上限(如 5–10)
|
||
|
||
def _uvpm_set(p, name, value, warnings):
|
||
"""防御式设 UVPM 属性:属性存在才设,否则记警告跳过——
|
||
名字不匹配(版本差异)时降级为按原 margin 跑 UVPM,不丢整个排布。"""
|
||
if hasattr(p, name):
|
||
setattr(p, name, value)
|
||
else:
|
||
warnings.append("UVPM 属性 %s 不存在,跳过" % name)
|
||
```
|
||
|
||
在 `_uvpm_repack` 内(属性名以探针为准):
|
||
|
||
```python
|
||
_uvpm_set(p, "rotation_enable", True, warnings)
|
||
_uvpm_set(p, "rotation_step", UVPM_ROTATION_STEP, warnings)
|
||
_uvpm_set(p, "heuristic_enable", True, warnings)
|
||
_uvpm_set(p, "heuristic_search_time", UVPM_HEURISTIC_TIME, warnings)
|
||
```
|
||
|
||
- 属性设置在现有 `try/except Exception`(`bl_decimate.py:293-314`)内——但用 `_uvpm_set` 的 `hasattr` 守卫,使**单个属性名不匹配只跳过该项、不抛异常**,从而不会触发整体失败回退(那会丢掉 UVPM 全部排布)。
|
||
- 旋转后的 UV 对烘焙无影响(烘焙按 UV 原样采样)。
|
||
- 边距、`pack` 调用签名(`mode_id`/`pack_op_type`)保持不变,除非探针发现启发式必须换 `pack_op_type`(若如此,在第 2 节据实调整并记录)。
|
||
|
||
### 3. 数据流(不变,仅 UVPM 内部行为增强)
|
||
|
||
```
|
||
… → _do_unwrap → 选定 UV → _uvpm_repack(现在:margin + 旋转 + 启发式搜索)→ 抽 UV 几何 → export
|
||
```
|
||
|
||
## 错误处理
|
||
|
||
| 失败点 | 处理 |
|
||
|---|---|
|
||
| UVPM 某属性名不存在(版本差异) | `_uvpm_set` 记警告、跳过该属性,其余照设、pack 照跑 |
|
||
| UVPM 启用/pack 整体失败 | 现有 `_uvpm_repack` try/except:记警告、`_uvpm_failed=True`、回退内置 pack(行为不变) |
|
||
| 启发式耗时过长 | 由 `UVPM_HEURISTIC_TIME` 上限约束 |
|
||
|
||
原则:排布增强单点失败降级,不影响主流程与 FBX 产出。
|
||
|
||
## 测试与验证
|
||
|
||
1. **现有单测不回归**:`python -m unittest tests.test_bl_decimate tests.test_uv_preview tests.test_unity_assets`(本改动无纯函数,主要确认无破坏)。
|
||
2. **端到端(主验收)**:`python model_decimate.py src/gargoyle.fbx --tris 3000 --max-overlap 0.15`,对比基线(seam@45 / 390 岛 / **利用率 52.3%** / 翻转 0% / 重叠 13.3%):
|
||
- 利用率**明显上升**;
|
||
- 岛数不变(排布不改岛数)、翻转仍 0%、重叠仍 ≤ 门限;
|
||
- 记录 pack 耗时增量;
|
||
- Read 观察图确认薄条更紧、黑块变少。
|
||
3. **回归**:`python model_decimate.py src/well1500.fbx --tris 5000`(正常走 seam@66 的模型)确认排布增强不破坏,流程不崩、产物正常。
|
||
|
||
## 验收标准
|
||
|
||
- gargoyle 3000(`--max-overlap 0.15`):UV 利用率明显高于 52.3%;岛数/翻转/重叠不退化。
|
||
- 属性名不匹配时降级不崩、仍出 UVPM 排布(或内置兜底)。
|
||
- well1500 回归正常。
|
||
- 现有单测全绿。
|
||
|
||
## 实现注意
|
||
|
||
- 属性名/取值一律以第 1 节探针结论为准;spike 未确认前不写死具体名。
|
||
- `_uvpm_set` 的 `hasattr` 守卫是关键:防止版本差异把整个 UVPM 排布拖垮。
|
||
- 若探针发现启发式需要非 `'0'` 的 `pack_op_type` 或专门 `mode_id`,在计划中据实调整并在 README 注明。
|
||
|
||
## 实现修订(2026-07-21,系统调试后)
|
||
|
||
实现中发现 **UVPM 启发式搜索在 headless 下会随机崩溃引擎进程**(`Error: Engine process died unexpectedly`):well1500 崩 3/3、gargoyle 崩 1/2(同模型重跑结果不一致),旋转步进无辜。启发式因此不可靠、且使输出非确定。
|
||
|
||
**最终落地**:弃用启发式,只保留 `rotation_step=15`(稳定、零崩溃、确定性)。实测利用率提升较小但可靠——gargoyle 3000(`--max-overlap 0.15`)52.3%→**55.6%**,well1500 5000 稳定 `+uvpm`。移除 `_uvpm_pack_once`/重试与 `UVPM_HEURISTIC_TIME`;`_uvpm_repack` 回到单次 UVPM(rotation 15)。验收从"明显上升"下修为"稳定小幅上升且零崩溃/确定性"。
|