系列文章目录
- 【3D AICG 系列-1】Trellis v1 和 Trellis v2 的区别和改进
- 【3D AICG 系列-2】Trellis 2 的O-voxel (上) Shape: Flexible Dual Grid
- 【3D AICG 系列-3】Trellis 2 的O-voxel (下) Material: Volumetric Surface Attributes
- 【3D AICG 系列-4】Trellis 2 的Shape SLAT Flow Matching DiT 训练流程
- 【3D AICG 系列-5】Trellis 2 的 Pipeline 推理流程的各个中间结果和形状
- 【3D AICG 系列-6】OmniPart 训练流程梳理
- 【3D AICG 系列-7】PartUV 代码流程深度解析
- 【3D AICG 系列-8】PartUV 流程图详解
- 【3D AICG 系列-9】Trellis2 推理流程图超详细介绍
- 【3D-AICG 系列-10】Trellis v2 只在 512/1024上训却能生成 1536
- 【3D-AICG 系列-11】Trellis 2 的 Shape VAE 训练流程梳理
- 【3D-AICG 系列-12】Trellis 2 的 Shape VAE 的设计细节 Sparse Residual Autoencoding Layer
- 【3D-AICG 系列-13】Trellis 2 的 SC-VAE 的 Training Loss 细节
文章目录
- 系列文章目录
- [为什么 Texturing Pipeline 保留单层薄壳,而 to_glb 会变成双层?](#为什么 Texturing Pipeline 保留单层薄壳,而 to_glb 会变成双层?)
-
- 背景
- 一、两条路径的完整流程对比
-
- [1.1 Image-to-3D Pipeline → to_glb](#1.1 Image-to-3D Pipeline → to_glb)
- [1.2 Texturing Pipeline](#1.2 Texturing Pipeline)
- 二、双层的根源:`remesh_narrow_band_dc`
-
- [2.1 算法原理](#2.1 算法原理)
- [2.2 为什么 UDF 必然产生双层](#2.2 为什么 UDF 必然产生双层)
- [2.3 作者为什么接受双层](#2.3 作者为什么接受双层)
- [三、Texturing Pipeline 为什么不需要 remesh](#三、Texturing Pipeline 为什么不需要 remesh)
-
- [3.1 输入假设不同](#3.1 输入假设不同)
- [3.2 纹理烘焙方式的差异](#3.2 纹理烘焙方式的差异)
- [3.3 材质设置的佐证](#3.3 材质设置的佐证)
- 四、对服装仿真的影响
- 五、总结
为什么 Texturing Pipeline 保留单层薄壳,而 to_glb 会变成双层?
深入分析 TRELLIS2 两条纹理化路径的架构差异与工程取舍
背景
TRELLIS2 提供了两条将 3D 形状"穿上纹理"的路径:
- Image-to-3D Pipeline (
trellis2_image_to_3d.py):从图像直接生成带纹理的 mesh,最终通过o_voxel.postprocess.to_glb()导出 GLB。 - Texturing Pipeline (
trellis2_texturing.py):接受一个已有的 mesh + 图像,只生成纹理并贴回原 mesh。
对于服装等单层 open surface,两条路径的输出存在根本性差异:
| 路径 | 几何变化 | 输出层数 | 可仿真性 |
|---|---|---|---|
to_glb (remesh=True) |
完全重建拓扑 | 双层闭合薄壳 | 不可仿真 |
| Texturing Pipeline | 零改变 | 保持原始单层 | 可仿真 |
本文将从代码层面解释这个差异的根源。
一、两条路径的完整流程对比
1.1 Image-to-3D Pipeline → to_glb
[图像]
│
▼
image_cond_model → 图像特征
│
▼
sample_sparse_structure → 稀疏体素坐标 (coords)
│
▼
sample_shape_slat → shape latent (SparseTensor)
│
▼
sample_tex_slat → texture latent (SparseTensor)
│
▼
decode_latent:
├── decode_shape_slat → Mesh (vertices, faces) + subs
├── decode_tex_slat → PBR 体素 (attr_volume)
├── fill_holes()
└── 打包成 MeshWithVoxel (几何 + 体素属性,纹理还是"散装"的)
│
▼
o_voxel.postprocess.to_glb(): ← 在这里分叉
├── remesh=True (带纹理 GLB)
│ remesh_narrow_band_dc → 窄带 DC 重建 → ★ 双层 ★
│ UV 展开 → 纹理烘焙 → GLB
│
└── remesh=False (白模 GLB)
simplify → cleanup → unify_face_orientations
UV 展开 → 纹理烘焙 → GLB (保持单层)
关键:decode_latent 的输出是 MeshWithVoxel------几何体 + 体素空间里的 PBR 属性。纹理尚未"贴到面上",只是和体素坐标绑定。要变成可渲染的 GLB,必须 经过 UV 展开 + 纹理烘焙,而这正是 to_glb() 的职责。
1.2 Texturing Pipeline
[已有 mesh (trimesh)] + [图像]
│ │
▼ ▼
preprocess_mesh preprocess_image
(归一化 + Y/Z 交换) (去背景 + 裁剪)
│ │
▼ ▼
encode_shape_slat get_cond
(FDG 体素化 → encoder) (图像特征)
│ │
└──────┬─────────────────┘
▼
sample_tex_slat → texture latent
│
▼
decode_tex_slat → PBR 体素
│
▼
postprocess_mesh:
├── 在 ★原始 mesh★ 上做 UV 展开 (cumesh.uv_unwrap)
├── nvdiffrast 在 UV 空间光栅化
├── grid_sample_3d 从 PBR 体素采样颜色
├── cv2.inpaint 修补 UV 缝隙
└── 组装 trimesh + PBRMaterial → GLB
关键:postprocess_mesh 直接在输入 mesh 的原始顶点/面上操作,没有任何拓扑重建步骤。
二、双层的根源:remesh_narrow_band_dc
2.1 算法原理
to_glb 中 remesh=True 时调用的核心函数是 cumesh.remeshing.remesh_narrow_band_dc,它做的事情是:
- 计算 UDF(无符号距离场):对输入 mesh 表面,计算空间中每个点到最近表面的距离(始终 ≥ 0)。
- 偏移等值面 :设
eps = band × scale / resolution,提取UDF = eps的等值面。 - 稀疏体素 + Dual Contouring:在表面附近建立窄带体素网格,用 DC 算法从体素场中提取 mesh。
- 投影回原面 :
project_back=0.9,把新顶点拉回原始表面附近。
2.2 为什么 UDF 必然产生双层
这是数学上的必然:
-
UDF 不区分内外:对 open surface(如一件衣服),表面两侧的距离都是正的。
-
UDF = eps等值面 :在原始表面的内侧和外侧 各eps处各形成一层,包裹成一个闭合的薄壳。eps 原始表面 eps ↓ ↓ ↓ ─────── ╔═══════╗ ═══════╗ ────── 外侧等值面 ║ 薄壳 ║ 内侧等值面 ─────── ╚═══════╝ ═══════╝ ──────
project_back 又把两层都压回原面附近 → 两层几乎重合,间距极小(实测 median_thickness ≈ 0.0007)。
2.3 作者为什么接受双层
to_glb 的设计目标是渲染/展示,而非仿真:
- DC remesh 后的拓扑均匀规整,UV 展开质量高。
- 双层闭合 mesh 不管从哪个角度看都有面朝向观察者,渲染结果完整。
- 因此材质设置为
doubleSided=False(不需要双面渲染,闭合壳自己就够了)。
三、Texturing Pipeline 为什么不需要 remesh
3.1 输入假设不同
| Image-to-3D | Texturing Pipeline | |
|---|---|---|
| 输入 | 无(从噪声生成) | 已有 mesh(如 PLY/OBJ) |
| 生成的几何 | FDG decoder 输出,拓扑可能不规则 | 用户提供,拓扑通常已经合理 |
| 是否需要修拓扑 | 是(DC remesh) | 否 |
Texturing Pipeline 的前提是:你已经有一个拓扑质量可接受的 mesh。它不负责修拓扑,只负责"上色"。
3.2 纹理烘焙方式的差异
虽然两条路径最终都是"从体素空间采样 PBR 属性到 UV 纹理",但实现方式截然不同:
to_glb 的方式 (postprocess.py):
python
# 用 remesh 后的新 mesh 做 UV 展开
out_vertices, out_faces, out_uvs, out_vmaps = mesh.uv_unwrap(...)
# 在 UV 空间光栅化(用新 mesh 的顶点/面)
rast, _ = dr.rasterize(ctx, uv_coords, out_faces, ...)
# 通过 BVH 映射回原始高分辨率 mesh 来采样属性
_, face_id, uvw = bvh.unsigned_distance(valid_pos, return_uvw=True)
它需要 BVH 做"新 mesh → 原始 mesh"的映射,因为几何已经被 remesh 替换了。
Texturing Pipeline 的方式 (trellis2_texturing.py):
python
# 直接在原始 mesh 上做 UV 展开
_cumesh.init(vertices_torch, faces_torch)
vertices_torch, faces_torch, uvs_torch, vmap = _cumesh.uv_unwrap(return_vmaps=True)
# 在 UV 空间光栅化(用原始 mesh 的顶点/面)
rast, _ = dr.rasterize(ctx, uvs_torch, faces_torch, ...)
# 直接用原始顶点位置去体素空间采样
pos = dr.interpolate(vertices_torch.unsqueeze(0), rast, faces_torch)[0][0]
attrs[mask] = grid_sample_3d(pbr_voxel.feats, pbr_voxel.coords, ...,
grid=((pos[mask] + 0.5) * resolution).reshape(1, -1, 3))
由于几何没变,UV 空间里光栅化出来的每个像素直接对应原始 mesh 上的 3D 位置,可以直接去体素空间采样------不需要 BVH 做中间映射,也不需要 remesh。
3.3 材质设置的佐证
python
# to_glb (postprocess.py:303)
doubleSided = True if not remesh else False
# remesh=True → doubleSided=False (双层闭合,不需要双面)
# remesh=False → doubleSided=True (单层,需要双面渲染)
# Texturing Pipeline (trellis2_texturing.py:355)
doubleSided = True # 始终双面,因为输入可能是单层 open surface
两处代码的 doubleSided 设置直接反映了开发者对输出几何的预期。
四、对服装仿真的影响
对于 CLO3D、Style3D、Marvelous Designer 等布料仿真软件:
| 要求 | to_glb (remesh=True) | Texturing Pipeline |
|---|---|---|
| 单层 open surface | ✗ 双层闭合 | ✓ 保持输入 |
| 法向一致性 | ✗ 内外层法向相反 | 取决于输入 |
| 可定义缝合线 | ✗ 无边界边 | ✓ 有边界边 |
| 碰撞检测 | ✗ 自碰撞爆炸 | ✓ 正常 |
| 质量/弯曲 | ✗ 翻倍 | ✓ 正确 |
| 纹理质量 | 高(规整拓扑 UV) | 取决于输入拓扑 |
结论:如果目标是生成可仿真的带纹理单层服装 mesh,应该:
- 用 Image-to-3D Pipeline 生成几何(
remesh=False路径保留单层) - 再用 Texturing Pipeline 在单层 mesh 上贴纹理
而不是直接用 to_glb(remesh=True) 一步到位。
五、总结
┌─────────────────────────────────┐
│ 纹理体素 (PBR Voxels) │
│ 在 3D 空间中存储颜色/材质属性 │
└──────────┬──────────────────┬────┘
│ │
┌──────────▼──────┐ ┌────────▼─────────┐
│ to_glb 路径 │ │ Texturing 路径 │
│ │ │ │
│ remesh (DC) │ │ 直接 UV 展开 │
│ → 双层闭合 │ │ → 保持原始几何 │
│ → 规整拓扑 │ │ → 原始拓扑 │
│ → BVH 映射 │ │ → 直接采样 │
│ → 高质量 UV │ │ → UV 质量取决输入 │
│ │ │ │
│ doubleSided: │ │ doubleSided: │
│ False │ │ True │
└─────────────────┘ └───────────────────┘
适合:渲染/展示 适合:仿真/动画
双层不是 bug,而是窄带 UDF + Dual Contouring 对 open surface 的固有行为------这是 to_glb 为获得规整拓扑和高质量 UV 展开所付出的代价。Texturing Pipeline 通过"不碰几何、只贴纹理"的设计,完全规避了这个问题,代价是要求输入 mesh 自身的拓扑质量足够好。
两条路径不是"好与坏"的关系,而是服务于不同的下游场景:看 还是用。