C++解析obj模型文件方法介绍_第1页
C++解析obj模型文件方法介绍_第2页
C++解析obj模型文件方法介绍_第3页
C++解析obj模型文件方法介绍_第4页
C++解析obj模型文件方法介绍_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论