OBJ文件是纯文本格式,主体由v、vt、vn、f四类行构成;f行索引格式复杂(如f v/vt/vn、f v//vn等),且索引从1开始、支持负数(表示倒序),解析时需按/拆分字段、正确处理正负索引并统一减1转为0-based下标,同时注意v/vt/vn为独立列表、面需三角化、绕序默认逆时针。

OBJ文件结构简单,但顶点索引容易搞错
OBJ不是二进制格式,本质是纯文本,v、vt、vn、f 四类行构成主体。真正麻烦的是 f 行的索引格式:可能为 f v/vt/vn、f v//vn 或 f v/vt,且索引从 1 开始(不是 0)。直接用 std::stoi 解析字段却忽略斜杠分隔和空字段,就会崩溃或读出错位顶点。
- 逐行读取时跳过空行和以
#开头的注释行 - 遇到
f行,先按空格切分每个面(如"1/2/3 4/5/6 7/8/9"),再对每个顶点字段用/拆解,缺失项填 0(后续做容错) - 所有索引统一减 1 转为 C++ 数组下标,但要注意:OBJ 中
v、vt、vn是三组独立列表,索引不共享
用 std::vector 存顶点数据,别手写动态数组
OBJ 可能含上千个顶点,且纹理坐标和法线可选。用 std::vector<:array>></:array> 分别存 v、vt、vn 最稳妥——自动扩容、无需手动 new/delete,也避免越界访问。解析完再按面生成最终顶点流(即把每个 f 中的索引组合成带位置/UV/法线的完整顶点)。
-
v行解析:每行三个浮点数,用std::istringstream提取,忽略多余字段 -
vt行可能只有两个数(UV),也可能三个(UVW),取前两个即可 -
vn行必须是三个数,否则丢弃该法线(OBJ 允许缺失法线,但不能格式错误) - 最终三角面数据建议存为
std::vector<:array>></:array>(x,y,z,u,v,nx,ny,nz),方便后续传给 OpenGL/Vulkan
face 行解析失败的典型错误:没处理负索引
OBJ 支持负数索引,表示“从末尾往前数第 N 个”,比如 -1 是最后一个顶点。很多初学者只调 std::stoi 然后直接减 1,结果负数变 -2,越界访问。正确做法是:先判断符号,负索引转正需加对应列表 size()。
- 对每个索引字段(如
"-2"),先检查首字符是否为'-' - 若是,转正后用
list.size() + stoi(field)(注意:stoi("-2") 是 -2,所以加 size() 后是 size()-2) - 正索引则直接
stoi(field) - 1 - 所有索引必须在
[0, list.size())范围内,越界要跳过该面或打日志,别硬塞
加载后顶点顺序混乱?检查 face 的 winding order
OBJ 默认是右手法则、逆时针绕序(CCW),但有些建模工具导出时可能反了。如果渲染出来背面剔除导致模型消失,不是解析错了,而是面序颠倒。最简单验证方式:打印前几个 f 行对应的三个顶点坐标,肉眼观察是否按 CCW 排列;或者统一翻转面序(交换第二和第三个顶点索引)再试。
立即学习“C++免费学习笔记(深入)”;
- OpenGL 默认启用
glFrontFace(GL_CCW),所以保持原序即可 - Vulkan 需显式设置
frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,否则要翻转 - 若模型含凸多边形面(>3 顶点),必须三角化——OBJ 的
f行可能是f 1 2 3 4,需拆成 (1,2,3)、(1,3,4) 等扇形三角面
OBJ 文件看着简单,但索引偏移、负数、字段缺失、面序这四点,任何一个没兜住,加载出来的模型就歪、破或干脆黑屏。尤其负索引和三角化,很容易被忽略,等渲染出问题才回头查解析逻辑。


















