第六课:曲率计算(离散高斯曲率 + 离散平均曲率)——从命中点到理解几何形状
🎯 第六课:曲率计算——从”命中点”到”理解几何形状”
为什么排第六?
前五课我们解决了”射线打在哪里”(Intersection),但 打在哪 只是几何信息,那个地方长什么样 才是业务关心的——3D 打印零件的表面是凸的?凹的?还是平的?有没有尖角?壁厚够不够?
曲率就是回答这些问题的数学工具。它是从”计算”到”理解几何”的桥梁——技术切入,通向业务。
用你的话说:”从’计算’到’理解几何’——技术到业务的桥”(学习路径规划时说的)。
费曼学习法 Step 1:用大白话解释它是什么
想象你用手摸一个零件的表面:
平坦的地方(平面):手指感觉不到弯曲 → 曲率 ≈ 0
圆柱面:一个方向弯,另一个方向平 → 一个曲率大、一个曲率小
球面:两个方向都弯 → 两个曲率都大
鞍面(马鞍形):一个方向往上弯,另一个往下弯 → 两个曲率一正一负
高斯曲率 = 两个方向曲率的乘积 → 告诉你”这个地方是凸的、凹的、还是平的”
平均曲率 = 两个方向曲率的平均值 → 告诉你”这里弯得有多厉害”
高斯曲率 平均曲率 形状 3D打印含义
0 > 0 球面(凸) 材料堆积,容易过热
0 < 0 球面(凹) 可能需要支撑
< 0 任意 鞍面 应力集中,容易变形
= 0 > 0 圆柱面 可打印,注意方向
= 0 = 0 平面 最容易打印
费曼学习法 Step 2:逐个拆解核心知识点
📦 A. 离散高斯曲率——角亏法(Angle Deficit)
A1. 连续世界的高斯曲率
光滑曲面上一点的主曲率 κ₁、κ₂,高斯曲率 K = κ₁ × κ₂。
但我们有的是三角形网格,不是光滑曲面——三角形内部是完全平的(曲率为0),曲率全部”藏”在顶点处。
A2. 角亏法——把曲率”收集”到顶点上
直觉: 想象你是一个顶点,周围围了一圈三角形。如果把周围的三角形”摊平”,看它们能不能刚好凑成360°:
刚好360° → 你在平面上 → K = 0
凑不够360°(有缺口)→ 你在球面上 → K > 0
超过360°(重叠了)→ 你在鞍面上 → K < 0
平面顶点 (K=0): 球面顶点 (K>0): 鞍面顶点 (K<0):
╲ 90° ╱ ╲ 60° ╱ ╲ 100° ╱
╲ ╱ ╲ ╱ ╲ ╱
90° ● 90° 60° ● 60° 100° ● 100°
╱ ╲ ╱ ╲ ╱ ╲
╱ 90° ╲ ╱ 60° ╲ ╱ 100° ╲
角度和 = 360° 角度和 = 240° 角度和 = 400°
角亏 = 360-360 = 0 角亏 = 360-240 = 120 角亏 = 360-400 = -40
K = 0 (平面) K > 0 (凸) K < 0 (鞍面)
A3. 公式
K(v) = (2π - Σθᵢ) / A(v)
其中:
θᵢ = 顶点v在第i个邻居三角形中的内角
Σθᵢ = 所有邻居三角形中v的内角之和
A(v) = 顶点v的”混合面积”(周围三角形面积的1/3之和)
2π = 360°的弧度值
为什么除以面积? 角亏是”总量”,除以面积得到”密度”——同样120°的角亏,分布在小面积上(尖锐的凸起)比大面积上(平缓的隆起)曲率更大。
A4. 项目中的实现流程
// 对每个顶点 v:
float angle_sum = 0;
float area_sum = 0;
// 遍历共享顶点v的所有三角形(用第3课的顶点哈希表!)
for (each triangle tri sharing vertex v) {
// ① 计算v在tri中的内角
Vec3 e1 = tri->other_vertex1 - v;
Vec3 e2 = tri->other_vertex2 - v;
float cos_angle = dot(normalize(e1), normalize(e2));
float angle = acos(clamp(cos_angle, -1.0f, 1.0f)); // 钳位防NaN
angle_sum += angle;
// ② 累加面积
area_sum += triangle_area(tri) / 3.0f; // 每个三角形贡献1/3面积
}
float K = (2.0f * M_PI - angle_sum) / area_sum;
A5. 关键细节:clamp(cos_angle, -1, 1)
acos 的输入必须在 [-1, 1],但浮点误差可能让 dot 算出 1.0000001 或 -1.0000001 → acos 返回 NaN → 整个曲率值炸掉。clamp 是一道保险,看似多余,缺了必崩。
AI坑点: AI 经常忘记 clamp,在正常模型上没问题,但遇到退化三角形(极小的角)就出 NaN,而且 NaN 会”传染”——后续聚类、分类全变成 NaN,极难溯源。
📦 B. 离散平均曲率——法线差分法
B1. 直觉
高斯曲率告诉你”凸还是凹”,平均曲率告诉你”弯得厉不厉害”。
一个平面可以是倾斜的(法线方向变了),但曲率为0。平均曲率捕捉的是法线方向的变化率——法线变得越快,曲面弯得越厉害。
B2. 公式——Laplacian 法线法
H(v) = ||Δv|| / 2
其中 Δv = Laplacian(v) = (1/n) Σ neighbor_i - v
= 邻居质心 - v本身
大白话: 把一个顶点的所有邻居的坐标取平均(邻居质心),看这个质心跟顶点本身差多少。差得越多,曲面在这里弯得越厉害。
B3. 项目中的实现——法线差分法(等价变体)
// 对每个顶点 v:
Vec3 normal_sum(0, 0, 0);
int count = 0;
// 遍历共享顶点v的所有三角形
for (each triangle tri sharing vertex v) {
normal_sum += tri->normal; // 累加邻居三角形法线
count++;
}
Vec3 avg_normal = normalize(normal_sum / count);
// v 的顶点法线(从顶点哈希表获取)
Vec3 vertex_normal = vertex_map[vertexKey(v)].normal;
// 法线差分 = 两个法线的偏差程度
float H = 1.0f - dot(avg_normal, vertex_normal);
// H ≈ 0 → 平坦
// H ≈ 1 → 急弯(法线几乎垂直)
为什么 1 - dot(n1, n2) 是合理的?
两个法线相同 → dot = 1 → H = 0 → 平坦 ✓
两个法线垂直 → dot = 0 → H = 1 → 急弯 ✓
两个法线相反 → dot = -1 → H = 2 → 极端弯折(锐边)✓
这就是余弦距离的变体——用点积衡量两个方向的差异。
📦 C. 法线夹角——邻域的”转弯程度”
float normal_angle(Vec3 n1, Vec3 n2) {
float cos_a = clamp(dot(n1, n2), -1.0f, 1.0f);
return acos(cos_a); // 返回弧度 [0, π]
}
C1. 这个函数在项目中的三处用途
用途 含义 判断标准
曲率计算 顶点法线与邻居法线的夹角 角度大 → 曲率高
边界检测 共享边的两个三角形法线夹角 角度 > 阈值 → 这是一条”硬边”
聚类边界 同一聚类内相邻三角形法线夹角 角度突变 → 聚类边界
一个函数,三种用途——这就是”定义问题”的价值(理念 1.1)。你定义了”法线夹角”这个度量,后面曲率、聚类、分类都用它。
📦 D. 厚度计算——光线追踪的业务终点
float compute_thickness(Triangle* tri1, Triangle* tri2) {
float min_dist = FLT_MAX;
// 两个三角形各3个顶点,共9对
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
float d = distance(tri1->vertex[i], tri2->vertex[j]);
min_dist = min(min_dist, d);
}
}
return min_dist;
}
D1. 为什么不是”点到面距离”而是”9对顶点最小距离”?
精确的”三角形到三角形距离”需要考虑点-边距离、边-边距离等6种情况,代码复杂且容易有浮点问题。9对顶点距离是一个保守近似——对于 3D 打印来说,壁厚的保守估计(偏小)比乐观估计(偏大)更安全。
D2. 这就是光线追踪 distance 的业务含义
第2课学过 Intersection 的 distance 字段。射线从表面射出,命中对面壁,distance 就是局部壁厚。9对顶点距离是对这个值的验证/补充——两者取最小值作为最终壁厚报告。
📦 E. 曲率计算依赖的完整数据流
┌─────────────────────────────────────────────────────┐
│ 第3课的成果 │
│ │
│ 顶点哈希表: vertexKey → VertexInfo{normal, area, degree}
│ 边哈希表: EdgeKey → vector
└──────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 曲率计算 (本课) │
│ │
│ for each vertex v: │
│ 邻居三角形 = vertex_map[vertexKey(v)] 的 degree │
│ │
│ ┌─ 高斯曲率 ────────────────────────────┐ │
│ │ angle_sum = Σ 内角(v在每个邻居三角形中) │ │
│ │ area_sum = Σ 面积/3 │ │
│ │ K = (2π - angle_sum) / area_sum │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌─ 平均曲率 ────────────────────────────┐ │
│ │ avg_normal = Σ tri.normal / degree │ │
│ │ H = 1 - dot(avg_normal, v.normal) │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌─ 壁厚 ──────────────────────────────┐ │
│ │ 从v沿法线射线 → BVH → distance │ │
│ │ 或9对顶点最小距离 │ │
│ │ thickness = min(ray_dist, vertex_dist)│ │
│ └──────────────────────────────────────┘ │
└──────────────────────┬──────────────────────────────┘
│
▼
每个顶点: {K, H, thickness}
│
▼
聚类 + 分类 (第7课)
费曼学习法 Step 3:三种曲率度量的对比
高斯曲率 K 平均曲率 H 壁厚 thickness
数学本质 内蕴(只跟曲面本身有关,跟怎么嵌入空间无关) 非内蕴(跟曲面在空间中的弯曲方式有关) 纯几何距离
计算方式 角亏 / 面积 法线差分 射线距离 / 顶点距离
依赖 顶点哈希表(邻居三角形内角) 顶点哈希表(邻居法线) BVH(光线追踪)
能区分 凸/凹/鞍/平 弯得厉害/平缓 厚/薄
3D打印含义 凸面过热、鞍面变形 急弯需减速 壁太薄不可打印
独立信息 ✅ 高斯曲率是内蕴量 ⚠️ 跟高斯曲率有关但不等价 ✅ 完全独立
关键认知: 高斯曲率和平均曲率是两个独立维度——同一个高斯曲率值,可能有不同的平均曲率(圆柱 vs 球面的一部分)。两个一起用,才能完整描述局部几何。
费曼学习法 Step 4:识别你的知识缺口
# 自检问题 如果你答不出
1 角亏法中,2π 是哪来的?为什么不是 π 或 4π? 重新理解”平面上的完整圆周角”
2 为什么面积要除以3(area_sum += tri_area / 3)?每个三角形3个顶点,各分1/3? 重新理解混合面积
3 clamp(cos, -1, 1) 去掉会怎样?什么场景下 dot 会超出 [-1,1]? 重新理解浮点精度 + NaN传染
4 平均曲率的 1 - dot(n1, n2),如果 n1 和 n2 都不归一化,结果对吗? 重新理解法线归一化的前提
5 高斯曲率 > 0 但平均曲率 ≈ 0,这种形状存在吗? 重新理解两种曲率的独立性
6 厚度用9对顶点距离是保守估计,什么情况下会严重偏离真实壁厚? 想象两个三角形平行但不正对的情况
7 曲率计算的时间复杂度是多少?跟顶点数 N 有什么关系? O(N) — 每个顶点只访问自己的邻居
费曼学习法 Step 5:用你的话复述
试着这样解释:
“3D 模型是三角形网格,三角形内部是平的,曲率全部集中在顶点上。高斯曲率用角亏法算——把一个顶点周围所有三角形的内角加起来,跟 360° 比:不够就是凸的(球面),超了就是鞍面,刚好就是平面。平均曲率用法线差分——看顶点自己的法线跟邻居法线差多少,差得多就是急弯。两个曲率是独立维度,一起才能完整描述局部形状。壁厚靠光线追踪算——沿法线射出,命中对面的距离就是壁厚。所有这些都依赖第3课的哈希表来查邻域,这就是为什么我说数据结构是地基。”
📌 本课与你的 AI 时代理念的映射
你的理念 本课体现
1.2 技术是切入点 角亏法、法线差分是技术,但”为什么需要曲率”是业务——3D打印需要知道哪里凸哪里凹哪里薄
1.1 出题人 你定义了”曲率”这个度量——用什么公式、什么阈值、什么精度,都是出题人的决策
1.5 AI代码有坑 忘 clamp → NaN 传染整个流水线;法线不归一化 → 曲率值全错;AI 不会注意到这些边界
1.4 架构能力 曲率 = 数据流的枢纽,连接了底层(哈希表+BVH)和上层(聚类+分类),理解这层才算理解系统
1.6 软实力 向产品经理解释”为什么这个面打印会出问题”,你不需要讲角亏公式,而是说”这里弯得太急”——翻译能力就是软实力


