Files
AnyImage/docs/technical.md
nan c3a151ad2c Refactor image mesh processing and background removal functionality
- Updated NODE_GROUP_NAME from "O Refine DA3 Surface" to "O DA3 Surface".
- Introduced `remove_surface_background` function to filter out background faces and compact geometry.
- Enhanced `build_image_mesh` to conditionally remove background faces based on a new property.
- Added `snapform_remove_background` and `snapform_keep_original` properties to operators for better control over background handling.
- Updated UI to reflect new properties and improved resolution scaling logic.
- Refactored tests to cover new background removal functionality and ensure proper geometry handling.
- Adjusted geometry nodes and validation scripts to remove references to background handling.
2026-08-12 16:15:14 +08:00

527 lines
38 KiB
Markdown
Raw 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.
# SnapForm 技术文档
本文档面向开发者和需要了解内部实现细节的用户,从原 README 迁移而来。普通用户请阅读项目根目录的
`README.md`。
`SnapForm` 是一套从图片快速构建空间结构的 Blender Extension。目前以
[Depth Anything 3](https://github.com/ByteDance-Seed/Depth-Anything-3)
作为首个 AI 能力,在独立 Runtime 中运行 DA3,可将官方导出结果保存在独立 job 目录,
也可把 Image Empty 转换成带几何与材质置换的立体面片。Debug 面板还可运行
[BEN2](https://github.com/PramaLLC/BEN2) 背景剔除功能测试。
## 当前功能
- 使用 Blender Extension manifest,可通过 `Install from Disk` 安装
- 在独立 Python Runtime 的常驻 HTTP AI Host 中运行 Torch、DA3 与 BEN2,避免污染 Blender Python 进程
- Blender 使用内置 `urllib.request` 访问仅监听 `127.0.0.1` 的 Host;请求带随机 Token
- 常驻 Host 复用 Torch、CUDA、BEN2 与最近使用的 DA3 模型,连续执行时不再重复加载
- 多个 Blender 实例自动连接同一个共享 Host;`Host` 面板显示状态、端口、客户端与模型缓存,并提供安装、启动、清理、重启和停止控制
- Runtime、模型和任务结果默认保存在 `~/.snapform`,可由多个 Blender 版本共享
- 支持 DA3 Small、Base、Large 1.1、Giant 1.1 和 Nested Giant Large 1.1
- 支持 BEN2 Base 单图背景剔除测试,输出透明 PNG 与推理耗时
- 支持单张 JPG、JPEG、PNG、WEBP 图片或常见视频文件输入
- 视频按顺序截取前若干帧,可设置参与重建的最大帧数
- `Host > Models` 默认折叠,展开后可逐项查看和下载所有可用模型
- Runtime 已就绪时不额外占用面板空间;模型列表每个模型只占一行
- `Debug` 默认折叠,内部的 `DA3 Output Test` 与 `BEN2 Removal Test` 也分别默认折叠
- 使用一个输出类型下拉菜单和一个 `Generate Output` 按钮
- 只显示当前格式的专属参数;GLB 与 PLY 设置不会同时出现
- Debug 的模型选项按格式过滤;PLY 只提供两个带 Gaussian head 的 Giant 模型
- Debug 与 Convert to Depth Plane 的模型选项直接显示下载状态;未下载模型带
`(Missing)`,已下载模型保持原名
- 分别调用 DA3 的 `glb`、`gs_ply`、`depth_vis` 和 `mini_npz` exporter
- `Textured Surface (GLB)` 直接使用 DA3 Prediction 中的深度、置信度和相机参数,把相邻像素
组成三角面并写入 `mesh.glb`;每个输入帧生成一张独立纹理网格,不做多视图融合
- Image Empty 右键菜单可直接运行 DA3,根据 `mini_npz` 深度生成面片
- Image Empty 右键菜单可直接运行 BEN2,保留原图并换用新建的透明背景图片
- Image Empty 支持视频与序列帧输入:`Convert to Depth Plane` 逐帧估计深度,
`Remove Background` 逐帧抠图,入口保持不变
- 视频生成的序列帧以外部文件引用保留在 job 目录(Blender 无法整体 Pack 序列帧);
Geometry Nodes 与材质 Image Texture 均可按场景帧播放序列帧
- 使用浮点深度贴图、`O DA3 Depth Plane` Geometry Nodes 和原图 Bump 构建立体面片
- 转换对话框选择推理模式、处理分辨率与背景移除;细分和置换方式在 Geometry Nodes 修改器中调整
- Debug 的 DA3 输出或 BEN2 背景剔除完成后只打开对应 job 文件夹,不修改当前场景
- 完整保留每次任务生成的所有官方文件
- 安装和模型下载使用后台子进程;推理提交给常驻 AI Host,状态栏实时显示阶段、百分比进度条与取消按钮
- 后台任务运行时,Install、Download 和 Export 按钮保持原位置并变灰,任务完成后恢复
## 使用
1. 先运行 `uv run python prepare_vendor.py`,按 `vendor.lock.toml` 下载并校验固定版本的
第三方构建输入;再运行 `uv run python pack.py`,分别构建 `windows-x64`、`macos-arm64`、
`linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.10.11.macos-arm64.zip`)。
每个包只含对应平台的 uv 二进制,并内置 DA3、BEN2 源码包。
2. 在 Blender 的 `Preferences > Extensions` 中选择 `Install from Disk`。
3. 在 Extension Preferences 中确认 Storage Root 与 Device。Device 是正式功能
(Image Empty 右键菜单)使用的推理设备,默认取本机最快的 CUDA/MPS,否则 CPU,且只列出
本机支持的设备。
4. 在 `Host` 点击 `Install Runtime`。首次安装会下载 Python、PyTorch、FastAPI、Uvicorn;
DA3、BEN2 源码包与 uv 已内置在扩展中,安装时从 `~/.snapform/vendor/` 解包使用,
并在 Runtime 就绪后自动下载 `DA3 Small` 和 `BEN2 Base` 推荐模型。Runtime 安装
固定显示 `Environment`、`PyTorch`、`AI components` 三个用户阶段;xFormers(macOS 跳过)、
FastAPI 等内部安装步骤合并在 `AI components` 中。耗时最长的 PyTorch 与 AI 组件占主要进度
区间。安装完成后直接写入 Runtime 就绪信息,不再额外导入全部组件进行验证。推荐模型属于安装
后的自动步骤,不计入阶段编号;`DA3 Base` 等其他模型可稍后在 `Models` 面板按需下载。
状态文字使用 `阶段/总数 阶段名 | 当前操作 | 预计剩余时间`,依赖安装会结合预设耗时与实际
进度修正预期;模型下载根据字节数和速度计算剩余时间,例如
`Downloading DA3 Small | 14.2 MB/s | About 2 min left`。重试或切换镜像时进度不会倒退。
Runtime 就绪后即使模型下载中断也不影响 Host 使用,可稍后在 `Models` 面板单独补下。
安装不做启动网络预检:pip 安装失败会自动依次重试清华、阿里镜像,模型下载由 worker
自行探测回退到 HF 镜像;需要手动指定镜像时可通过 `PIP_INDEX_URL` 环境变量覆盖。
5. 在 3D View 按 `N`,打开 `SnapForm` 页签。
6. 如需其他模型,展开 `Host > Models`,找到模型并点击该行的 `Download`。
7. 展开默认收起的 `Debug`,选择 `Output Type` 和本次执行使用的模型。
8. 选择一张图片或一个视频文件。视频可设置从头截取的帧数,然后点击 `Generate Output`。
9. 测试背景剔除时,确认 `Host > Models` 中的 `BEN2 Base` 已就绪,然后在 `Debug` 的
`BEN2 Removal Test` 区域选择单张图片并点击 `Remove Background`。
完成后会打开 job 文件夹,其中的 `foreground.png` 带透明 Alpha;状态栏显示
BEN2 推理、模型加载与任务总耗时。当前测试固定关闭较慢的 foreground refinement。
## Image Empty 去背景
1. 确认 `Host > Models` 中的 `BEN2 Base` 已就绪;首次安装 Runtime 时会默认下载。
2. 在 3D View 选中 Image Empty,右键打开 `SnapForm` 子菜单并选择 `Remove Background`。
3. 完成后以 Fake User 保留原 Image datablock,新建并 Pack 一张透明 PNG 到当前 `.blend`,然后只把
当前 Empty 的 `data` 换成新图;Empty 的 Transform、显示尺寸、Offset 及其他设置保持不变。
4. 新 Image 使用原图去掉格式后再追加 `.foreground` 的名称。例如 `img.png` 生成
`img.foreground`,`img.png.001` 生成 `img.001.foreground`。已有同名 datablock 时由
Blender 自动追加编号。
原图已 Pack 时会先写出临时 PNG 交给 BEN2,任务结束后删除临时输入;原 Image 仍保持 Pack
状态和原始内容。BEN2 的 `foreground.png` 也会保留在对应 job 目录中。
Image Empty 引用视频或序列帧时,Remove Background 直接执行,默认取 Empty 的 Image Frames
数值(未设置时为完整帧数,显式设为 1 时只处理 1 帧),起始帧跟随 Empty 的 Offset;若 Offset 加 Frames 超出视频长度,
会把窗口整体收进视频内(保证实际处理 Frames 帧数),Offset 过小时从第 1 帧开始。输出
`foreground.0001.png` 等透明序列帧(帧号从 0001 开始,与 Blender 场景帧一一对应),并把
源帧以 `frame.0001.jpg` 等命名复制到 job 目录根下供材质颜色导入。替换 Empty 的 Image 为
加载这些帧的 SEQUENCE Image,从原 Empty 的 Start 位置开始播放(Start 原样保留),
Cyclic 沿用原 Empty 的设置。序列帧无法被 Blender 整体 Pack,因此保持外部引用 job 目录;
请保留对应 job 目录,否则序列会失效。
## Image Empty 转深度面片
1. 在 3D View 选中 `Object Type = Empty`、`Display As = Image` 的对象。
2. 右键打开 `SnapForm` 子菜单并选择 `Convert to Depth Plane`,通过 `Depth from` 选择深度来源:
- `AI Generated`:显示 Model、Use Ray Pose 和 Resolution Scale。Process Res 先以原图长边乘以
Resolution Scale,再钳制到 `252–1008`,最后对齐到最接近的 `14` 倍数。Resolution Scale 以百分比
显示,范围为 `1–100%`,默认 `100%`;界面只在标签中实时显示最终 Process Res。
转换导出 `mini_npz`,使用前需要在
`Host > Models` 下载所选模型,并固定使用 `process_res_method="upper_bound_resize"`;设备和参考视图策略
继续使用场景设置。Model 下拉菜单只列出已经下载的 DA3 模型;若没有可用模型,对话框会引导到
`Host > Models` 下载。
- `Original`:直接把原图作为深度贴图,不显示估算参数。仅在开启 `Remove Background` 时运行 BEN2
取得前景 Alpha;视频或序列帧按需同时抽取原始帧与前景序列,把原始帧作为 `.depth` 序列。
`Original` 是默认深度来源。选择 `AI Generated` 时,其 Model、Use Ray Pose 与 Resolution Scale
参数会集中显示在独立边框中。`Remove Background` 与 `Unshaded` 直接显示,不再使用 Options 折叠;
`Keep Original` 默认关闭,并在分割线下方保持为最后一项。仅当开启 `Remove Background` 时才需要
`BEN2 Base`,并把 `.foreground` 的 Alpha 作为独立
Mask,不在 Host 中改写深度值。保存 `.foreground` 时保留 BEN2 Alpha,但 RGB 使用未经修改的原始输入;
因此材质直接用 `.foreground` 同时提供颜色和 Mask,不再额外加载原图 Image Texture。
Image Empty 引用视频或序列帧时,AI Generated 会按 Empty 的 Image Frames 数值逐帧估计
深度(未设置时为完整帧数,显式设为 1 时只处理 1 帧),起始帧跟随 Empty 的 Offset,与背景蒙版范围一致;
Offset 超出视频长度时同样把窗口收进视频内。材质节点与置换贴图的 Start 保持与原 Empty
一致;Original 模式的视频或序列帧使用 `frame.0001.jpg` 等原图序列作为颜色与深度,
`foreground.0001.png` 只提供前景 Alpha。
AI Generated 的深度仍以 EXR 序列帧(`depth.0001.exr` 起)保存在 job 目录根下。
`Unshaded`、`Remove Background` 与 `Keep Original` 均默认关闭,因此默认删除原对象。
3. AI Generated 完成后会自动创建面片。面片几何先由 `mini_npz` 的相机内参与深度确定,再通过对象
Transform 匹配原 Image Empty 的可见尺寸、位置、旋转和 Offset。Original 模式直接使用源 Empty
的可见尺寸、Offset 与 Transform。AI Generated 会把用于匹配画面尺寸的缩放预乘到 `.depth` 数值,
XY 缩放烘焙到 Mesh,Z 不参与平面匹配;最终对象 Scale 为 `(1, 1, 1)`,视觉深度与旧逻辑一致。
关闭 `Keep Original` 时,成功生成后删除源 Empty。生成面片 Object 使用源 Empty 的原名;开启
`Keep Original` 时不处理
同名冲突,由 Blender 自动为生成面片追加编号。
4. Blender 只创建保留图像纵横比的最低密度基础网格,再把 `O DA3 Depth Plane` 作为 Geometry Nodes
修改器添加到对象。节点组与 `O DA3 Surface` 一同保存在 `O_SnapForm.blend`,关闭
`show_group_selector`,不为每个对象复制节点组。修改器公开以下参数:
- `Displacement`:位于 Geometry 之后的第一项,菜单包含 `Geometry`(默认)与 `Material`。`Geometry`
在节点组内沿法线修改网格,`Material` 保持网格平面并启用材质置换;
- `Subdivision`:位于第二项,默认 `5`。节点读取基础平面的 Bounding Box,短边分段数为 `2^Subdivision`,
长边按宽高比计算并四舍五入;例如 16:9 图片在等级 `5` 时生成 `57×32` 个面;
- `Background Depth`:默认 `0`,允许正负值,Structure Type 为 `Single`,直接显示且不再放在 Panel 中。前景 Mask 外的结构或
材质置换使用该值;是否存在背景只取决于转换所得色彩贴图的 `Alpha > 0.5` 二值结果;
- `Depth Scale`:默认 `1`,允许正负值,Structure Type 为 `Single`,同时控制 Geometry 与 Material
两条置换路径。
Grid 重建后通过隐藏的 Material 输入重新赋予原图材质,并对所有面执行 Shade Smooth。
材质使用显式指向 `UVMap` 的 UV Map 节点连接颜色、前景 Mask 与深度 Image Texture,不依赖活动 UV。
前景深度先与 `Background Depth` 取 Maximum,再按色彩贴图 Alpha 与背景深度混合,
深度分支在混合前使用 `Alpha > 0.5` 转为二值 Mask,避免半透明前景被背景深度拉扯;
材质表面 Alpha 同样使用二值 Mask。材质置换使用相同的二值和钳制顺序。
节点组还会把 `o_material_displacement` 与 `o_background_depth` 写入点域,材质通过
`Geometry` 类型的 Attribute 节点读取。点域会为顶点保存重复值,但能在应用修改器后继续保留参数;
相比自适应网格本身,其占用很小。Depth Image 与 Foreground Image 是修改器隐藏数据输入,
由导入流程自动设置。
视频与序列帧还会为 Depth 与 Foreground 共同写入隐藏的 `Frame`、`Frame Start`、`Frames` 与 `Cyclic`。
节点组内部不再使用 Scene Time。只有原 Image Empty 是视频或序列时,导入流程才直接给修改器的 `Frame`
socket 添加 Scripted Expression Driver `frame`;静态图保持默认值 `1`,不创建 Driver。导入内容已经
是截取后的帧,因此 Offset 在节点组内恒定为 `0`。
节点组根据 `Frame` 计算从 `1` 开始的 Image Texture Frame:场景帧与 Start 都为 `1` 时读取图片第 `1` 帧;非循环输入
钳制到有效范围;循环输入使用 Floored Modulo,让 `Frame Start` 前后的负、正相对帧都映射到
`1…Frames`,因此开始帧之前也会循环播放。Geometry 置换会像材质 Image Texture 一样随场景帧刷新。
## Image Empty 转透视网格
1. 在 3D View 选中仅包含一帧的 Image Empty,右键打开 `SnapForm` 子菜单并选择
`Convert to Surface`。该入口独立于
`Convert to Depth Plane`,当前不处理视频或序列帧。
对话框中的 Resolution Scale 与 Depth Plane 使用相同设计:原图长边乘以 `1–100%` 的比例后,
将实际 Process Res 钳制在 `252–1008` 并按 `14` 对齐。
2. Host 请求 DA3 的 `depth`、`conf` 与 `intrinsics`。仅在开启 `Remove Background` 时运行 BEN2 取得
前景 Alpha,并在输出前删除包含背景顶点的三角面;关闭时不加载 BEN2。Depth 不做 smoothing,Host
反投影深度网格,建立源三角面,计算 confidence percentile 与 camera-space edge stretch,并写入
基于原图像素坐标的源 `UVMap`。这些步骤只准备修改器的输入网格,不执行 Relax、拓扑修复、平滑、
小岛清理或最终 UV 重投影。运行时显示的六个 `Preparing surface data` 步骤均指这段 Host 端源数据准备。
3. Blender 把勾选 `Usage > Modifier` 的 `O DA3 Surface` 节点组直接作为 Geometry Nodes 修改器
添加到 Surface,不另设修改器名称。节点组内没有子节点组,依次完成:
- 修改器顶层依次显示 `Resolution`、`Relax Iterations`、`Smooth`;默认展开的 `Culling` Panel
只包含 `Confidence` 与 `Stretch`。两个百分比输入都使用 `0–100` 的节点值,并在组内
转换为 `0–1` 阈值;`Stretch = 100%` 明确选择全部面,剔面后同时删除
Face Count 为零的孤立顶点;
- 每轮 Remesh Iteration 依次执行 Connected Merge、循环拓扑修复与一次 Smooth,使新拓扑在进入下一轮
Merge 前先稳定;拓扑修复删除非流形面、单面拓扑和小面积 mesh islands;
- 默认 `Resolution` 为原始有效三角边长中位数乘以 `sqrt(10) * 2`。`sqrt(10)` 来自原先 90% 面数缩减
的二维边距估计,再乘以 2 为迭代合并留出更平滑、稳定的默认尺度;节点内部 Connected Merge 的
Distance 为 `Resolution * 0.5`;
- Smooth 采用 `O Smooth` 的边界检测和模糊法线权重,固定开放边界与薄面,只保留一个
`Smooth` 程度系数;节点组不再额外执行 Flip Faces;
- 从修改前的原始表面以 nearest-surface 重新采样 `UVMap`,最终明确写为 Corner 域 `2D Vector`。
4. 节点组随插件放在 `O_SnapForm.blend`。Blender 4.5 首次 Append 后复用同一 datablock;
Blender 5.0 以 linked ID 载入后调用 Pack,后续 Surface 继续复用。插件不会为每个 Surface 新建节点组,
也不会在添加后重命名 Geometry Nodes 修改器。节点组只启用 `Usage > Modifier`,不 Mark as Asset;
修改器关闭 `show_group_selector`。`Fix Topology` 位于默认折叠的 `Options` Panel。
节点图按 Culling、Remesh、Topology Fix、Island Cleanup 与 UV 分区摆放;各区就近使用独立的
Group Input,并隐藏没有连线的接口,避免跨越整个节点图的输入连线。
5. Host 准备过程固定显示 6 个编号阶段:验证 DA3 数据、反投影、准备 DA3 与可选 BEN2 数据、构建原始
源三角面、源 UV/attributes、写出网格。状态栏显示 `Preparing surface data n/6: ...`。
6. Host 在 job 根目录写出 `mesh.npz` 与 `texture.png`。NPZ 保存 camera-space `vertices`、
`faces`、`uv`、`intrinsics`、`image_size` 与有效深度中位数 `reference_depth`。Blender 读取这些
数据后直接创建 Surface 与原图材质,不需要 Open3D 或 OpenCV 进入 Blender Python 环境。
Surface 保留下列结构化 attributes:
- 点域:`o_depth`、`o_confidence`、`o_confidence_percentile`、`o_background`;
- 面域:`o_face_confidence`、`o_edge_stretch`、`o_background_face`。
7. 网格以 `reference_depth` 截面匹配 Image Empty 的显示矩形:该截面的投影四角与 Empty 的可见
边界重合,主点同时匹配 Empty Offset。网格对象直接沿用源 Empty 的 `matrix_world`,因此位置、旋转、
缩放及父级关系产生的世界变换保持一致。更近的顶点沿 Empty 局部 `+Z` 方向凸出,三角面正面朝向
观察相机,启用 Backface Culling 时可从正面看到。
8. 转换对话框不再提供后处理参数,只保留模型与 Resolution Scale 设置、`Create Matching Camera`、
`Remove Background`、`Unshaded`,以及分割线下方的 `Keep Original`。背景移除默认关闭;开启后 Host
使用 BEN2,并在写出 Surface 前直接删除背景三角面,不再由 Geometry Nodes 的 Background 开关处理。
`Unshaded` 使用与 Depth Plane 相同的 Emission/Transparent 材质路径;普通材质的
Bump Scale 按 `abs(object.scale.z) × camera scale_z` 设置,并在材质组内乘以固定基准 `0.001`,
与透视表面实际深度尺度一致;它允许负数以反转凹凸方向。
相机默认关闭;开启后会在 Empty 局部 `+Z` 侧创建一台朝向局部 `-Z` 的透视相机,并成为生成 Surface
的子级,使二者移动时保持投影关系。关闭 `Keep Original` 时,网格成功创建后删除源 Empty。
表面、材质与相机会归入同一个 `Convert to Surface` Undo 步骤。
Process Res 的设计依据 DA3 官方实现:API 与 CLI 默认使用 `process_res=504` 和
`process_res_method="upper_bound_resize"`;输入预处理的 `PATCH_SIZE=14`,会把宽高分别调整到最接近的
`14` 倍数。DA3 论文以 `504×504` 为 base resolution,公开的训练尺寸长边为 `504`、`672`、`756` 或
`896`。官方 CLI 虽给出 `1024` 示例,但没有声明更高分辨率会提升质量,也没有规定 `1000` 为硬上限。
因此转换入口使用相对原图的 Resolution Scale,同时通过上下限避免 Process Res 过低或过高,并把训练范围
之外的值明确视为实验设置。参考
[DA3 API](https://github.com/ByteDance-Seed/Depth-Anything-3/blob/main/docs/API.md)、
[输入预处理实现](https://github.com/ByteDance-Seed/Depth-Anything-3/blob/main/src/depth_anything_3/utils/io/input_processor.py)
与[技术报告](https://depth-anything-3.github.io/assets/da3_tech_report_2025.pdf)。
Debug 的 Textured Surface GLB 会把 depth、confidence 与 confidence percentile 写入自定义顶点
attributes。其他 DA3 Debug 三维输出会同时写出未压缩的 `prediction.attributes.npz`,保存完整
depth、confidence、intrinsics、extrinsics 与 confidence percentile。当前 trimesh 的 glTF 点图元
导出器只写 Position/Color,无法可靠把任意 attribute 嵌入点云 GLB,因此点云先以 companion NPZ
保留完整结构;后续若改为 SnapForm 自有 glTF writer,可再把这些字段直接嵌入点图元。
所有任务使用统一的状态栏进度显示。变化中的阶段说明显示在右侧的进度条内部,取消按钮
位于进度条右侧。状态字段支持阶段、当前操作与预计剩余秒数,界面统一组合为紧凑文案并保证
进度单调递增。任务进程与 Host 请求在后台启动,按钮点击后立即进入进度状态。Image Depth 的阶段包括 Runtime 启动、输入准备、模型加载、深度估算、缓存
写出和面片构建。进度更新会强制刷新状态栏与窗口,无需移动鼠标即可实时显示。安装中断后残留的
半成品 Runtime 会在下次安装前自动清理,不会因为中断而无法再次安装。
导入过程精确保留 NPZ 内深度值之间的差值。例如深度 `2.0` 和 `5.5` 在
`Displacement Strength = -1` 时,沿局部 Z 轴相差 `3.5 Blender Unit`。它不会把数值压缩到
`0..1`,也不再提供视觉归一化模式。
面片尺寸以有效深度的中位数 `Zref` 作为参考截面。先用 `mini_npz` 中的相机内参计算原始
投影面积,再恢复源图纵横比:
```text
RawWidth = depth_width × Zref × abs(Displacement Strength) / fx
RawHeight = depth_height × Zref × abs(Displacement Strength) / fy
Area = RawWidth × RawHeight
Aspect = source_image_width / source_image_height
Width = sqrt(Area × Aspect)
Height = sqrt(Area / Aspect)
```
这样即使 DA3 估计出的 `fx/fy` 不一致,也不会把正方形原图拉成长方形。主点 `cx/cy` 仍用于
确定原始相机面片,随后对象 XY Transform 将它匹配到源 Image Empty。生成对象会记录
`snapform_reference_depth`、`snapform_camera_plane_width` 和
`snapform_camera_plane_height`,便于检查实际采用的
参考深度和尺寸。
材质始终导入深度 Image Texture,并连接到 Displacement 节点和 Material Output;材质 Surface
Displacement 设置为 `Displacement Only`,AI Generated 与 Original 都使用 `Midlevel = 0`、`Scale = 1`。
`o_material_displacement` 为 `False` 时,材质把 Height 乘以零,置换只在几何节点中生效;为 `True` 时,
几何节点关闭结构置换,材质 Height 恢复。色彩贴图的 Alpha 二值结果同时用于选择原始深度或有符号的
`Background Depth`,使两种置换路径保持一致;未移除背景的普通不透明图片因此全部视为前景。
视频与序列帧的颜色、深度与前景 Image Texture
通过 image_user 的 Frames / Offset / Start 把第 i 帧映射到场景 `Start+i` 帧,并启用 Auto Refresh。
颜色贴图到 Principled BSDF 之间使用独立的 Shader Node Group。Surface 使用 `O DA3 Surface Layer`;
Depth Plane 使用 `O DA3 Depth Plane Layer` 材质组,并把背景剔除、背景深度与材质置换 Attribute
读取全部收进组内。两者都依次接收 `Color`、`Alpha`、Float 类型的 `Depth`、`Bump Scale`。
`O DA3 Surface Layer` 只输出 `Color`、`Roughness`、`Alpha` 与 `Normal`,不创建 Displacement 节点或输出;
`O DA3 Depth Plane Layer` 另外输出 `Displacement`。Surface 把原图 Color 同时送入 `Color` 与 `Depth`,
保持图像 Bump 效果。外层材质只保留贴图、材质组、Principled BSDF 与 Material Output,方便继续调整。
Depth Plane 的 Bump Height 直接使用材质组的 `Color` 输入,只受独立的 `Bump Scale` 影响,不使用深度贴图,
也不经过 `Displacement` 模式开关、`Depth Scale` 或 `Background Depth`;材质 Displacement 仍使用这些参数处理后的深度。
无论选择哪种置换方式,原始彩色贴图都会连接
Principled BSDF 的 `Base Color`,并通过
`Bump.Height → Bump.Normal → Principled.Normal` 提供表面细节。Bump 的基础 Distance 为
`0.001`,会乘以面片 Transform 的统一缩放倍率。原图 Color 还会经过 `Float Curve` 连接到
Principled BSDF 的 `Roughness`,曲线两端输出分别为 `0.5` 和 `0.1`;材质 IOR 保持
Principled BSDF 默认值 `1.5`。
开启 `Unshaded` 后,色彩贴图 Color 连接 Emission,二值 Alpha 作为 Mix Shader 系数,在
Transparent BSDF 与 Emission 之间混合后连接 Material Output;不创建 Principled BSDF、Bump 或
Float Curve。开启 `Remove Background` 时色彩贴图使用 BEN2 输出;关闭时使用原图。
这里的“原始距离”是 DA3 模型输出的尺度。普通 Small、Base、Large 和 Giant 模型的深度不应
被解释为现实世界的米;需要现实尺度时应选择带 metric depth 的
`DA3 Nested Giant Large 1.1`。此外,当前结果仍是固定 XY 平面上的深度位移面,不是使用
相机内参逐像素反投影得到的完整 camera-space 网格;中位深度截面为 Geometry Nodes
深度面片提供正确的整体高宽尺度。
生成完成后会直接创建面片,不提供缓存重建入口或单独的导入参数。重新选择
`Convert to Depth Plane` 会按当前 Depth from 再次转换。
面片、深度 Image、前景 Image、Material 与 Geometry Nodes 修改器会在同一个 Blender Undo 步骤中创建;转换完成后执行
`Ctrl+Z` 会完整撤销本次转换,不会越过转换去回退更早的 Image Empty 操作。
Image Empty 使用 Packed Image 时,SnapForm 会通过 Blender 写出临时 PNG 交给 DA3,
不会 Unpack 或修改源 Image;任务结束后自动删除临时文件。普通图片仍直接读取源文件。DA3 的原始
`results.npz` 保留在对应 job 文件夹根下,生成的浮点深度 Image 会 Pack 到
当前 `.blend`。Depth Image 与 Displace Texture 根据源 Image datablock 命名。例如 Empty Object
名为 `obj_img`、引用的 Image 名为 `img.png` 时,Depth Image 与 Texture 都命名为 `img.depth`;
生成的 Object、Mesh 与 Material 仍命名为 `obj_img`。Pack 使用 OpenEXR,因而不会把大于 `1.0`
的原始深度截断。已有同名 datablock 时由 Blender 自动追加 `.001` 等编号。
视频输入时 Blender 无法整体 Pack 序列帧,因此 `.depth` 与颜色序列改为引用 job 目录根下的
`depth.0001.exr`、`frame.0001.jpg`(`.frame`)或 `foreground.0001.png`,并在每帧保留原始
浮点深度;保存 `.blend` 后再次打开仍需对应 job 目录存在。
选择 PLY 后,模型菜单只会提供 `DA3 Giant 1.1` 和
`DA3 Nested Giant Large 1.1`。Small、Base 和 Large 没有 Gaussian head,无法由
DA3 生成 `gs_ply`。
每个任务在 `~/.snapform/jobs/` 下用独立文件夹包裹。文件夹统一采用
`动作-对象_日期-时间-微秒`,每种任务都有唯一名称,例如
`generate-depth-plane_20260807-153000-123456`。微秒用于避免同一秒启动的任务共用目录。
Blender 为 Packed Image 或序列帧生成的输入副本也归档到对应 job 根目录:单图命名为
`input.png`,序列帧命名为 `frame.0001.*`。它们以前暂存在 `jobs/inputs/`,现在不再创建这个
公共中转文件夹。普通磁盘文件和视频仍直接读取原文件,不额外复制。
日志、运行中的状态文件、归档输入与输出产物均直接放在 job 根目录。各类任务名称为:
- `install-runtime`
- `download-model`
- `export-reconstruction`
- `generate-depth-plane`
- `generate-image-mesh`
- `remove-background`
- `remove-image-background`
- `generate-original-depth-plane`
DA3 官方 exporter 产生的 `gs_ply/`、`depth_vis/`、`exports/mini_npz/` 会在完成后自动平铺,
不会保留只含一层产物的子目录:
```text
export-reconstruction_20260807-153000-123456/
├── export-reconstruction_20260807-153000-123456.log
├── export-reconstruction_20260807-153000-123456.json
└── scene.glb
export-reconstruction_20260807-153000-123456/
├── export-reconstruction_20260807-153000-123456.log
├── export-reconstruction_20260807-153000-123456.json
└── *.ply
export-reconstruction_20260807-153000-123456/
├── export-reconstruction_20260807-153000-123456.log
├── export-reconstruction_20260807-153000-123456.json
└── *.jpg
export-reconstruction_20260807-153000-123456/
├── export-reconstruction_20260807-153000-123456.log
├── export-reconstruction_20260807-153000-123456.json
└── results.npz
remove-background_20260807-153000-123456/
├── remove-background_20260807-153000-123456.log
├── remove-background_20260807-153000-123456.json
├── foreground.png 单图结果
├── foreground.0001.png… 视频/序列帧结果
├── frame.0001.jpg… 视频/序列帧的源帧副本
└── depth.0001.exr… AI 深度序列帧
```
建议从 `DA3 Base` 开始。它使用 Apache 2.0 模型权重;`DA3 Large 1.1` 权重采用
CC BY-NC 4.0,仅限非商业用途。
## 输出格式说明
`Output Type` 提供以下结果:
- Point Cloud (GLB):DA3 `glb`
- Gaussian Splats (PLY):DA3 `gs_ply`,同时传入 `infer_gs=True`
- Depth Preview (Images):DA3 `depth_vis`
- Depth Data (NPZ):DA3 `mini_npz`
- Textured Surface (GLB):SnapForm 根据 DA3 Prediction 构建并输出 `mesh.glb`
Point Cloud 专属设置仅在对应类型下显示,`gs_views_interval` 仅用于 Gaussian Splats。
Textured Surface 使用 0–100% 的 Confidence Threshold 删除置信度最低的一定比例:
- `process_res=504`
- `process_res_method="upper_bound_resize"`
- `use_ray_pose=False`
- `ref_view_strategy="saddle_balanced"`
- `conf_thresh_percentile=40.0`
- `num_max_points=1_000_000`
- `show_cameras=True`
- `export_kwargs["gs_ply"]["gs_views_interval"]=1`
DA3 官方 `glb` 是彩色点云与可选相机线框;`gs_ply` 是带 Gaussian 属性的 PLY;
`depth_vis` 是原图与着色深度图的并排预览;`mini_npz` 保存深度、置信度和可用的相机参数。
Point Cloud GLB 任务会关闭其默认附带的深度预览,使五种输出产物保持分离。
官方格式与限制可参阅
[DA3 Python API 文档](https://github.com/ByteDance-Seed/Depth-Anything-3/blob/main/docs/API.md)。
## Runtime
Extension 本身不包含 Torch。`Install Runtime` 使用 `uv` 创建独立 Python 3.12
环境,并安装。机器上即使没有安装 `uv` 或 Python 也没关系:安装程序由 Blender
自带的 Python 启动,缺 `uv` 时会解包扩展内置的对应平台二进制,再用它下载
Python 3.12 与全部依赖。`uv` 会缓存在扩展存储目录下,后续安装直接复用。
安装不做网络预检,直接开始;环境变量仍可切换镜像与代理:`PIP_INDEX_URL`(PyPI 镜像)、
`SNAPFORM_TORCH_INDEX`(PyTorch 索引)、`HF_ENDPOINT`(Hugging Face 镜像)、
`HTTP(S)_PROXY`/`ALL_PROXY`(代理)。未指定 PyPI 镜像时,
pip 安装失败会自动依次重试镜像(清华、阿里云);模型下载不预探测,官方 Hugging Face
失败后自动回退 ModelScope(`modelscope.cn`,仓库名与 Hugging Face 一致)。
GitHub 不再参与安装:DA3、BEN2、uv 已内置,缺少内置文件会直接报错提示重新安装,不再下载兜底。
单独下载模型时遵循同样的顺序:Hugging Face(可用 `HF_ENDPOINT` 指定其他镜像)失败后自动切换到
ModelScope。
Windows 上代理若只在系统设置里而未写入环境变量,安装程序会尝试自动从系统代理注入子进程。
- PyTorch 2.9.1
- TorchVision 0.24.1
- xFormers 0.0.33.post2(与 PyTorch 2.9.1 对应,仅 Windows/Linux;macOS 无 wheel 自动跳过)
- 固定到已验证 commit 的 Depth Anything 3
- 固定到已验证 commit 的 BEN2
- FastAPI 0.139.2 与 Uvicorn 0.51.0
- DA3 与 BEN2 源码包随扩展打包,安装时复制到 `~/.snapform/vendor/` 后由 uv 本地安装
(无需访问 GitHub);SnapForm 直接调用 DA3
底层 export 接口,`gs_ply` 导出不会触发 `gs_video` 渲染,因此不需要 gsplat
与 C++ 编译工具链
- `vendor/` 是不进入 Git 的本地缓存;`vendor.lock.toml` 固定下载 URL、commit/version 与
SHA-256,`patches/<包名>/` 进入 Git 并存放 SnapForm 对源码的覆盖文件(移除 xformers
硬依赖、export 入口改懒加载、
射线位姿的 3×3 QR 分解在 MPS 上改走 CPU),
`pack.py` 打包时自动套入官方包,扩展 zip 内才是修补后的版本;更新上游只需替换官方包
并核对覆盖文件是否仍适用
- `addict` 2.4.0(补充 DA3 上游包元数据暂未声明的运行依赖)
安装 Runtime 时会自动选择 PyTorch 后端:检测到 NVIDIA 驱动则安装 CUDA 12.8 版本,否则安装
CPU 版;macOS 始终安装 CPU 版 PyTorch(MPS 包含其中)。CPU 可以用于验证,但实际推理通常很慢。
Extension Preferences 的 `Device` 决定正式功能的推理设备,默认取本机最快的 CUDA/MPS,否则
CPU;Debug 面板的 `Debug Device` 只影响 Debug 任务。两处设备选项都只列出本机支持的设备:
Windows 不显示 MPS,机器上没有可用 NVIDIA 驱动时不显示 CUDA。Runtime 安装完成时会检测真实
可用的设备并写入 `snapform_ready.json`,设备列表以该检测结果为准。
CUDA 12.8 的 PyTorch wheel 自带 CUDA 运行库,无需安装 CUDA Toolkit;只需要
NVIDIA 驱动 ≥ 570(数据中心显卡可用更老驱动)。驱动不满足或没有 NVIDIA 显卡时,
设备会自动回退到 CPU(macOS 为 MPS),只有显式选择 CUDA 才会报错。
macOS(Apple Silicon)会自动从 PyPI 安装 CPU 版 PyTorch,推理设备默认使用 MPS
(Apple Metal)加速,也可以在设备选项里切换 CPU。PyTorch 自 2.3 起不再提供 Intel
Mac(x86_64)轮子,Intel Mac 暂不支持。DA3 射线位姿估计中的 3×3 QR 分解在 MPS
上尚未实现,SnapForm 的覆盖文件会单独把该分解放到 CPU 执行,其余推理仍走 MPS。
Extension 启用后只会查找已有的共享 AI Host,不会主动启动新进程。第一次执行 AI 操作或在侧边栏
手动点击 `Start` 时才会启动 Host。Host 只绑定回环地址,并让操作系统分配动态端口;第一个实例把
端口、进程和随机 Token 写入 `host.json`,其余 Blender 通过标准库
`urllib.request` 连接同一个 Host。`host.lock` 保证同一 Storage Root 同时只有一个 Host,Token
则阻止同机其他进程直接调用接口。Host 全局同时只执行一个 AI 任务。
每个 Blender 会注册独立客户端并每 5 秒发送心跳。正常关闭 Blender 时先注销自身;如果当前 Host
就是由这个 Blender 启动的,会直接终止它,无需等待。异常退出的客户端会在 10 秒后过期;Host
在最后一个客户端离开后仍会保留模型缓存等待 20 秒再退出,作为 Host 由其他进程启动等场景的兜底。
取消任务不会重启 Host,并会保留已经加载的模型;
如果取消发生在一次不可中断的 Torch 调用中,任务会在该调用返回后的最近检查点结束。
侧边栏 `Host` 面板以 `Running (Clients: n)` 表示空闲服务,以 `Running (Busy)` 表示正在执行任务,
并显示缓存模型和活动任务。Blender 完成结果导入时会立即释放当前任务对应的本地 Busy 快照,无需等待
下一次 Host 轮询。空闲且只有当前 Blender 连接时,可以清理模型缓存、重启或停止 Host;
多客户端或任务执行期间不会允许破坏共享服务。
BEN2 在首次使用后保持加载;DA3 保留最近使用的一个模型。切换 DA3 模型时会释放旧模型;加载时如果
显存不足,会先释放另一类缓存模型并重试。
Runtime Schema 已升级,旧 Runtime 需要重新执行 `Install Runtime` 才能安装 HTTP Host 依赖。
Host 的启动和崩溃日志写入 `~/.snapform/ai-host.log`。
Runtime、模型和任务结果默认保存在用户主目录的 `~/.snapform`:
```text
~/.snapform/
├── runtime/
├── models/
├── jobs/
├── ai-host.log
├── host.json # Host 运行时存在
└── host.lock # Host 运行时持有
```
`models/` 下每个模型一个文件夹,命名统一带前缀:DA3 为 `da3_*`(如 `da3_small`、`da3_base`),
BEN2 为 `ben2_*`(如 `ben2_base`)。旧版无前缀的 DA3 文件夹会在首次使用时自动改名为新名称。
不同 Blender 版本安装相同 Extension 后会共用这个目录。也可以在 Extension
Preferences 的 `Storage Root` 中修改共享位置。不要把模型写入 Extension 安装目录,
因为该目录可能只读,并且升级时会被替换。不要同时从多个 Blender 实例执行
`Install Runtime`;推理 Host、任务队列和模型读取可以共享。
## 开发
```bash
uv run python prepare_vendor.py
uv run pytest tests -v
uv run python pack.py
```
项目结构:
```text
.
├── vendor.lock.toml 第三方构建输入的版本、URL 与 SHA-256
├── prepare_vendor.py 下载并校验第三方构建输入
├── pack.py
├── patches/ 进入 Git 的 DA3 源码覆盖文件
├── vendor/ 不进入 Git 的下载缓存;本地调试也直接读取这里
├── src/
│ └── snapform/
│ ├── blender_manifest.toml
│ ├── ai_host.py
│ ├── bootstrap_runtime.py
│ ├── models.py
│ ├── image_depth.py
│ ├── operators.py
│ ├── panel.py
│ ├── preferences.py
│ ├── properties.py
│ ├── runtime.py
│ └── worker.py
└── tests/
```
## 上游与许可证
- Depth Anything 3 代码采用 Apache 2.0。
- BEN2 代码与模型权重采用 MIT License。
- 本 Extension 代码采用 GPL-3.0-or-later。
- 模型权重许可证由所选 Hugging Face 模型仓库决定,Extension 下载前会在界面显示。