在三维生成任务里,直接预测网格顶点会面临顶点数量不固定、拓扑复杂和训练不稳定等问题。Dream3D采用的隐式曲面方案,改让模型输出空间中有符号距离场(Signed Distance Field,SDF)。网络接收一个三维坐标作为输入,输出该点到最近表面的有符号距离,距离为零的所有点共同构成模型表面。这样做把生成任务从离散几何预测转变为连续函数拟合,既保留了细节表达能力,又让反向传播更加顺畅。

一、SDF与隐式曲面的核心原理
SDF的定义比较直接:对空间中的任意点 x,函数 f(x) 返回 x 到模型表面最近点的欧氏距离。如果 x 位于闭合表面内部,距离取负值;如果位于外部,距离取正值。于是表面可以描述为集合 {x | f(x)=0},它不再依赖显式的三角形列表或点坐标,只用一个标量函数完整表达几何体。
隐式曲面带来的最大好处是拓扑自由度。以球体为例,标准SDF可以写成 f(x)=||x-c||-r,其中 c 是球心,r 是半径。这个函数对所有三维坐标都有定义,不需要存储任何面片。当形状发生融合或分离时,隐式函数只需要改变对应区域的值,而显式网格则必须重新连接顶点和边。对生成模型而言,这种连续性让网络可以学习平滑的形状流形,不会因为顶点顺序不同而陷入局部最优。
当然SDF也有代价。它要求模型在任意被查询的坐标上都能给出合理距离值,推理时要对包围盒进行密集采样,计算量高于直接生成少量顶点。此外,只有零等值面附近的值才直接决定表面位置,远离表面的区域主要影响梯度传播和法线方向。因此训练时如何设计采样策略,会显著影响最终生成质量。
二、Dream3D中的隐式生成流程
Dream3D将形状编码为一个隐函数网络,通常采用多层感知机(MLP)或带有位置编码的网络结构。输入除了三维坐标,还可以拼接潜码、视角方向或条件向量。输出是单个标量,表示该坐标的SDF值。网络参数通过监督或可微渲染进行优化,使输出距离场逼近真实形状。
在训练阶段,数据准备会围绕点采样展开。常见做法包括在网格表面采集点,并沿法线方向做小幅度扰动,形成表面附近的正负样本;同时在包围盒内部均匀采样,提供全局空间约束。损失函数一般包含三个部分:预测距离与真实距离的绝对误差、Eikonal正则项使梯度模长接近1、以及表面附近的法线一致性损失。Eikonal项非常关键,它保证SDF在局部近似欧氏距离函数,否则提取出的表面可能出现厚薄不均或断裂。
推理时,Dream3D会先确定感兴趣区域,例如一个以原点为中心的立方体。然后按固定分辨率把立方体划分成网格,将所有格点输入网络得到距离值,形成离散的三维距离场。该距离场随后交给等值面提取算法转换为三角网格。整个过程不需要生成中间点云,也不需要预先指定顶点数量。
三、关键代码实现:网络、损失与网格提取
下面给出一个简化实现,展示SDF网络的前向计算、训练损失组装以及从距离场恢复网格的过程。代码使用PyTorch风格,实际项目中可替换为其他框架。
import torch
import torch.nn as nn
class SDFNet(nn.Module):
def __init__(self, hidden_dim=256):
super().__init__()
self.net = nn.Sequential(
nn.Linear(3, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, 1)
)
def forward(self, x):
return self.net(x)
训练时,假设已经有一批坐标 points 和对应的真实SDF值 gt_sdf,可以这样计算损失。为了让网络输出的梯度更接近真实距离函数,Eikonal约束需要对输入坐标求导。
import torch
def sdf_loss(model, points, gt_sdf, lambda_eikonal=0.1):
points = points.requires_grad_(True)
pred = model(points)
data_loss = torch.abs(pred - gt_sdf).mean()
# 计算预测SDF关于输入坐标的梯度
grad = torch.autograd.grad(
outputs=pred,
inputs=points,
grad_outputs=torch.ones_like(pred),
create_graph=True,
retain_graph=True
)[0]
grad_norm = grad.norm(dim=-1)
eikonal_loss = torch.abs(grad_norm - 1.0).mean()
total_loss = data_loss + lambda_eikonal * eikonal_loss
return total_loss
在推理阶段得到离散距离场后,常用Marching Cubes算法提取零等值面。借助 skimage.measure.marching_cubes 可以直接得到顶点和面片索引。代码中要注意坐标原点与体素索引的对应关系,否则生成物体会出现偏移或镜像。
import numpy as np
from skimage.measure import marching_cubes
def extract_mesh(model, bounds=(-1.0, 1.0), resolution=128):
x = np.linspace(bounds[0], bounds[1], resolution)
y = np.linspace(bounds[0], bounds[1], resolution)
z = np.linspace(bounds[0], bounds[1], resolution)
grid_x, grid_y, grid_z = np.meshgrid(x, y, z, indexing='ij')
points = np.stack([grid_x, grid_y, grid_z], axis=-1).reshape(-1, 3)
points_tensor = torch.from_numpy(points).float()
with torch.no_grad():
sdf_values = model(points_tensor).numpy().reshape(resolution, resolution, resolution)
verts, faces, normals, _ = marching_cubes(sdf_values, level=0.0, spacing=(
(bounds[1] - bounds[0]) / (resolution - 1),
(bounds[1] - bounds[0]) / (resolution - 1),
(bounds[1] - bounds[0]) / (resolution - 1)
))
return verts, faces
上述代码中,spacing 参数将体素索引换算回世界坐标单位。如果省略它,顶点坐标会以体素为单位,范围在0到resolution之间。实际使用时,还需根据训练时的包围盒中心对顶点进行平移,使网格位于正确位置。
四、采样策略与训练稳定性
SDF网络的训练效果高度依赖点采样分布。只使用表面附近点会让网络在远离表面的区域输出任意值,导致提取网格时出现多余闭合面或空洞。而只使用均匀采样又会削弱对表面细节的约束,使最终模型缺乏清晰边缘。比较实用的做法是混合采样:大约一半样本从表面加点噪声得到,另一半从整个包围盒均匀采样。表面附近的正负样本比例也应大致平衡,否则网络可能偏向预测某一侧距离。
Eikonal损失权重同样需要调试。权重过大时,网络会倾向于让所有区域梯度都为1,可能牺牲表面精度;权重过小则距离场可能退化为任意函数,法线方向混乱。实践中可以把初始权重设为0.1,再根据验证集上的网格质量调整。另一种有效技巧是周期性终止Eikonal项,让网络先学习粗略表面,再恢复距离场一致性。
除了几何损失,Dream3D类方法还会加入颜色或纹理分支。网络可以在预测SDF的同时输出RGB值,然后在表面提取后为顶点赋予颜色。这种方式需要额外处理颜色场和几何场的分辨率差异,通常颜色分支可以用更低频率的特征,避免纹理过拟合到训练视角。
五、从隐式曲面到可用资产的转化
提取出的三角网格往往存在大量三角形,尤其是在高分辨率下,Marching Cubes会产生接近体素密度的面片数量。因此生成后处理通常包括网格简化、法线平滑和UV展开。网格简化可以使用二次误差度量或顶点聚类,减少面片的同时尽量保留表面轮廓。法线平滑则有助于消除体素提取产生的阶梯状伪影。
另一个容易被忽略的问题是水密性。SDF保证零等值面在理想情况下是闭合的,但数值误差和网络近似可能导致表面出现小孔。可以通过后处理补洞工具修复,也可以在提取时略微调整等值面阈值,例如使用level=0.005,观察是否得到更完整的表面。若生成结果需要用于物理仿真或3D打印,水密性是必须检查的属性。
Dream3D隐式曲面方案的价值不仅在于生成静态模型,还在于它可以和扩散模型、NeRF式表示结合。通过在不同噪声级别上学习距离场,扩散过程能够逐步生成越来越清晰的形状。由于SDF是连续函数,上采样只需要增加采样点,不需要重新训练网络结构,这为高分辨率三维资产生成提供了便利。