请先看这两个中文博客中对于obj的介绍:
更为详细的英文资料(用google或者aol搜索 "obj format"即可得到):
http://en.wikipedia.org/wiki/Wavefront_.obj_file
Wavefront OBJ File Format Summary
最详细的资料 obj spec: http://www.martinreddy.net/gfx/3d/OBJ.spec
http://people.cs.clemson.edu/~dhouse/courses/405/docs/brief-obj-file-format.html
http://www.scratchapixel.com/lessons/3d-advanced-lessons/obj-file-format/obj-file-format/
看完以上随便一个内容,obj的格式可以说了解了。下面实现对obj文件的读取。
.obj文件中,每一行都有表明该行意义的标志符。对obj的读取中,处理以下标志符
"v"--点的坐标,三维模型为x, y, z的顺序;程序中以
1 typedef struct ObjVector3 2 { 3 ObjFloat x; 4 ObjFloat y; 5 ObjFloat z; 6 } ObjVector3;
结构存储。
"vt"--纹理坐标,程序中以
1 typedef struct ObjVector2 2 { 3 ObjFloat x; 4 ObjFloat y; 5 } ObjVector2;
结构存储
"vn"--法向量坐标,程序中与“v”的存储结构相同
"f"--面所用到的点坐标/纹理坐标/法向量坐标的索引,
"mtllib"--.obj文件用到的material库文件,“usemtl”标志符用到的material都是从material库文件中取出的
"g"--组group的名称,group里面的"f"可以使用0-N个"usemtl"对面的显示渲染进行控制,当使用了大于1个“usemtl”标志符,程序处理时对于已经读取的"f"很难控制;同时查看obj读取的源码,有的用到了这个标志符,有的没有使用该标志,有的使用了"usemtl"标志符对所读取的"f"面进行分割,本文的处理是使用"usemtl"标志符将"f"面分割为mesh,但是考虑到不同的group可以使用相同的"usemtl”标志(即不同的group都使用了 usemtl AAA),因此将"g"与"usemtl"结合起来,二者的名称作为mesh的名称
"usemtl"--参见"mtllib","g"。一旦使用了该标志符,则在该标志符后面的"f"全部受影响,直到遇到下一个"usemtl"
mesh结构,解析已经读取的“f”存储所面的点坐标/纹理坐标/法向量坐标,其结构为
1 struct Mesh 2 { 3 string name; // name 4 ObjIntd mtl_idx; // the index of material used by mesh 5 6 vector<ObjVector3> positions; // "v" flag 7 vector<ObjVector2> texcoords; // "vt" flag 8 vector<ObjVector3> normals; // "vn" flag 9 vector<ObjDword> indices; // the indices of points of face in positions 10 11 void reset() 12 { 13 mtl_idx = -1; 14 15 name.clear(); 16 positions.clear(); 17 texcoords.clear(); 18 normals.clear(); 19 indices.clear(); 20 } 21 };
结构中出现的
ObjFloat ObjIntd
是一组typedef,具体为
1 typedef int32_t ObjBool; 2 typedef uint8_t ObjByte; 3 typedef uint16_t ObjWord; 4 typedef uint32_t ObjDword; 5 typedef int8_t ObjIntb; 6 typedef int16_t ObjIntw; 7 typedef int32_t ObjIntd; 8 9 typedef float ObjFloat; 10 typedef double ObjDouble;
对obj文件读取的主体程序如下
1 ObjBool obj_file_load(const string& objname) 2 { 3 if (objname.empty()) 4 return LIBOBJ_FALSE; 5 6 int mesh_count = 0; 7 Mesh mesh_; 8 string groupname; 9 string usemtl; 10 vector<ObjVector3> positions; 11 vector<ObjVector3> normals; 12 vector<ObjVector2> texcoords; 13 vector<vector<ObjVertexIndex> > faces; 14 map<string, ObjIntd> mtlMap; 15 16 fstream obj_stream; 17 obj_stream.open(objname.c_str(), std::ios_base::in); 18 if (!obj_stream.is_open()) 19 return LIBOBJ_FALSE; 20 21 string obj_flag; 22 while (obj_stream.good()) 23 { 24 obj_stream >> obj_flag; 25 26 if (obj_flag.empty() || (obj_flag[0] == \'#\')) 27 { 28 obj_stream.ignore(1024, \'\n\'); // skip line 29 continue; 30 } 31 32 if (obj_flag == "v") // position flag 33 if (!obj_parse_vector3(obj_stream, positions)) 34 return LIBOBJ_FALSE; 35 else if (obj_flag == "vt") // texture coordinate flag 36 if (!obj_parse_vector2(obj_stream, texcoords)) 37 return LIBOBJ_FALSE; 38 else if (obj_flag == "vn") // normal flag 39 if (!obj_parse_vector3(obj_stream, normals)) 40 return LIBOBJ_FALSE; 41 else if (obj_flag == "g") //group name 42 if (!obj_parse_group(obj_stream, groupname)) 43 return LIBOBJ_FALSE; 44 else if (obj_flag == "f") // face flag 45 { 46 vector<ObjVertexIndex> face; 47 48 obj_parse_face(obj_stream, face, positions, texcoords, normals); 49 50 faces.push_back(face); 51 } 52 else if (obj_flag == "mtllib") // Material library 53 { 54 vector<string> materialnames; 55 56 obj_parse_mtllib(obj_stream, materialnames); 57 58 for (unsigned i = 0; i < materialnames.size(); i++) 59 if (!mtl_file_load(get_file_path(objname) + \'/\' + materialnames[i])) 60 return LIBOBJ_FALSE; 61 } 62 else if (obj_flag == "usemtl") 63 { 64 if (obj_save_mesh(positions, texcoords, normals, faces, groupname+usemtl, mesh_)) 65 { 66 m_meshes.back().mtl_idx = mtl_index(usemtl); 67 68 faces.clear(); 69 mesh_.reset(); 70 } 71 72 obj_parse_group(obj_stream, usemtl); 73 } 74 else 75 { 76 obj_stream.ignore(1024, \'\n\'); // skip line 77 } 78 } 79 80 if (!obj_stream.eof()) 81 return LIBOBJ_FALSE; 82 83 //save mesh data 84 if (obj_save_mesh(positions, texcoords, normals, faces, groupname+usemtl, mesh_)) 85 { 86 m_meshes.back().mtl_idx = mtl_index(usemtl); 87 88 faces.clear(); 89 mesh_.reset(); 90 } 91 92 obj_stream.close(); 93 94 return LIBOBJ_TRUE; 95 }
obj文件用到了.mtl格式的material,在对“mtllib”标志符解析时开始读取该.mtl文件,
对于.mtl文件的介绍,请阅读
http://www.fileformat.info/format/material/
http://people.cs.clemson.edu/~dhouse/courses/405/docs/brief-mtl-file-format.html
本程序中存储mtl文件的结构较为简单,只使用了mtl文件中的几个结构而没有全部处理
1 struct ObjMaterial 2 { 3 string name; 4 5 ObjRgb ambient; 6 ObjRgb diffuse; 7 ObjRgb specular; 8 9 ObjWord shininess; 10 ObjByte illumination_model; 11 ObjFloat transparency; 12 13 string ambient_texture; 14 string diffuse_texture; 15 string specular_texture; 16 };
对mtl文件的解析代码:
1 ObjBool mtl_file_load(const string& mtlname) 2 { 3 if (mtlname.empty()) // if obj has no material 4 return LIBOBJ_FALSE; 5 6 ObjMaterial* pmaterial = NULL; 7 8 fstream mtlstream; 9 mtlstream.open(mtlname.c_str(), std::ios_base::in); //open strFileName file 10 if (!mtlstream.is_open()) 11 return LIBOBJ_FALSE; 12 13 string mtlflag; 14 while (mtlstream.good()) 15 { 16 mtlstream >> mtlflag; 17 18 if (mtlflag == "newmtl") 19 { 20 string materialname; 21 mtlstream >> materialname; 22 if (mtl_index(materialname) != -1) 23 continue; 24 25 pmaterial = new ObjMaterial; 26 pmaterial->name = materialname; 27 m_materials.push_back(pmaterial); 28 } 29 else if (mtlflag == "Ka") //Ambient color 30 if (!mtl_parse_color(mtlstream, pmaterial->ambient)) 31 return LIBOBJ_FALSE; 32 else if (mtlflag == "Kd") //Diffuse color 33 if (!mtl_parse_color(mtlstream, pmaterial->diffuse)) 34 return LIBOBJ_FALSE; 35 else if (mtlflag == "Ks") //Specular color 36 if (!mtl_parse_color(mtlstream, pmaterial->specular)) 37 return LIBOBJ_FALSE; 38 else if (mtlflag == "Tr"/*"d"*/) // Alpha, that is transparent 39 if (!mtl_parse_light(mtlstream, pmaterial->transparency)) 40 return LIBOBJ_FALSE; 41 else if (mtlflag == "Ns") //Shininess 42 if (!mtl_parse_light(mtlstream, pmaterial->shininess)) 43 return LIBOBJ_FALSE; 44 else if (mtlflag == "illum") //Illumination type 45 if (!mtl_parse_light(mtlstream, pmaterial->illumination_model)) 46 return LIBOBJ_FALSE; 47 else if (mtlflag == "map_Ka") //ambient Texture 48 if (!mtl_parse_texture(mtlstream, pmaterial->ambient_texture)) 49 return LIBOBJ_FALSE; 50 else if (mtlflag == "map_Kd") //diffuse Texture 51 if (!mtl_parse_texture(mtlstream, pmaterial->diffuse_texture)) 52 return LIBOBJ_FALSE; 53 else if (mtlflag == "map_Ks") //specular Texture 54 if (!mtl_parse_texture(mtlstream, pmaterial->specular_texture)) 55 return LIBOBJ_FALSE; 56 else 57 mtlstream.ignore(1024, \'\n\'); // ignore this line, on the assumption that the max characters at this line is 1024. 58 } 59 60 if (!mtlstream.eof()) 61 return LIBOBJ_FALSE; 62 63 mtlstream.close(); 64 return LIBOBJ_TRUE; 65 }
完整的代码放在了https://github.com/priseup/obj_load
ps: 手上没有数据,还没有进行测试