




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第C++解析obj模型文件方法介绍目录一、前言二、中间文件三、使用四、完整代码
一、前言
tinyobjloader地址:
传送门
而tinyobjloader库只有一个头文件,可以很方便的读取obj文件。支持材质,不过不支持骨骼动画,vulkan官方教程便是使用的它。不过没有骨骼动画还是有很大的局限性,这里只是分享一下怎么读取材质和拆分网格。
二、中间文件
我抽象了一个ModelObject类表示模型数据,而一个ModelObject包含多个Sub模型,每个Sub模型使用同一材质(有的人称为图元Primitive或DrawCall)。最后我将其保存为文件,这样我的引擎便可直接解析ModelObject文件,而不是再去读obj、fbx等其他文件了。
这一节可以跳过,下一节是真正使用tinyobjloader库。
//一个文件会有多个ModelObject,一个ModelObject根据材质分为多个ModelSub
//注意ModelSub为一个材质,需要读取时合并网格
classModelObject
friendclassVK;
public:
//从源文件加载模型
staticvectorModelObject*Create(string_viewpath_name);
voidLoad(string_viewpath_name);
//保存到文件
voidSaveToFile(string_viewpath_name);
private:
vectorModelObjectSub_allSub;//下标减1为材质,0为没有材质
vectorVertex_allVertex;//顶点缓存
vectoruint32_t_allIndex;//索引缓存
vectorModelObjectMaterial_allMaterial;//所有材质
//------------------不同格式加载实现--------------------------------
//obj
staticvectorModelObject*_load_obj(string_viewpath_name);
staticvectorModelObject*_load_obj_2(string_viewpath_name);
};
ModelObjectSub只是表示在索引缓存的一段范围:
//模型三角形范围
structModelTriangleRange
ModelTriangleRange():
_countTriangle{0},
_offsetIndex{0}
size_t_countTriangle;
size_t_offsetIndex;
//子模型对象范围
structModelObjectSub
ModelTriangleRange_range;
};
而ModelObjectMaterial表示模型材质:
//!材质
structMaterial
glm::vec4_diffuseAlbedo;//漫反射率
glm::vec3_fresnelR0;//菲涅耳系数
float_roughness;//粗糙度
//模型对象材质
structModelObjectMaterial
//最后转为Model时,变为可以用的着色器资源
Material_material;
string_materialName;
//路径为空,则表示没有(VK加载时会返回0)
string_pathTexDiffuse;
string_pathTexNormal;
};
三、使用
首先引入头文件:
#defineTINYOBJLOADER_IMPLEMENTATION
#includetiny_obj_loader.h
接口原型,将obj文件变为多个ModelObject:
vectorModelObject*ModelObject::_load_obj_2(string_viewpath_name);
取得文件名,和文件所在路径(会自动加载路径下的同名mtl文件,里面包含了材质):
stringstr_path=string{path_name};
stringstr_base=String::EraseFilename(path_name);
constchar*filename=str_path.c_str();
constchar*basepath=str_base.c_str();
基本数据:
debug(format("开始加载obj文件:{},{}",filename,basepath));
booltriangulate=true;//三角化
tinyobj::attrib_tattrib;//所有的数据放在这里
std::vectortinyobj::shape_tshapes;//子模型
std::vectortinyobj::material_tmaterials;//材质
std::stringwarn;
std::stringerr;
加载并打印一些信息:
boolb_read=tinyobj::LoadObj(attrib,shapes,materials,warn,err,filename,
basepath,triangulate);
//打印错误
if(!warn.empty())
debug_warn(warn);
if(!err.empty())
debug_err(err);
if(!b_read)
debug_err(format("读取obj文件失败:{}",path_name));
return{};
debug(format("顶点数:{}",attrib.vertices.size()/3));
debug(format("法线数:{}",attrib.normals.size()/3));
debug(format("UV数:{}",attrib.texcoords.size()/2));
debug(format("子模型数:{}",shapes.size()));
debug(format("材质数:{}",materials.size()));
这将打印以下数据:
由于obj文件只产生一个ModelObject,我们如下添加一个,并返回顶点、索引、材质等引用,用于后面填充:
//obj只有一个ModelObject
vectorModelObject*ret;
ModelObject*model_object=newModelObject;
std::vectorVertexmo_vertices=model_object-_allVertex;
std::vectoruint32_tmo_indices=model_object-_allIndex;
vectorModelObjectMaterialmo_material=model_object-_allMaterial;
ret.push_back(model_object);
首先记录材质信息:
//------------------获取材质-------------------
mo_material.resize(materials.size());
for(size_ti=0;imaterials.size();++i)
tinyobj::material_tm=materials[i];
debug(format("材质:{},{}",i,));
ModelObjectMaterialmaterial=model_object-_allMaterial[i];
material._materialName=;
material._material._diffuseAlbedo={m.diffuse[0],m.diffuse[1],m.diffuse[2],1.0f};
material._material._fresnelR0={m.specular[0],m.specular[1],m.specular[2]};
material._material._roughness=ShininessToRoughness(m.shininess);
if(!m.diffuse_texname.empty())
material._pathTexDiffuse=str_base+m.diffuse_texname;
if(!m.normal_texname.empty())
material._pathTexNormal=str_base+m.normal_texname;
}
这将产生以下输出:
然后遍历shape,按材质记录顶点。这里需要注意的是,一个obj文件有多个shape,每个shape由n个三角面组成。而每个三角形拥有独立的材质编号,所以这里按材质分别记录,而不是一般的合并为整体:
//------------------获取模型-------------------
//按材质放入面的顶点
vectorvectortinyobj::index_tall_sub;
all_sub.resize(1+materials.size());//0为默认
for(size_ti=0;ishapes.size();i++)
{//每一个子shape
tinyobj::shape_tshape=shapes[i];
size_tnum_index=shape.mesh.indices.size();
size_tnum_face=shape.mesh.num_face_vertices.size();
debug(format("读取子模型:{},{}",i,));
debug(format("索引数:{};面数:{}",num_index,num_face));
//当前mesh下标(每个面递增3)
size_tindex_offset=0;
//每一个面
for(size_tj=0;jnum_face;++j)
intindex_mat=shape.mesh.material_ids[j];//每个面的材质
vectortinyobj::index_tsub_idx=all_sub[1+index_mat];
sub_idx.push_back(shape.mesh.indices[index_offset++]);
sub_idx.push_back(shape.mesh.indices[index_offset++]);
sub_idx.push_back(shape.mesh.indices[index_offset++]);
}
按材质记录顶点的索引(tinyobj::index_t)后,接下来就是读取顶点的实际数据,并防止重复读取:
//生成子模型,并填入顶点
std::unordered_maptinyobj::index_t,size_t,hash_idx,equal_idx
uniqueVertices;//避免重复插入顶点
size_ti=0;
for(vectortinyobj::index_tsub_idx:all_sub)
ModelObjectSubsub;
sub._range._offsetIndex=i;
sub._range._countTriangle=sub_idx.size()/3;
model_object-_allSub.push_back(sub);
for(tinyobj::index_tidx:sub_idx)
autoiter=uniqueVertices.find(idx);
if(iter==uniqueVertices.end())
Vertexv;
v._pos[0]=attrib.vertices[idx.vertex_index*3+0];
v._pos[1]=attrib.vertices[idx.vertex_index*3+1];
v._pos[2]=attrib.vertices[idx.vertex_index*3+2];
//vt
v._texCoord[0]=attrib.texcoords[idx.texcoord_index*2+0];
v._texCoord[1]=attrib.texcoords[idx.texcoord_index*2+1];
v._texCoord[1]=1.0f-v._texCoord[1];
uniqueVertices[idx]=mo_vertices.size();
mo_indices.push_back((uint32_t)mo_vertices.size());
mo_vertices.push_back(v);
else
mo_indices.push_back((uint32_t)iter-second);
++i;
debug(format("解析obj模型完成:v{},i{}",mo_vertices.size(),mo_indices.size()));
returnret;
上面用到的哈希函数:
structequal_idx
booloperator()(consttinyobj::index_ta,consttinyobj::index_tb)const
returna.vertex_index==b.vertex_index
a.texcoord_index==b.texcoord_index
a.normal_index==b.normal_index;
structhash_idx
size_toperator()(consttinyobj::index_ta)const
return((a.vertex_index
^a.texcoord_index1)1)
^(a.normal_index1);
};
最后打印出来的数据如下:
对于材质的处理,漫反射贴图即是基本贴图,而法线(凹凸)贴图、漫反射率、菲涅耳系数、光滑度等需要渲染管线支持并与光照计算产生效果。
四、完整代码
可以此处获取最新的源码(我会改用Assimp,并添加骨骼动画、Blinn-Phong光照模型),也可以用后面的:传送门
如果有用,欢迎点赞、收藏、关注,我将更新更多C++相关的文章。
#defineTINYOBJLOADER_IMPLEMENTATION
#includetiny_obj_loader.h
structequal_idx
booloperator()(consttinyobj::index_ta,consttinyobj::index_tb)const
returna.vertex_index==b.vertex_index
a.texcoord_index==b.texcoord_index
a.normal_index==b.normal_index;
structhash_idx
size_toperator()(consttinyobj::index_ta)const
return((a.vertex_index
^a.texcoord_index1)1)
^(a.normal_index1);
floatShininessToRoughness(floatYpoint)
floata=-1;
floatb=2;
floatc;
c=(Ypoint/100)-1;
floatD;
D=b*b-(4*a*c);
floatx1;
x1=(-b+sqrt(D))/(2*a);
returnx1;
vectorModelObject*ModelObject::_load_obj_2(string_viewpath_name)
stringstr_path=string{path_name};
stringstr_base=String::EraseFilename(path_name);
constchar*filename=str_path.c_str();
constchar*basepath=str_base.c_str();
booltriangulate=true;
debug(format("开始加载obj文件:{},{}",filename,basepath));
tinyobj::attrib_tattrib;//所有的数据放在这里
std::vectortinyobj::shape_tshapes;//子模型
std::vectortinyobj::material_tmaterials;
std::stringwarn;
std::stringerr;
boolb_read=tinyobj::LoadObj(attrib,shapes,materials,warn,err,filename,
basepath,triangulate);
//打印错误
if(!warn.empty())
debug_warn(warn);
if(!err.empty())
debug_err(err);
if(!b_read)
debug_err(format("读取obj文件失败:{}",path_name));
return{};
debug(format("顶点数:{}",attrib.vertices.size()/3));
debug(format("法线数:{}",attrib.normals.size()/3));
debug(format("UV数:{}",attrib.texcoords.size()/2));
debug(format("子模型数:{}",shapes.size()));
debug(format("材质数:{}",materials.size()));
//obj只有一个ModelObject
vectorModelObject*ret;
ModelObject*model_object=newModelObject;
std::vectorVertexmo_vertices=model_object-_allVertex;
std::vectoruint32_tmo_indices=model_object-_allIndex;
vectorModelObjectMaterialmo_material=model_object-_allMaterial;
ret.push_back(model_object);
//------------------获取材质-------------------
mo_material.resize(materials.size());
for(size_ti=0;imaterials.size();++i)
tinyobj::material_tm=materials[i];
debug(format("材质:{},{}",i,));
ModelObjectMaterialmaterial=model_object-_allMaterial[i];
material._materialName=;
material._material._diffuseAlbedo={m.diffuse[0],m.diffuse[1],m.diffuse[2],1.0f};
material._material._fresnelR0={m.specular[0],m.specular[1],m.specular[2]};
material._material._roughness=ShininessToRoughness(m.shininess);
if(!m.diffuse_texname.empty())
material._pathTexDiffuse=str_base+m.diffuse_texname;
if(!m.normal_texname.empty())//注意这里凹凸贴图(bump_texname)更常见
material._pathTexNormal=str_base+m.normal_texname;
//------------------获取模型-------------------
//按材质放入面的顶点
vectorvectortinyobj::index_tall_sub;
all_sub.resize(1+materials.size());//0为默认
for(size_ti=0;ishapes.size();i++)
{//每一个子shape
tinyobj::shape_tshape=shapes[i];
size_tnum_index=shape.mesh.indices.size();
size_tnum_face=shape.mesh.num_face_vertices.size();
debug(format("读取子模型:{},{}",i,));
debug(format("索引数:{};面数:{}",num_index,num_face));
//当前mesh下标(每个面递增3)
size_tindex_offset=0;
//每一个面
for(size_tj=0;jnum_face;++j)
intindex_mat=shape.mesh.material_ids[j];//每个面的材质
vectortinyobj::index_tsub_idx=all_sub[1+index_mat];
sub_idx.push_back(shape.mesh.indices[index_offset++]);
sub_idx.push_back(shape.mesh.indices[index_offset++]);
sub_idx.push_back(shape.
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 美团外卖商家订单分成合同
- 直播活动内容补充与品牌合作协议
- 软性材料研发与市场推广合伙协议
- 网络文学有声书制作与环保公益活动合作协议
- 影视作品版权购买与版权收益分成合同
- 顶级域名所有权及商业价值转让服务合同
- 影视特效动作捕捉系统全面解决方案租赁协议
- 生物样本冷链物流与生命科学研究支持合同
- 小产权房配套设施共享及社区公共设施保养维护合同
- 电商侵权案件管辖权争议补充协议
- TBSRS 038-2020 核电厂液态流出物中锶-90的分析方法
- YY/T 1809-2021医用增材制造粉末床熔融成形工艺金属粉末清洗及清洗效果验证方法
- 部编版二年级下册语文课件语文园地七-小动物
- 融合终端微应用开发设计规范-版本
- 妇科门诊护理质量控制管理考核标准
- 秋收起义-完整版课件
- 朝阳区编制外岗位应聘人员报名表
- 自动喷水灭火系统质量验收项目缺陷判定记录
- 人教版一年级起点小学二年级英语下册全套教案
- T-CCIAT 0043-2022 建筑工程渗漏治理技术规程
- 供货、安装、调试、验收方案
评论
0/150
提交评论