109 lines
5.9 KiB
Markdown
109 lines
5.9 KiB
Markdown
# model_bake 自适应射线距离 设计
|
||
|
||
日期:2026-07-23
|
||
|
||
## 背景与动机
|
||
|
||
真机诊断确认:`--reducer quad` 低模烘焙时,前裙等**薄部件**出现深色"脏斑"。量化结果:
|
||
supergirl quad 低模 **7.06% 的面**,selected-to-active 烘焙射线**穿透薄表面、打到背/内面**
|
||
(命中法线与低模面法线反向),把里面的深色烘到正面。
|
||
|
||
- **机制**:`bl_bake._bake_pass` 用固定 `cage_extrusion=ray`、`max_ray_distance=ray*2`,
|
||
其中 `ray = 低模包围盒对角线 * 2%`(`DEFAULT_RAY_PCT`)。对薄部件,`max_ray`(=4% 对角线)
|
||
远大于裙子厚度 → 射线穿透。
|
||
- **为何 quad 特有**:QR 重拓扑低模偏离高模表面;collapse 用原始顶点贴在高模上,射线第一下
|
||
即中正面,故干净。
|
||
- **为何自适应能修**:7% 穿透面的面心其实离**正面**很近(分离≈0,只是射线过长冲到背面)。
|
||
按实测分离距离把射线调小后,射线先命中最近的正面 → 穿透面转为正面命中,脏斑消除。
|
||
|
||
固定 2% 射线是问题根源。改为**按实测低↔高分离距离**自适应,是 `2026-07-16-model-bake`
|
||
计划早已设计但未落地的 `adaptive_ray_distance` 思路,本 spec 聚焦落地这一块。
|
||
|
||
## 约束
|
||
|
||
- 不加外部依赖(Python 标准库 + `mathutils`)。
|
||
- 显式 `--ray-distance` 仍优先、覆盖自适应。
|
||
- 自适应失败(无分离样本/包围盒退化)**降级回退**到现有固定 2%,绝不中断烘焙。
|
||
- 不改烘焙 pass 结构、输出命名、贴图内容协议;`MT_SUMMARY` 只增字段不改既有。
|
||
- 纯逻辑(percentile、adaptive_ray_distance)与 Blender 操作分离,纯逻辑 TDD。
|
||
- 不做 `2026-07-16` 计划的法线/UV 修复;不加 cage 物体支持(YAGNI)。
|
||
|
||
## 架构(全在 `bl_bake.py`,collapse/quad 通用)
|
||
|
||
### 纯函数(可单测,不 import bpy)
|
||
|
||
- `percentile(values, q) -> float`:插值分位数;空列表或 q∉[0,1] 抛 `ValueError`。
|
||
- 常量 `RAY_SAFETY=1.5`、`RAY_LOWER_PCT=0.0005`、`RAY_UPPER_PCT=0.01`。
|
||
- `adaptive_ray_distance(distances, bbox_dims) -> dict`:
|
||
- `diagonal = sqrt(sum(d^2))`;`diagonal<=0` 抛 `ValueError`。
|
||
- `p99 = percentile(distances, 0.99)`;`raw = max(p99*RAY_SAFETY, diagonal*RAY_LOWER_PCT)`;
|
||
`value = min(raw, diagonal*RAY_UPPER_PCT)`。
|
||
- 返回 `{"distance_p99": p99, "value": value, "capped": raw > diagonal*RAY_UPPER_PCT}`。
|
||
- 即:射线 = 分离 P99×1.5,夹在包围盒对角线 0.05%~1% 之间(旧固定值是 2%,故必然更小)。
|
||
|
||
### Blender 侧
|
||
|
||
- `_measure_separation(low, high, limit=10000) -> list[float]`:
|
||
- 高模建 `BVHTree.FromPolygons(高模世界坐标顶点, 多边形, all_triangles=False)`。
|
||
- 低模面若 ≤limit 全取,否则均匀采样 limit 个面;对每个面心(世界坐标)求 `bvh.find_nearest`
|
||
的距离,收集为距离数组(丢弃无命中的)。
|
||
- 返回距离列表(可能为空)。
|
||
|
||
### main 集成
|
||
|
||
- `ray_arg == "auto"` 分支:
|
||
- `dists = _measure_separation(low, high)`。
|
||
- 若 `dists` 非空:`info = adaptive_ray_distance(dists, tuple(low.dimensions))`;`ray = info["value"]`;
|
||
`ray_source = "adaptive"`;`info["capped"]` 为真时记警告"自适应射线达对角线 1% 上限,检查高低模对应"。
|
||
- 若 `dists` 空 或 `adaptive_ray_distance` 抛异常:`ray = estimate_ray_distance(low_dims)`(回退固定 2%),
|
||
记警告"分离测量失败,回退固定射线",`ray_source = "fixed_fallback"`。
|
||
- `ray_arg != "auto"`:`ray = float(ray_arg)`,`ray_source = "explicit"`(不变)。
|
||
- `_bake_pass` 的 `cage_extrusion=ray, max_ray_distance=ray*2.0` **关系不变**(ray 已是自适应小值)。
|
||
|
||
## 数据流
|
||
|
||
```
|
||
import high/low --apply transform-->
|
||
ray_arg=auto: high 建 BVH -> 低模面心最近距离数组 -> adaptive_ray_distance(P99*1.5, clamp[0.05%,1%])
|
||
(空/异常 -> 回退固定 2%)
|
||
ray_arg=显式: float(ray_arg)
|
||
--> 各 pass _bake_pass(cage=ray, max_ray=ray*2) --> PNG + MT_SUMMARY{ray_distance, ray:{source,p99,value,capped}}
|
||
```
|
||
|
||
## MT_SUMMARY 变化
|
||
|
||
在现有 `"ray_distance": round(ray,6)` 基础上新增 `"ray"` 字段:
|
||
`{"source": "adaptive|explicit|fixed_fallback", "distance_p99": <float|None>, "value": <float>, "capped": <bool>}`。
|
||
`model_bake.py` 打印一行射线信息(来源/P99/距离/是否夹取)。
|
||
|
||
## 错误处理
|
||
|
||
| 情况 | 处理 |
|
||
|---|---|
|
||
| 低模无面 / BVH 无命中 → 分离数组空 | 回退固定 2% 射线,记警告 |
|
||
| 低模包围盒对角线为 0(退化) | `adaptive_ray_distance` 抛 ValueError → 回退固定 2%,记警告 |
|
||
| 显式 `--ray-distance` | 直接用,不测分离 |
|
||
| 自适应值被夹到 1% 上限 | 记警告(提示高低模可能不匹配),仍用夹取值 |
|
||
|
||
## 测试
|
||
|
||
- **`tests/test_bl_bake.py`**(纯函数):
|
||
- `percentile`:`[0,10]` q=0.25 → 2.5;空列表/q=1.1 抛 ValueError。
|
||
- `adaptive_ray_distance`:
|
||
- 贴合样本 `[0.002]*20, bbox(1,0,0)` → value=0.003(P99×1.5,未夹)、capped=False。
|
||
- 全 0 分离 → value=下限 0.0005、capped=False(下限保护)。
|
||
- 大分离 `[0.02]`,bbox(1,0,0) → value=0.01(夹到 1% 上限)、capped=True。
|
||
- 零包围盒 → ValueError。
|
||
- **回归**:`python -m unittest discover -s tests` 全绿。
|
||
- **真机 smoke**:
|
||
- 重烘 supergirl quad → 用 backhit 诊断(临时脚本)验证**穿透面 7% → 近 0**,前裙脏斑消失,
|
||
且无大面积落空(黑像素不显著增加)。
|
||
- collapse 回归:射线 source=adaptive、烘焙正常,贴图与升级前相当。
|
||
- 显式 `--ray-distance 0.02` → source=explicit,行为不变。
|
||
|
||
## 明确不做(YAGNI)
|
||
|
||
- 不做法线/UV 自动修复(`2026-07-16` 计划另一块)。
|
||
- 不加 cage 物体支持、不加 per-face 射线(单一自适应全局值已解决穿透)。
|
||
- 不新增 CLI 旋钮(复用现有 `--ray-distance` 覆盖)。
|