You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

需求:基于初始三角3D网格的极小曲面近似算法(Python实现)

带约束的三角网格极小曲面近似算法(复刻Blender平滑顶点)

Blender的「平滑顶点」核心是带余切权重的拉普拉斯平滑,这个算法完全契合极小曲面的离散化准则(局部平均曲率为零),以下是核心逻辑、约束处理、收敛控制的细节,以及Python核心实现:

核心算法原理

极小曲面的离散化等价于让每个顶点的余切拉普拉斯向量为零——这个向量的计算考虑了相邻三角形的几何形状,比简单的邻域平均更贴合极小曲面的数学定义:

  • 对每个非约束顶点 (v_i),拉普拉斯向量计算公式:
    [
    \Delta v_i = \frac{1}{2A_i} \sum_{j \in N(i)} (\cot \alpha_{ij} + \cot \beta_{ij}) (v_j - v_i)
    ]
    其中:
    • (N(i)) 是 (v_i) 的邻接顶点集合
    • (\alpha_{ij})、(\beta_{ij}) 是边 (v_i v_j) 两侧三角形中,与该边相对的夹角
    • (A_i) 是 (v_i) 的Voronoi面积(可简化为相邻三角形面积的1/3)
  • 顶点更新:(v_i^{t+1} = v_i^t + \lambda \cdot \Delta v_i),(\lambda) 是步长(松弛因子,通常取0.1~0.5)

约束处理

  • 固定顶点:直接跳过更新,保留原始坐标
  • 面内平滑:若需约束顶点在指定平面内,每次更新后将顶点投影回目标平面即可;也可在计算拉普拉斯向量时,仅保留平面内的分量

步长与收敛控制

  • 步长 (\lambda):过小会导致收敛缓慢,过大可能引发震荡甚至发散,建议从0.2开始调试
  • 收敛判定:两种常用方式:
    1. 计算所有顶点的位移平方和,当小于阈值(如 (1e-6))时停止迭代
    2. 固定迭代次数(Blender默认约10次,复杂网格可增加到50次)

Python核心实现(无依赖)

import numpy as np

def build_cotangent_adjacency(vertices, faces):
    # 构建邻接表,存储每个顶点的邻接点及对应的余切权重
    adjacency = {i: [] for i in range(len(vertices))}
    vertices = np.array(vertices)
    
    for face in faces:
        a, b, c = face
        # 计算当前面三条边的余切权重
        # 边ab的余切(对应三角形中c角的邻边夹角)
        vec_ac = vertices[c] - vertices[a]
        vec_bc = vertices[c] - vertices[b]
        cross = np.cross(vec_ac, vec_bc)
        if np.linalg.norm(cross) < 1e-9:
            cot_ab = 0.0
        else:
            cot_ab = np.dot(vec_ac, vec_bc) / np.linalg.norm(cross)
        
        # 边bc的余切(对应a角)
        vec_ba = vertices[a] - vertices[b]
        vec_ca = vertices[a] - vertices[c]
        cross = np.cross(vec_ba, vec_ca)
        if np.linalg.norm(cross) < 1e-9:
            cot_bc = 0.0
        else:
            cot_bc = np.dot(vec_ba, vec_ca) / np.linalg.norm(cross)
        
        # 边ca的余切(对应b角)
        vec_cb = vertices[b] - vertices[c]
        vec_ab = vertices[b] - vertices[a]
        cross = np.cross(vec_cb, vec_ab)
        if np.linalg.norm(cross) < 1e-9:
            cot_ca = 0.0
        else:
            cot_ca = np.dot(vec_cb, vec_ab) / np.linalg.norm(cross)
        
        # 更新邻接表(注意每条边属于两个面,完整实现需合并权重,这里简化为累加)
        adjacency[a].append((b, cot_ab))
        adjacency[b].append((a, cot_ab))
        adjacency[b].append((c, cot_bc))
        adjacency[c].append((b, cot_bc))
        adjacency[c].append((a, cot_ca))
        adjacency[a].append((c, cot_ca))
    
    return adjacency

def constrained_laplacian_smooth(vertices, faces, fixed_vertex_indices, 
                                iterations=10, step=0.2, convergence_threshold=1e-6):
    vertices = np.array(vertices, dtype=np.float64)
    adjacency = build_cotangent_adjacency(vertices, faces)
    
    for _ in range(iterations):
        new_vertices = vertices.copy()
        total_displacement = 0.0
        
        for idx in range(len(vertices)):
            if idx in fixed_vertex_indices:
                continue
            
            weight_sum = 0.0
            laplacian = np.zeros(3)
            for neighbor_idx, cot_weight in adjacency[idx]:
                laplacian += cot_weight * (vertices[neighbor_idx] - vertices[idx])
                weight_sum += cot_weight
            
            if weight_sum < 1e-9:
                continue
            
            # 归一化拉普拉斯向量,应用步长更新
            laplacian /= weight_sum
            displacement = step * laplacian
            new_vertices[idx] += displacement
            total_displacement += np.linalg.norm(displacement) ** 2
        
        # 检查收敛
        if total_displacement < convergence_threshold:
            break
        vertices = new_vertices
    
    return vertices.tolist()

关键细节补充

  • 余切权重的作用:权重大小对应相邻三角形的“平坦度”,平坦区域的权重更高,顶点移动时会更倾向于向这些区域靠拢,完美匹配极小曲面的平均曲率为零要求
  • Blender的面内平滑:本质是将更新后的顶点投影到其相邻面的平均平面上,避免顶点被拉出原始网格的局部平面
  • 简化版实现:如果不需要严格的极小曲面精度,也可以用简单邻域平均(每个顶点移动到邻接点的平均位置),但效果远不如余切权重版本贴合极小曲面准则

内容的提问来源于stack exchange,提问作者Franz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 12:45:40