# Surface ## Image Empty 转透视网格 1. 在 3D View 选中仅包含一帧的 Image Empty,右键打开 `SnapForm` 子菜单并选择 `Convert to Surface`。该入口独立于 `Convert to Depth Plane`,当前不处理视频或序列帧。 模型列表包含三个 MoGe-2 Normal 模型和两个 DA3 模型,默认使用 MoGe-2 ViT-S Normal。DA3 的 Resolution Scale 与 Depth Plane 使用相同设计;MoGe-2 改为显示共享的 Resolution Level。 2. Server 先用未经抠图的原始 RGB 请求 DA3 的 `depth`、`conf` 与 `intrinsics`,随后仅在开启 `Remove Background` 时运行 BEN2 取得前景 Alpha;Alpha 经目标网格分辨率缩放与轻微模糊后,只要 大于 `0` 就把对应点视为前景;每个三角形的三个顶点必须全部为前景,任一顶点 Alpha 为 `0` 就在输出前删除该面; 关闭时不加载 BEN2。Depth 不做 smoothing,Server 反投影深度网格,建立源三角面, 计算 confidence percentile 与 3×3 相对深度不连续度,并写入 基于原图像素坐标的源 `UVMap`。这些步骤只准备修改器的输入网格,不执行 Relax、拓扑修复、平滑、 小岛清理或最终 UV 重投影。运行时显示的六个 `Preparing surface data` 步骤均指这段 Server 端源数据准备。 DA3 与 MoGe-2 最终都由 `server/geometry/surface.py` 构建同一份 Surface 数据;模型模块只提供各自的 深度、点、法线、Mask 与相机数据,不再自行拥有 Surface 网格输出实现。 3. Blender 把勾选 `Usage > Modifier` 的 `O DA3 Surface` 节点组直接作为 Geometry Nodes 修改器 添加到 Surface,不另设修改器名称。节点组内没有子节点组,依次完成: - 修改器顶层依次显示 `Iterations`、`Resolution`、`Smooth`;`Iterations` 默认 `2`、最小 `0`, 默认展开的 `Culling` Panel 依次包含 `Depth Continuity` 与 `Confidence`。前者直接读取面域 3×3 相对深度差,并使用指数映射: 默认 `50%` 对应官方 `rtol=0.04`,数值越大,内部阈值越小,剔除的断层面越多; - 每轮 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`。 MoGe-2 使用独立的 `O MoGe2 Surface`。Server 由内置 NumPy 网格构建函数 生成有限深度范围内的紧凑三角网格,并保留模型 mask、逐点 depth 及相邻点相对深度差。Geometry Nodes 按官方 `mask & ~depth_map_edge(depth, rtol=0.04)` 的含义,先由 `Mask` 删除 mask 外的面,再由 `Depth Continuity` 按同一份 3×3 邻域相对深度差删除断层面。DA3 与 MoGe-2 使用相同的属性、 名称、指数滑块和 FACE 删除逻辑,不再额外删除孤立点。之后才执行拓扑修复、Relax、孤岛清理与 UV 重投影。 节点组不提供 Confidence 或 Remesh 开关;Remesh 仍是节点处理流程的一部分。 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,并隐藏没有连线的接口,避免跨越整个节点图的输入连线。 `O DA3 Surface` 与 `O MoGe2 Surface` 的 `Iterations` 默认值均为 `2`、最小值为 `0`, `Resolution` 默认值均为 `0.1`,所有输入 socket 的 Structure Type 均固定为 `Single`。 5. Surface Job 统一按输入准备、模型推理、可选 BEN2、构建 SurfaceMesh、写出产物五个阶段更新进度;单帧推理只显示模型与阶段名称,不追加 `1/1`,仅多帧时显示当前帧与总数; Surface Builder 是不接收 Job Context 或 progress callback 的纯数据转换。 6. Server 在 job 根目录写出 `mesh.npz`。NPZ 保存 camera-space `vertices`、`faces`、`uv`、 `intrinsics`、`image_size` 与有效深度中位数 `reference_depth`。颜色层无论来自原图复制还是 BEN2, Server 都统一写为 `color.0001.*`。Blender 读取网格数据后额外导入为 `.color`,材质不复用源 Empty 的 Image,Color Space 固定为 `AgX Base sRGB`。Server 不生成 `texture.png`,材质只在 Blender 端创建。 DA3 Surface 只保留 Geometry Nodes 后处理实际使用的 attributes:点域 `o_depth`、面域 `o_confidence` 与面域 `o_depth_discontinuity`。Remove Background 已在 Server 建面前直接删除背景三角面,不写入任何 background attribute。MoGe-2 的导入网格保留点域 `o_depth`, 以及面域 `o_mask`、`o_depth_discontinuity`,不伪造 confidence、stretch 或 background 属性, 也不写入 custom normal。MoGe-2 推理返回的 normal 直接编码为独立的 `normal.png`,Blender 将它 作为 Non-Color 贴图导入现有材质;normal 不进入 `mesh.npz`。 所有 MoGe-2 Image datablock 都以源图片名而非对象名为基名:depth、normal 与 mask 分别使用 `图片名.depth`、`图片名.normal`、`图片名.mask`,不附加 `.moge2`。 7. 网格以 `reference_depth` 截面匹配 Image Empty 的显示矩形:该截面的投影四角与 Empty 的可见 边界重合,主点同时匹配 Empty Offset。网格对象直接沿用源 Empty 的 `matrix_world`,因此位置、旋转、 缩放及父级关系产生的世界变换保持一致。更近的顶点沿 Empty 局部 `+Z` 方向凸出,三角面正面朝向 观察相机,启用 Backface Culling 时可从正面看到。 8. 转换对话框不再提供后处理参数,只保留模型与 Resolution Scale 设置、`Create Matching Camera`、 `Remove Background`、`Shadeless`,以及分割线下方的 `Keep Original`。背景移除默认关闭;开启后 Server 使用 BEN2,并在写出 Surface 前直接删除背景三角面,不再由 Geometry Nodes 的 Background 开关处理。 该预删除同时适用于 DA3 与 MoGe-2;因此 MoGe-2 转换后即使关闭 `Mask`,BEN2 已删除的背景也不会恢复。 Surface 材质的 Color Image Texture 始终使用 `.color`;Remove Background 只决定该层是否带 BEN2 Alpha,原 Empty 始终保持原图。颜色层使用 `AgX Base sRGB`。 `Shadeless` 使用与 Depth Plane 相同的 `O Shadeless` 节点组,并继续通过 Alpha 与 Transparent BSDF 混合;普通材质的 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)。 所有任务使用统一的状态栏进度显示。变化中的阶段说明显示在右侧的进度条内部,取消按钮 位于进度条右侧。状态字段支持阶段、当前操作与预计剩余秒数,界面统一组合为紧凑文案并保证 进度单调递增。任务进程与 Server 请求在后台启动,按钮点击后立即进入进度状态。Image Depth 的阶段包括 Environment 启动、输入准备、模型加载、深度估算、缓存 写出和面片构建。进度更新会强制刷新状态栏与窗口,无需移动鼠标即可实时显示。安装中断后残留的 半成品 Environment 会在下次安装前自动清理,不会因为中断而无法再次安装。 导入过程精确保留 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`,DA3 与 Original 都使用 `Midlevel = 0`、`Scale = 1`。MoGe-2 的位移只在专用 Geometry Nodes 中处理。 `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。DA3 Surface 使用 `O DA3 Surface Layer`, MoGe-2 Surface 使用独立的 `O MoGe2 Surface Layer`;它额外接收模型输出的 `Normal` 贴图,使用 `Normal Map` 节点按 `Object Space` 解码后连接到 Bump 的 `Normal` 输入,使像素级模型法线与原图 Bump 合并,不依赖重拓扑后的顶点密度;`Normal Strength` 默认值为 `1.0`,只调节模型 normal map, 颜色贴图 Bump 仍由独立的 `Bump Scale` 控制; Depth Plane 使用 `O DA3 Depth Plane Layer` 材质组,并把背景剔除、背景深度与材质置换 Attribute 读取全部收进组内。两者都接收 `Color`、`Alpha` 与 `Bump Scale`,只有 `O DA3 Depth Plane Layer` 额外接收 Float 类型的 `Depth`。 `O DA3 Surface Layer` 只输出 `Color`、`Roughness`、`Alpha` 与 `Normal`,以原图 Color 保留 Bump 效果, 不创建 Displacement 节点或输出;`O DA3 Depth Plane Layer` 另外输出 `Displacement`。外层材质只保留贴图、 材质组、Principled BSDF 与 Material Output,方便继续调整。 Depth Plane 的 Bump Height 直接使用材质组的 `Color` 输入,只受独立的 `Bump Scale` 影响,不使用深度贴图, 也不经过 `Displacement` 模式开关、`Depth Scale` 或 `Background Depth`;材质 Displacement 仍使用这些参数处理后的深度。 无论选择哪种置换方式,Layer 的 `Color` 都会连接 Principled BSDF 的 `Base Color`、 `Subsurface Radius` 与 `Emission Color`;Layer 的 `Normal` 同时连接 `Normal` 与 `Coat Normal`, 通过 `Bump.Height → Bump.Normal` 提供表面细节。Subsurface、Emission 与 Coat 的强度等其他输入 保持 Principled BSDF 默认值。Bump 的基础 Distance 为 `0.001`,会乘以面片 Transform 的统一缩放倍率。原图 Color 还会经过 `Float Curve` 连接到 Principled BSDF 的 `Roughness`,曲线两端输出分别为 `0.5` 和 `0.1`;材质 IOR 保持 Principled BSDF 默认值 `1.5`。 开启 `Shadeless` 后,色彩贴图 Color 接入 `O Shadeless`。该组沿用 Blender Images as Planes 的 Diffuse、Emission、Light Path 与射线判断连接;二值 Alpha 仍作为 Mix Shader 系数,在 Transparent BSDF 与 `O Shadeless` 之间混合后连接 Material Output。外层不再创建 Emission、Principled BSDF、Bump 或 Float Curve。开启 `Remove Background` 时色彩贴图使用 `.color`;关闭时使用 `.color`。 这里的“原始距离”是 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`、`color.0001.jpg`(`.color`)或 `color.0001.png`,并在每帧保留原始 浮点深度;保存 `.blend` 后再次打开仍需对应 job 目录存在。 每个 Server Job 被接受时,Job Runtime 在 Runtime Storage Root 的 `jobs/` 下创建一个独立文件夹。文件夹名是 32 位 opaque Job ID,不包含任务类型或对象名称。执行逻辑从 `JobContext.directory` 获取路径,输出产物直接写入该目录。 Blender 为 Packed Image 或序列帧生成的输入副本先保存在系统临时目录,Server 读取完成后由 Blender 清理,不归档到 Job 目录。普通磁盘文件和视频仍直接读取原文件。Server 日志统一写入 `server.log`,进度只保存在 Server 内存中。Environment Bootstrap 使用 Storage Root 下的 `install.log` 与临时的 `install-status.json`,不创建 Job 目录。 正式几何输出由 `generate-depth` 与 `generate-surface` 负责。Debug 使用彼此独立的 `debug-da3`、`debug-moge2` 与 `debug-ben2`,只保存模型 API 的直接返回,不进入 Geometry 或 Surface 输出流程。 Job 目录中的典型正式产物如下: ```text / ├── results.npz、mesh.npz 或 normal.png └── prediction.npz Debug 原始 API 返回 / ├── color.0001.jpg/png… 正式工作流颜色层(原图复制或 BEN2 Alpha) ├── depth.0001.exr… AI 深度序列帧 └── normal.png MoGe-2 Surface 法线贴图 ``` 建议从 `DA3 Base` 开始。它使用 Apache 2.0 模型权重。