OBJ模型加载器
项目目标
实现一个简单的 OBJ 3D 模型加载器,支持解析 Wavefront OBJ 格式文件,提取顶点和面信息,并使用线框渲染显示模型。
实现过程
核心功能
- OBJ格式解析:支持顶点(v)和面(f)的解析
- 多种面格式支持:
f v1 v2 v3 和 f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3
- 自动缩放和居中:根据模型边界自动调整显示
- 线框渲染:使用 Bresenham 算法绘制三角形边缘
迭代历史
- Iteration 1: 初始实现,一次性成功
- 完整的OBJ解析器(支持多种格式)
- 自动边界计算和缩放
- 线框渲染输出
- 验证: 量化验证通过
- 图片尺寸:800x600 ✅
- 立方体线框:1199像素 ✅
- 位置居中:(400, 300) ✅
核心代码
1. OBJ格式解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| int parseVertexIndex(const std::string& token) { size_t pos = token.find('/'); std::string indexStr = (pos == std::string::npos) ? token : token.substr(0, pos); int index = std::stoi(indexStr); return index - 1; }
bool load(const std::string& filename) { std::ifstream file(filename); std::string line; while (std::getline(file, line)) { std::istringstream iss(line); std::string prefix; iss >> prefix; if (prefix == "v") { float x, y, z; iss >> x >> y >> z; vertices.push_back(Vec3(x, y, z)); } else if (prefix == "f") { std::string v1, v2, v3; iss >> v1 >> v2 >> v3; int idx1 = parseVertexIndex(v1); int idx2 = parseVertexIndex(v2); int idx3 = parseVertexIndex(v3); faces.push_back(Triangle(idx1, idx2, idx3)); } } return true; }
|
2. 自动缩放和居中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Vec3 minBound(1e10, 1e10, 1e10); Vec3 maxBound(-1e10, -1e10, -1e10); for (const auto& v : model.vertices) { minBound.x = std::min(minBound.x, v.x); minBound.y = std::min(minBound.y, v.y); minBound.z = std::min(minBound.z, v.z); maxBound.x = std::max(maxBound.x, v.x); maxBound.y = std::max(maxBound.y, v.y); maxBound.z = std::max(maxBound.z, v.z); }
Vec3 center = (minBound + maxBound) * 0.5f; Vec3 size = maxBound - minBound; float scale = std::min(width, height) * 0.4f / std::max(std::max(size.x, size.y), size.z);
|
3. 正交投影
1 2 3 4 5 6
| auto project = [&](const Vec3& v) -> std::pair<int, int> { float x = (v.x - center.x) * scale + width / 2; float y = (v.y - center.y) * scale + height / 2; return {(int)x, (int)y}; };
|
4. 线框渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| for (const auto& tri : model.faces) { Vec3 v0 = model.vertices[tri.v0]; Vec3 v1 = model.vertices[tri.v1]; Vec3 v2 = model.vertices[tri.v2]; auto p0 = project(v0); auto p1 = project(v1); auto p2 = project(v2); drawLine(p0.first, p0.second, p1.first, p1.second); drawLine(p1.first, p1.second, p2.first, p2.second); drawLine(p2.first, p2.second, p0.first, p0.second); }
|
运行结果

验证结果:
- ✅ 图片尺寸:800x600
- ✅ 白色背景比例:99.75%
- ✅ 立方体线框像素数:1199
- ✅ 立方体中心位置:(400, 300)
- ✅ 立方体尺寸:240x240 像素
技术总结
学到的技术点
OBJ格式理解
- 顶点:
v x y z
- 面:
f v1 v2 v3 或 f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3
- 索引从1开始(需要转换)
边界盒计算
- 遍历所有顶点求 min/max
- 计算中心点和尺寸
- 根据屏幕尺寸自动缩放
正交投影
- 简单的2D投影(忽略Z轴)
- 适用于线框渲染
- 后续可升级为透视投影
线框渲染
- 遍历所有三角形
- 使用 Bresenham 算法绘制边
- 轻量级的3D模型可视化方法
未来改进方向
- 法线和纹理坐标:支持
vn 和 vt 解析
- 实体渲染:实现三角形填充(光栅化)
- 光照模型:添加 Phong/Blinn-Phong 光照
- 透视投影:实现真实的3D透视效果
- 复杂模型:支持加载外部OBJ文件(如Stanford Bunny)
- 旋转动画:添加交互式模型旋转
代码仓库
GitHub: daily-coding-practice/2026/02/02-25-OBJ-Model-Loader
完成时间: 2026-02-25 05:32
迭代次数: 1 次(一次成功)
编译器: g++ 12.3.1
总用时: ~2 分钟