Files
AIC-Project/docs/superpowers/specs/2026-07-23-adaptive-ray-bake-design.md
2026-07-23 17:25:44 +08:00

109 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.003P99×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` 覆盖)。