如何在体素网格中可微表示多面体?求一致多面体体素化可微算法
可微多面体生成与体素化实现方案
一、一致性多面体生成:凸包+可微优化
要保证同一输入点集输出完全一致的多面体,凸包是最可靠的选择——点集的凸包是唯一的,且是能包含所有点的最大体积凸多面体,天然满足“一致性+最大体积”的要求。但传统凸包算法是离散的,不可微,所以得用可微优化的方式实现:
- 核心思路:把凸包的几何约束转化为可微损失函数,用梯度下降求解
- 约束项:确保所有输入点都在多面体内部/表面——计算每个输入点到多面体各面的有符号距离(面方程
ax+by+cz+d=0,内部点满足ax+by+cz+d>0),如果点在外部,就产生损失 - 目标项:最大化多面体体积——凸多面体的体积可以用顶点叉积的求和公式计算,这个表达式完全可微
- 一致性保证:固定优化的初始种子(比如用输入点的哈希值作为随机种子),确保同一输入每次优化的起点相同,最终收敛到同一个多面体
- 约束项:确保所有输入点都在多面体内部/表面——计算每个输入点到多面体各面的有符号距离(面方程
二、可微体素化:基于有符号距离场(SDF)
要把多面体转成体素网格,同时支持插值和可微,别用传统的射线法(不可微),用SDF是最优解:
- 坐标映射:先把多面体的顶点坐标映射到体素网格的坐标系(比如把输入点归一化到体素网格的
[0, W-1]×[0, H-1]×[0, D-1]范围) - 可微内部/外部判断:
- 对每个体素的中心坐标,计算到多面体的有符号距离(内部为正,外部为负)
- 用sigmoid函数把距离值映射到
0.001~0.99:value = 0.99 * sigmoid(k*d) + 0.001*(1-sigmoid(k*d)),k是控制陡峭程度的参数(值越大,体素内部/外部的分界越锐利) - 插值天然支持:因为体素中心是连续坐标,直接代入SDF计算即可,不需要额外做离散插值
- 多面体支持:如果有多个多面体,就计算每个多面体的SDF,取所有SDF的最大值(只要体素在任意一个多面体内部,就标记为内部),再代入上面的sigmoid转换
三、关键可微模块的细节
- 可微体积计算:凸多面体体积公式
1/6 * |Σ(v_i · (v_j × v_k))|,其中v_i,v_j,v_k是每个面的三个顶点(从原点出发的向量),这个公式对顶点参数完全可微 - 可微SDF计算:凸多面体的SDF是点到所有面的有符号距离的最小值,但硬最小值不可微,所以用softmin替代:
softmin(x1,...,xn) = -ε * log(Σexp(-xi/ε)),ε是平滑参数,既近似最小值,又能传递梯度
四、PyTorch伪代码实现
import torch def face_signed_dist(points, face_planes): # face_planes: (num_faces, 4),每个面的参数(a,b,c,d),内部点满足ax+by+cz+d>0 a, b, c, d = face_planes.unbind(dim=-1) return a*points[...,0] + b*points[...,1] + c*points[...,2] + d def softmin(x, eps=1e-3): # 平滑最小值,替代硬min保证可微 return -eps * torch.log(torch.exp(-x/eps).sum(dim=-1)) def poly_sdf(points, face_planes): # 计算点到多面体的有符号距离(内部为正) face_dists = face_signed_dist(points, face_planes) return softmin(face_dists, eps=1e-3) def poly_volume(vertices, faces): # 计算凸多面体体积,vertices: (num_verts,3), faces: (num_faces,3)(顶点索引) face_verts = vertices[faces] # (num_faces, 3, 3) cross_prod = torch.cross(face_verts[:,1], face_verts[:,2], dim=-1) dot_prod = torch.sum(face_verts[:,0] * cross_prod, dim=-1) return torch.abs(dot_prod.sum()) / 6.0 def generate_consistent_poly(input_points, num_faces=12): # 固定种子保证同一输入输出一致 seed = hash(tuple(input_points.tolist())) % 2**32 torch.manual_seed(seed) # 初始化面参数(从输入点中选初始面) init_faces = torch.randint(0, len(input_points), (num_faces, 3)) face_planes = torch.randn(num_faces, 4, requires_grad=True) optimizer = torch.optim.Adam([face_planes], lr=1e-3) for _ in range(1000): optimizer.zero_grad() # 约束所有输入点在多面体内部 point_dists = poly_sdf(input_points, face_planes) constraint_loss = torch.mean(torch.relu(-point_dists)) # 外部点产生损失 # 最大化体积 vol = poly_volume(input_points, init_faces) # 总损失:约束优先,再最大化体积 loss = 100 * constraint_loss - vol loss.backward() optimizer.step() return face_planes def voxelize_multiple_polys(poly_face_planes, voxel_shape=(32,32,32)): W, H, D = voxel_shape # 生成体素中心坐标网格 x = torch.linspace(0, W-1, W) y = torch.linspace(0, H-1, H) z = torch.linspace(0, D-1, D) xx, yy, zz = torch.meshgrid(x, y, z, indexing='ij') voxel_centers = torch.stack([xx, yy, zz], dim=-1).reshape(-1, 3) # 计算所有多面体的SDF,取最大值(只要在任意多面体内部就算内部) sdf_list = [] for planes in poly_face_planes: sdf_list.append(poly_sdf(voxel_centers, planes)) total_sdf = torch.stack(sdf_list, dim=-1).max(dim=-1)[0] # 映射到0.001-0.99的范围 steepness = 10.0 # 控制分界锐利程度 sig = torch.sigmoid(steepness * total_sdf) voxel_vals = 0.99 * sig + 0.001 * (1 - sig) return voxel_vals.reshape(W, H, D)
内容的提问来源于stack exchange,提问作者Jakub Mitura
相关产品推荐
相关产品推荐

