Add specifications for two-times upscale and unified depth plane features
- Introduced the two-times upscale specification detailing requirements for unified output, fixed model presets, arbitrary dimensions, and consistent job behavior. - Added scenarios for existing models, repeated upscale, and handling of alpha preservation. - Created the unified depth plane specification outlining geometry modes, stable interface, and asset evaluation. - Defined scenarios for mode switching, relief and camera preservation, and validation of delivered assets.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-16
|
||||
@@ -0,0 +1,81 @@
|
||||
## Context
|
||||
|
||||
`common/depth_surface.py` 的 `build_surface_camera` 负责把「点的相机采样」换成「面的相机采样」:保持点自身的射线方向,只沿射线把深度缩放到相邻面的采样值(`projected = original × separated.z / original.z`)。`separated` 是 FACE 域的采样,消费到 POINT 域时按该点的保留相邻面取平均。
|
||||
|
||||
三个调用点的接入方式不一致:
|
||||
|
||||
| 组 | 切口来源 | 面深覆盖范围 |
|
||||
| --- | --- | --- |
|
||||
| `O Image Depth Cutout` | `Depth Split` 切边、网格轮廓 | `cut_vertex` + `always_vertex=outline`(全部自由边界点) |
|
||||
| `O Image Depth Plane` | `Depth Mask` 删点、`Depth Split` 切边 | 仅 `cut_vertex` |
|
||||
| `O Image Depth Panorama` | `Depth Mask` 删点、`Depth Split` 切边 | 仅 `cut_vertex` |
|
||||
|
||||
Plane 的矩形网格另有一条始终存在的自由边界:原画幅外框(UV 边距 0)。它以 `original_boundary` 的形式被 `smooth_boundary_camera` 与 `smooth_cut_boundary` 的 `protected` 参数排除在平滑之外。Panorama 的球面是闭合流形,Depth Mask 打开时唯一的自由边界就是删除产生的切口。
|
||||
|
||||
`Depth Mask` 删点后,切口点的深度仍取该点自身的纹理采样。Plane 的深度纹理使用 Linear + EXTEND 采样,外框行或无效区旁的像素被 clamp 或跨断裂混合时,切口点会顶出尖刺;Panorama 的点采样已有 nearest 保护,但切口与 Depth Split 切边仍走两套深度来源。
|
||||
|
||||
`common/validity.py` 的删点用 POINT 域的 Delete Geometry:无效顶点连同所有相邻面一起消失,切口因此比阈值轮廓多退一圈,剩下的悬空边还要再删一次。Panorama 的无效判定链先经 `evaluate_field(..., "POINT")` 固定到点域,Plane 的判定链则直接由 UV 属性与纹理采样组成。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** Depth Plane 与 Panorama 的 mask 切口与 Depth Split 切边遵循同一条深度规则,并与 Depth Cutout 的既有约定一致;规则内建在共享 helper 里,调用方不再枚举哪些点用面深,切口规则本身不带例外。
|
||||
|
||||
**Non-Goals:** Panorama 点采样的 nearest 保护、`Boundary Smooth` 与位置平滑的语义(含原画幅外框在边界带平滑中的保护)、`O Image Depth Cutout` 与 `O Image Relief Plane` 的行为。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 切口边界内建,删除 `always_vertex` 参数
|
||||
|
||||
规则统一后三个调用点都要它,参数只剩「要不要这条规则」一个含义,而三处答案相同,因此把它移进 `build_surface_camera`:helper 内部用 `edge_boundary_field` 求值边界点,再与 `cut_vertex` 一起接入既有的 `OR → Switch`。`always_vertex` 参数删除,不留兼容分支。代价是将来若有调用方需要退出这条规则,得重新引入参数——目前看不出这样的调用方。
|
||||
|
||||
### 切口 = 全部自由边界,不设例外
|
||||
|
||||
`build_surface_camera` 内部把自由边界直接当作切口,不接受调用方给定的点集合,也不接受排除字段。Plane 的原画幅外框因此与 mask 切口同规则,Depth Mask 关闭时外框也会取相邻面采样。
|
||||
|
||||
曾做过一版「外框例外」:以 UV 边距为零排除外框,并把被 mask 判为无效的外框点并回切口。实际结果是外框上仍会剩下一两个自身采样有效、又紧贴 mask 边界的点按点采样,边界上出现个别不合规则的深度;判据还多出「外框 ∧ 自身采样是否有效」这层状态。相比之下,把全部自由边界当作切口既符合规则本身,也少一层例外,因此取消。
|
||||
|
||||
外框在边界带平滑(`smooth_boundary_camera`、`smooth_cut_boundary` 的 `protected`)中的保护保持不变,与深度规则互不影响。
|
||||
|
||||
### 深度替换沿用射线方向不变的比率法
|
||||
|
||||
切口点保持自身的射线方向,只取相邻保留面的深度。替代方案是整点改用面采样(方向与深度都换),那会把切口点拉离它所在的行列,破坏 Plane 的规则网格与 Panorama 的等距经纬投影;比率法只改深度分量,是既有 Depth Split 与 Cutout 轮廓已验证的形式。
|
||||
|
||||
### 平滑顺序保持不变
|
||||
|
||||
```
|
||||
删除面(切口成为自由边界)
|
||||
│
|
||||
▼
|
||||
build_surface_camera(切口点取相邻面深)
|
||||
│
|
||||
▼
|
||||
切口带内深度模糊,沿射线重建(Plane)/径向重建(Panorama)
|
||||
│
|
||||
▼
|
||||
切口带内位置平滑
|
||||
```
|
||||
|
||||
面深决定切口点贴在哪张面上,带内模糊决定切口附近怎么过渡,两者职责不同,先锚定再过渡,与 Depth Cutout 一致。可观测后果是切口点的最终深度等于「面深在切口带内与邻点模糊一次」的结果,而不是相邻面深的精确值。要让切口严格等于面深,只能把替换挪到模糊之后或把切口点也排除出模糊,两者都会在切口与第二圈之间留出台阶,不采用。
|
||||
|
||||
### Depth Mask 按面删除
|
||||
|
||||
删除改用 FACE 域,并去掉随后的残留边清理。理由是节点链按消费域求值:Plane 的判定链没有固定到点域,切到 FACE 后 UV 属性取该面四角平均,等于在面中心采样深度,因此面中心仍有效的面保留下来,切口不再比阈值轮廓多退一圈;点删除遗留的悬空边也随面删除一并消失,不需要第二次 Delete Geometry。
|
||||
|
||||
Panorama 的判定链同样改在面心求值:无效采样用面中心经纬(`combine(face_u, face_v)`)取 Closest 纹理,再固定到 FACE 域供删面使用。面心经纬由面中心位置反算,不经过点域经度的平均,因此不会在 seam 处折返。判据统一后 Panorama 的切口与 Plane 一样落在面中心轮廓上,不再比阈值轮廓多退一圈,无效判定也只剩这一处,不再有过渡带删面或按深度通道判无效的第二套规则。
|
||||
|
||||
面心判定让切口点可能落在无效侧:自身采样无效、但所属面中心有效。这些点由切口规则取相邻面深,不会被当成无效区。Depth Mask 关闭时被保留的无效面则整片落到 `Dome Radius`,它同时是 Depth Scale 为 0 时的球半径:`radius = Dome Radius + Depth Scale × (d − Dome Radius)`。两个作用共用同一个值,无效面拿到的就是同一次 `alpha < Mask Threshold` 的结果。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 推翻「Depth Mask 只删点、不改位置」的既有契约 → 重写相关断言为「切口取相邻面深」,并保留 Depth Mask 关闭时结果逐位不变的断言。
|
||||
- 没有相邻保留面的点参与面深替换时 FACE→POINT 求值为 0,比率会塌到原点 → `edge_boundary_field` 只标记开口边界与残留边端点,不标记孤立点,并加断言兜住。
|
||||
- 外框改按面深后,Depth Mask 关闭时外框的深度也随之变化 → 由切口规则测试覆盖;外框在边界带平滑中的保护保持不变。
|
||||
- 源码与 `.blend` 资产不同步 → 修改后运行 `uv run node-group build`,源码、测试与资产一并更新。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
直接替换构建函数、两个组的接入与相关测试,不保留旧参数或开关;重新构建节点资产。回滚时恢复 `build_surface_camera`、两个组与测试并重建资产。用户工程中已保存的节点实例沿用保存时的接口,不做迁移。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。
|
||||
@@ -0,0 +1,32 @@
|
||||
## Why
|
||||
|
||||
`O Image Depth Plane` 与 `O Image Depth Panorama` 上由 Depth Mask 删点形成的切口,深度仍取该点自己的纹理采样;同一场景里 `O Image Depth Cutout` 的轮廓点和 Depth Split 切边早已改为取相邻面的采样。同类切口因此落在不同深度,clamp 或跨深度断裂的像素会把切口顶出尖刺。
|
||||
|
||||
## What Changes
|
||||
|
||||
- `build_surface_camera` 内建切口规则:自由边界点与 Depth Split 切边一样,深度取相邻面的采样,射线方向不变;删除仅有单一调用方使用的 `always_vertex` 参数。
|
||||
- `O Image Depth Plane` 的原画幅外框与 mask 切口同规则:所有自由边界点都取相邻面采样,切口规则不带例外,外框也不再保留自身采样。
|
||||
- `O Image Depth Panorama` 不传排除项:球面闭合,删除后出现的自由边界只有 mask 切口。
|
||||
- Depth Mask 的删除由点改为面:面中心的采样低于阈值才删除该面,切口因此不再比阈值轮廓多退一圈,残留边也不需要单独清理。
|
||||
- `O Image Depth Panorama` 去掉 `Invalid Distance`,无效判定只剩 mask 那一处;Depth Mask 关闭时保留的无效面落到 `Dome Radius`,它同时是 Depth Scale 为 0 时的球半径。
|
||||
- `build_surface_camera` 不再接受排除字段:切口规则覆盖全部自由边界点,取消上一版「外框例外」。
|
||||
- 平滑顺序保持不变:切口先取面深,切口带的深度模糊与位置平滑照常在其后作用。
|
||||
- 重建 `O_AnyImage.blend`,补齐切口面深与实位不变的断言。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `mask-cut-depth-sampling`: Depth Plane 与 Panorama 上由 Depth Mask 删除产生的切口如何确定深度,以及原画幅外框的例外。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。`openspec/specs` 尚无可修改的已归档规格。
|
||||
|
||||
## Impact
|
||||
|
||||
- `tools/nodes/common/depth_surface.py`:切口边界内建在 `build_surface_camera`,`always_vertex` 参数移除。
|
||||
- `tools/nodes/groups/image_depth_plane.py`、`tools/nodes/groups/image_depth_panorama.py`:接入切口规则,Plane 额外声明原画幅外框。
|
||||
- `tests/tools/nodes/`:切口面深断言,以及既有「Depth Mask 只删点、不改位置」断言的重写。
|
||||
- `src/anyimage/assets/O_AnyImage.blend`:重新构建节点资产。
|
||||
- 不改动 `O Image Depth Cutout`、`O Image Relief Plane`、Cutout 创建入口、材质与依赖。
|
||||
@@ -0,0 +1,99 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 自由边界与切边取相邻面的深度采样
|
||||
|
||||
`O Image Depth Plane` 与 `O Image Depth Panorama` 上由 Depth Mask 删除产生的切口点,以及 `O Image Depth Plane` 原画幅外框上的点,其深度 MUST 取该点相邻保留面的采样值,而不是该点自己的纹理采样。切口点 MUST 保持自身的射线方向,只改变沿射线的深度分量。
|
||||
|
||||
#### Scenario: 无效侧深度尖刺不影响 Plane 切口
|
||||
|
||||
- **WHEN** Depth Mask 删除一片区域,且紧邻该区域的无效侧像素深度与有效侧相差一个数量级
|
||||
- **THEN** 切口点的深度等于相邻有效面中心的采样深度,切口不出现跟随无效侧像素的尖刺
|
||||
|
||||
#### Scenario: Panorama 切口跟随有效侧半径
|
||||
|
||||
- **WHEN** Panorama 的 Depth Mask 删除一片区域,且被删区域的距离值与有效侧相差一个数量级
|
||||
- **THEN** 切口圈各点的径向距离等于相邻有效面中心的采样距离
|
||||
|
||||
#### Scenario: 射线方向保持
|
||||
|
||||
- **WHEN** 切口点取相邻面深度
|
||||
- **THEN** Plane 切口点的相机横纵坐标不变,Panorama 切口点仍落在原经纬方向上
|
||||
|
||||
#### Scenario: 关闭 Depth Split 时仍然生效
|
||||
|
||||
- **WHEN** Depth Split 为 0
|
||||
- **THEN** 切口点仍取相邻面深度,Depth Split 切边不参与深度替换
|
||||
|
||||
#### Scenario: 外框与切口同规则
|
||||
|
||||
- **WHEN** `O Image Depth Plane` 用同一深度图求值,Depth Mask 打开或关闭
|
||||
- **THEN** 原画幅外框上的点与外框邻接面的采样深度一致,不保留自身采样
|
||||
|
||||
### Requirement: 切口规则内建在共享相机采样
|
||||
|
||||
切口规则 MUST 由 `build_surface_camera` 自身求值自由边界并应用,调用方 MUST NOT 传入「哪些点使用面深」的集合,也 MUST NOT 传入排除字段;Depth Split 切边 MUST 与切口点走同一条深度替换路径。
|
||||
|
||||
#### Scenario: 三个调用点行为一致
|
||||
|
||||
- **WHEN** 比较 `O Image Depth Cutout`、`O Image Depth Plane` 与 `O Image Depth Panorama` 的相机采样接线
|
||||
- **THEN** 三者都不传入点集合或排除字段,切口点与 Depth Split 切边由同一规则覆盖
|
||||
|
||||
### Requirement: Depth Mask 按面删除
|
||||
|
||||
Depth Mask 的删除 MUST 按面判定:面中心的采样低于 Mask Threshold 时删除该面,其余面连同其顶点一起保留。删除后 MUST NOT 再单独清理残留边。
|
||||
|
||||
#### Scenario: 切口不再多退一圈
|
||||
|
||||
- **WHEN** Depth Mask 打开,且无效区与有效区之间存在只跨过部分顶点的面
|
||||
- **THEN** 该面在面中心仍有效时保留,切口落在面中心轮廓上,而不是所有相邻顶点都有效的内圈
|
||||
|
||||
#### Scenario: 删除后没有悬空边
|
||||
|
||||
- **WHEN** Depth Mask 删除整片面
|
||||
- **THEN** 输出的边都至少属于一个保留面,没有只由删除残留形成的边
|
||||
|
||||
### Requirement: Panorama 的无效面落到 Dome Radius
|
||||
|
||||
`O Image Depth Panorama` 的 `Dome Radius` MUST 同时承担两个作用:Depth Scale 为 0 时的球半径,以及 Depth Mask 关闭时被保留的无效面所落到的距离。无效判定 MUST 复用同一次 Mask Threshold 比较,MUST NOT 另行采样或按深度通道判断。输出半径 MUST 为 `Dome Radius + Depth Scale × (d − Dome Radius)`,`d` 为有效点的距离采样。
|
||||
|
||||
#### Scenario: 关闭 Depth Mask 保留无效区
|
||||
|
||||
- **WHEN** Depth Mask 关闭,且存在 alpha 低于 Mask Threshold 的面
|
||||
- **THEN** 这些面整片落在 Dome Radius,其余面保持自身距离
|
||||
|
||||
#### Scenario: Depth Scale 为 0
|
||||
|
||||
- **WHEN** Depth Scale 为 0
|
||||
- **THEN** 输出是半径为 Dome Radius 的球面
|
||||
|
||||
### Requirement: 原画幅外框的平滑保护独立于切口规则
|
||||
|
||||
`O Image Depth Plane` 的原画幅外框(UV 边距为 0 的点)MUST 继续作为 `smooth_boundary_camera` 与 `smooth_cut_boundary` 的保护范围,边界带平滑 MUST NOT 因此改变切口规则的覆盖范围。
|
||||
|
||||
#### Scenario: 外框保护不参与深度规则
|
||||
|
||||
- **WHEN** Boundary Smooth 大于 0 且存在切口
|
||||
- **THEN** 原画幅外框保持不被边界带平滑拖入,同时其深度仍取相邻面采样
|
||||
|
||||
#### Scenario: 关闭 Depth Mask 时几何逐位不变
|
||||
|
||||
- **WHEN** Depth Mask 关闭
|
||||
- **THEN** `O Image Depth Plane` 与 `O Image Depth Panorama` 的输出与改动前逐位一致
|
||||
|
||||
### Requirement: 切口深度先锚定后过渡
|
||||
|
||||
切口点的面深替换 MUST 发生在切口带深度模糊与位置平滑之前。切口点的最终几何 MUST 由「面深在切口带内模糊一次,再沿射线重建」得到,带外点的位置与深度 MUST 与改动前一致。
|
||||
|
||||
#### Scenario: 切口带保留平滑过渡
|
||||
|
||||
- **WHEN** Boundary Smooth 大于 0 且存在 mask 切口
|
||||
- **THEN** 切口带各点沿各自射线过渡到相邻面深,切口带以外的点与 Boundary Smooth 为 0 时逐位一致
|
||||
|
||||
### Requirement: 无相邻面的边界点不参与面深替换
|
||||
|
||||
切口规则 MUST 在删除无面边(wire edge)之后求值自由边界。没有相邻保留面的点 MUST NOT 进入面深替换,避免深度塌陷到原点。
|
||||
|
||||
#### Scenario: 孤立点保持自身深度
|
||||
|
||||
- **WHEN** 几何中存在没有相邻面的自由点
|
||||
- **THEN** 该点保持自身采样深度,输出坐标有限
|
||||
@@ -0,0 +1,29 @@
|
||||
## 1. 共享相机采样
|
||||
|
||||
- [x] 1.1 在 `build_surface_camera` 内部求值 `edge_boundary_field` 得到切口边界,删除 `always_vertex` 参数,让切口点与 Depth Split 切边走同一条深度替换路径
|
||||
- [x] 1.2 为 `build_surface_camera` 增加可选排除字段并施加在切口边界上:被排除的点保持自身采样
|
||||
- [x] 1.3 更新 `build_surface_camera` 的 docstring,说明切口取相邻面深、射线方向不变、排除字段的语义
|
||||
|
||||
## 2. 三个组接入
|
||||
|
||||
- [x] 2.1 `O Image Depth Plane`:去掉 `always_vertex` 用法,外框与切口同规则,不再传排除字段
|
||||
- [x] 2.2 `O Image Depth Panorama`:去掉 `always_vertex` 用法,不传排除字段
|
||||
- [x] 2.3 `O Image Depth Cutout`:去掉局部 `outline` 变量与 `always_vertex` 传参,确认轮廓与切边仍由内建规则覆盖
|
||||
- [x] 2.4 确认 `O Image Relief Plane`、Cutout 创建入口与材质接线未受影响
|
||||
- [x] 2.5 `delete_invalid_points` 改为 `delete_invalid_faces`:Delete Geometry 用 FACE 域,去掉残留边的第二次删除
|
||||
- [x] 2.6 `O Image Depth Plane`:取消外框例外,`build_surface_camera` 去掉排除字段,全部自由边界点取相邻面采样
|
||||
|
||||
## 3. 测试
|
||||
|
||||
- [x] 3.1 Plane:在无效侧写入与有效侧相差一个数量级的深度,断言 mask 切口点跟随相邻面深、不出现尖刺(`test_plane_validity_hole_blurs_depth_and_protects_rectangle`)
|
||||
- [x] 3.2 Panorama:在无效侧写入差异距离,断言切口圈径向距离跟随相邻有效面(`test_panorama_blurs_opening_radially_and_keeps_invalid_distance`)
|
||||
- [x] 3.3 断言切口点保持自身射线方向(Plane 相机横纵坐标不变、Panorama 经纬方向不变)
|
||||
- [x] 3.4 把 `test_valid_only_deletes_invalid_points_and_wire_edges` 的保留点断言改为「面中心采样低于 Mask Threshold 才删面」(改名 `test_valid_only_keeps_faces_covering_valid_samples`),保留 Depth Mask 关闭时逐位不变的对比
|
||||
- [x] 3.5 断言 Plane 原画幅外框取相邻面深(`test_outline_points_use_adjacent_face_depth_without_split`),以及不存在相邻保留面的自由点输出坐标有限(`test_validity.py`)
|
||||
- [x] 3.6 运行 `uv run pytest`(至少覆盖 `tests/tools/nodes/test_depth_relief_planes.py`、`test_boundary_depth.py`、`test_depth_surface_split.py`、`test_image_depth_panorama.py`)
|
||||
|
||||
## 4. 资产与收尾
|
||||
|
||||
- [x] 4.1 运行 `uv run node-group build`,确认节点资产构建与校验通过、Capture Attribute 为零
|
||||
- [x] 4.2 检查源码与测试中不再有 `always_vertex` 及旧路径引用,确认最终差异只涉及本次范围
|
||||
- [x] 4.3 `O Image Depth Panorama` 的无效判定改在面心求值并去掉 `Invalid Distance`,Depth Mask 关闭时保留的无效面整片落到 `Dome Radius`(同时是 Depth Scale 0 的球半径),并补面心轮廓、切口点半径与 dome 回退断言
|
||||
Reference in New Issue
Block a user