Files
ViewDesignEngine/docs/tutorial/02-sdf-modeling.md
T
2026-07-24 12:42:49 +00:00

334 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SDF 隐式建模教程
有符号距离函数(Signed Distance Function)是 ViewDesignEngine 的核心建模方式之一。
通过数学函数直接定义几何体,无需依赖网格或曲面表示。
## 核心概念
SDF 函数 `f(p)` 返回点 `p` 到最近表面的有符号距离:
- `f(p) < 0`:点在几何体**内部**
- `f(p) = 0`:点在几何体**表面**上
- `f(p) > 0`:点在几何体**外部**
## 基本图元(球、盒、环)
ViewDesignEngine 提供的内建 SDF 图元,定义在 `vde/sdf/sdf_primitives.h`
```cpp
#include <vde/sdf/sdf_primitives.h>
using namespace vde::sdf;
using namespace vde::core;
// 球体:位于原点,半径 1.0
double d = sphere(Point3D(1.0, 0.0, 0.0), 1.0);
// d ≈ 0.0(点在球面上)
// 轴对齐盒子:以原点为中心,半长为 halfExtent
auto box_fn = [](double x, double y, double z) {
return box(Point3D(x, y, z), Point3D(1.5, 1.5, 0.5));
};
// 环面(torus):major_radius = 环半径,minor_radius = 管半径
auto torus_fn = [](double x, double y, double z) {
return torus(Point3D(x, y, z), 2.0, 0.4);
};
// 椭球:指定各轴半径
auto ellipsoid_fn = [](double x, double y, double z) {
return ellipsoid(Point3D(x, y, z), Point3D(2.0, 1.0, 1.5));
};
// 圆柱(沿 Y 轴)
auto cylinder_fn = [](double x, double y, double z) {
return cylinder(Point3D(x, y, z), 0.5, 3.0);
};
// 胶囊体:两点 + 半径
auto capsule_fn = [](double x, double y, double z) {
return capsule(Point3D(x, y, z),
Point3D(0, -2, 0), Point3D(0, 2, 0), 0.4);
};
// 无限长圆柱:沿任意轴
auto inf_cyl = [](double x, double y, double z) {
return infinite_cylinder(Point3D(x, y, z),
Vector3D(0, 0, 1), 0.5);
};
```
## CSG 操作(并、交、差、光滑并集)
布尔组合运算定义在 `vde/sdf/sdf_operations.h`
```cpp
#include <vde/sdf/sdf_primitives.h>
#include <vde/sdf/sdf_operations.h>
using namespace vde::sdf;
// === 标准布尔运算 ===
// 并集 A ∪ B:取最小值(两个几何体的合并)
auto union_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
return op_union(sphere(p, 1.0),
box(p, Point3D(0.8, 0.8, 0.8)));
};
// 交集 A ∩ B:取最大值(两个几何体的共同区域)
auto intersect_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
return op_intersection(sphere(p, 1.0),
box(p, Point3D(0.6, 0.6, 0.6)));
};
// 差集 A \ B:从 A 中减去 B
auto diff_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
return op_difference(sphere(p, 1.0),
box(p, Point3D(0.4, 0.4, 0.4)));
};
// === 光滑布尔运算(带有机融合过渡) ===
// smooth unionk 控制过渡弧度
auto smooth_union_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
double d1 = sphere(p, 1.0);
double d2 = sphere(p - Point3D(1.5, 0, 0), 1.0);
return op_smooth_union(d1, d2, 0.3); // k=0.3 适中的融合
};
// smooth intersection
auto smooth_isect_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
double d1 = sphere(p, 1.0);
double d2 = box(p, Point3D(0.8, 0.8, 0.8));
return op_smooth_intersection(d1, d2, 0.2);
};
// smooth difference
auto smooth_diff_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
double d1 = box(p, Point3D(1.5, 1.5, 1.5));
double d2 = sphere(p, 0.8);
return op_smooth_difference(d1, d2, 0.2);
};
```
## 域变形(重复、镜像、扭转)
域变形算子对采样点做空间变换,再传入 SDF 函数求值:
```cpp
#include <vde/sdf/sdf_operations.h>
using namespace vde::sdf;
// 无限重复:将空间单元化,在每个单元内放置几何体
auto repeated_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
// 以 (2, 2, 2) 为周期重复
Point3D q = op_repeat(p, Point3D(2.0, 2.0, 2.0));
return sphere(q, 0.5);
};
// 镜像:沿 YZ 平面对称(x 方向镜像,offset 为对称轴位置)
auto mirrored_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
Point3D q = op_mirror_x(p, 0.0); // 以 x=0 为镜像轴
return sphere(q - Point3D(1.5, 0, 0), 0.6);
};
// 多维镜像组合
auto quad_mirror = [](double x, double y, double z) {
Point3D p(x, y, z);
Point3D q = op_mirror_x(op_mirror_y(p));
return sphere(q - Point3D(2.0, 2.0, 0), 0.5);
};
// 平移 / 旋转 / 缩放
auto transformed_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
// sdf_operations.h 提供 op_translate, op_rotate, op_scale 等
// 注意:是逆变换——要移动几何体,需反向移动采样点
Point3D q = op_translate(p, Point3D(2.0, 0, 0)); // 几何体向右移 2
return sphere(q, 1.0);
};
// 扭转(沿 Y 轴)
#include <cmath>
auto twisted_shape = [](double x, double y, double z) -> double {
double angle = y * 0.5; // 扭转角度与 y 坐标成正比
double sx = std::sin(angle);
double cx = std::cos(angle);
double rx = cx * x - sx * z;
double rz = sx * x + cx * z;
Point3D q(rx, y, rz);
return box(q, Point3D(0.8, 3.0, 0.3));
};
// 修饰算子:圆角、壳
auto decorated_shape = [](double x, double y, double z) {
Point3D p(x, y, z);
double d = box(p, Point3D(1.0, 1.0, 1.0));
// op_round: 对所有边做圆角(近似 round_box)
d = op_round(d, 0.1);
// op_onion: 创建壳层(空心厚度)
// d = op_onion(d, 0.05);
return d;
};
```
## 从 SDF 到网格(Marching Cubes
```cpp
#include <vde/sdf/sdf_primitives.h>
#include <vde/sdf/sdf_operations.h>
#include <vde/mesh/marching_cubes.h>
#include <vde/mesh/halfedge_mesh.h>
#include <vde/foundation/io_gltf.h>
using namespace vde::core;
using namespace vde::sdf;
using namespace vde::mesh;
using namespace vde::foundation;
int main() {
// 定义 SDF 函数
auto shape = [](double x, double y, double z) -> double {
Point3D p(x, y, z);
double d = box(p, Point3D(2.0, 2.0, 2.0));
d = op_round(d, 0.15); // 圆角
return d;
};
// 采样区域:[-3,3]³
Point3D bmin(-3, -3, -3), bmax(3, 3, 3);
// Marching Cubes64³ 分辨率
auto mc = marching_cubes(shape, 0.0, bmin, bmax, 64);
// 构建半边网格并导出
HalfedgeMesh mesh;
mesh.build_from_triangles(mc.vertices, mc.triangles);
GltfOptions opts;
opts.binary = true;
opts.include_normals = true;
write_gltf("rounded_box.glb", mesh, opts);
std::cout << "Vertices: " << mesh.num_vertices()
<< ", Faces: " << mesh.num_faces() << "\n";
return 0;
}
```
**分辨率选择指南:**
| 分辨率 | 适用场景 | 输出网格规模 |
|--------|----------|-------------|
| 32³ | 快速预览 | ~2K 顶点 |
| 64³ | 一般用途 | ~8K 顶点 |
| 128³ | 精细表面 | ~30K 顶点 |
| 256³ | 高精度需求 | ~100K+ 顶点 |
## 完整示例:齿轮形状
```cpp
#include <vde/sdf/sdf_primitives.h>
#include <vde/sdf/sdf_operations.h>
#include <vde/mesh/marching_cubes.h>
#include <vde/mesh/halfedge_mesh.h>
#include <vde/foundation/io_gltf.h>
#include <iostream>
#include <cmath>
using namespace vde::core;
using namespace vde::sdf;
using namespace vde::mesh;
using namespace vde::foundation;
int main() {
const double M_PI = 3.14159265358979323846;
const double gear_radius = 2.0; // 齿轮半径
const double tooth_count = 12.0; // 齿数
const double tooth_depth = 0.3; // 齿深
const double gear_thickness = 0.5; // 齿轮厚度
auto gear_sdf = [&](double x, double y, double z) -> double {
Point3D p(x, y, z);
// 圆柱主体(沿 Y 轴的薄圆盘)
double d_cylinder = cylinder(Point3D(x, y, z), gear_radius, gear_thickness);
// 中心孔
double d_hole = cylinder(Point3D(x, y, z), 0.4, gear_thickness * 2);
// 差集:打孔
double d = op_difference(d_cylinder, d_hole);
// 齿轮齿:沿圆周等距分布
double angle = std::atan2(z, x);
double radius = std::sqrt(x * x + z * z);
// 每个齿是一个小 Box 沿径向放置
double teeth_union = 1e10;
for (int i = 0; i < tooth_count; i++) {
double theta = i * 2.0 * M_PI / tooth_count;
double ca = std::cos(theta);
double sa = std::sin(theta);
// 旋转采样点到该齿的局部空间
double lx = ca * x + sa * z;
double lz = -sa * x + ca * z;
Point3D lp(lx, y, lz);
double tooth = box(lp,
Point3D(0.3, 0.6, gear_radius + tooth_depth));
// 向圆心方向偏移
double d_tooth = op_translate_along_x(lp,
gear_radius + tooth_depth * 0.5).norm() > 0
? box(Point3D(lx - (gear_radius + tooth_depth * 0.5), y, lz),
Point3D(0.15, gear_thickness * 1.1, tooth_depth * 0.5))
: 1e10;
// 用旋转矩阵的逆变换做齿的径向放置
double r = std::sqrt(x * x + z * z);
double dx = r - (gear_radius + tooth_depth * 0.5);
double dy_abs = std::abs(y);
double dz = std::abs(std::atan2(z, x) - theta);
if (dz > M_PI) dz = 2 * M_PI - dz;
dz = dz * gear_radius;
double tooth_dist = std::sqrt(dx * dx + dy_abs * dy_abs + dz * dz)
- tooth_depth * 0.5;
if (dy_abs <= gear_thickness * 0.55 && r >= gear_radius - 0.05) {
teeth_union = std::min(teeth_union, tooth_dist);
}
}
d = op_union(d, teeth_union);
return d;
};
// Marching Cubes
Point3D bmin(-3, -3, -3), bmax(3, 3, 3);
auto mc = marching_cubes(gear_sdf, 0.0, bmin, bmax, 128);
HalfedgeMesh mesh;
mesh.build_from_triangles(mc.vertices, mc.triangles);
GltfOptions opts;
opts.binary = true;
opts.include_normals = true;
write_gltf("gear.glb", mesh, opts);
std::cout << "Gear exported: " << mesh.num_vertices()
<< " vertices, " << mesh.num_faces() << " faces\n";
return 0;
}
```
编译运行,用 `viewer/index.html` 查看效果。