最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++解析obj模型文件方法介紹

 更新時(shí)間:2022年09月05日 09:05:28   作者:略游  
由于本人打算使用Assimp來(lái)加載模型,這里記錄一下tinyobjloader庫(kù)的使用。之前也研究過(guò)fbxsdk,除了骨骼動(dòng)畫(huà)暫未讀取外,代碼自認(rèn)為還算可靠

一、前言

tinyobjloader地址:

傳送門(mén)

而tinyobjloader庫(kù)只有一個(gè)頭文件,可以很方便的讀取obj文件。支持材質(zhì),不過(guò)不支持骨骼動(dòng)畫(huà),vulkan官方教程便是使用的它。不過(guò)沒(méi)有骨骼動(dòng)畫(huà)還是有很大的局限性,這里只是分享一下怎么讀取材質(zhì)和拆分網(wǎng)格。

二、中間文件

我抽象了一個(gè)ModelObject類(lèi)表示模型數(shù)據(jù),而一個(gè)ModelObject包含多個(gè)Sub模型,每個(gè)Sub模型使用同一材質(zhì)(有的人稱(chēng)為圖元Primitive或DrawCall)。最后我將其保存為文件,這樣我的引擎便可直接解析ModelObject文件,而不是再去讀obj、fbx等其他文件了。

這一節(jié)可以跳過(guò),下一節(jié)是真正使用tinyobjloader庫(kù)。

//一個(gè)文件會(huì)有多個(gè)ModelObject,一個(gè)ModelObject根據(jù)材質(zhì)分為多個(gè)ModelSub
//注意ModelSub為一個(gè)材質(zhì),需要讀取時(shí)合并網(wǎng)格
class ModelObject
{
	friend class VK;
public:
	//從源文件加載模型
	static vector<ModelObject*> Create(string_view path_name);
	void Load(string_view path_name);
	//保存到文件
	void SaveToFile(string_view path_name);
private:
	vector<ModelObjectSub> _allSub; //下標(biāo)減1 為材質(zhì),0為沒(méi)有材質(zhì)
	vector<Vertex> _allVertex;//頂點(diǎn)緩存
	vector<uint32_t> _allIndex;//索引緩存
	vector<ModelObjectMaterial> _allMaterial;//所有材質(zhì)
	//------------------不同格式加載實(shí)現(xiàn)--------------------------------
	//obj
	static vector<ModelObject*> _load_obj(string_view path_name);
	static vector<ModelObject*> _load_obj_2(string_view path_name);
};

ModelObjectSub只是表示在索引緩存的一段范圍:

//模型三角形范圍
struct ModelTriangleRange
{
	ModelTriangleRange() :
		_countTriangle{ 0 },
		_offsetIndex{ 0 }
	{}
	size_t _countTriangle;
	size_t _offsetIndex;
};
//子模型對(duì)象 范圍
struct ModelObjectSub
{
	ModelTriangleRange _range;
};

而ModelObjectMaterial表示模型材質(zhì):

//! 材質(zhì)
struct Material
{
	glm::vec4 _diffuseAlbedo;//漫反射率
	glm::vec3 _fresnelR0;	//菲涅耳系數(shù)
	float _roughness;		//粗糙度
};
//模型對(duì)象 材質(zhì)
struct ModelObjectMaterial
{
	//最后轉(zhuǎn)為Model時(shí),變?yōu)榭梢杂玫闹髻Y源
	Material _material;
	string _materialName;
	//路徑為空,則表示沒(méi)有(VK加載時(shí)會(huì)返回0)
	string _pathTexDiffuse;
	string _pathTexNormal;
};

三、使用

首先引入頭文件:

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

接口原型,將obj文件變?yōu)槎鄠€(gè)ModelObject:

vector<ModelObject*> ModelObject::_load_obj_2(string_view path_name);

取得文件名,和文件所在路徑(會(huì)自動(dòng)加載路徑下的同名mtl文件,里面包含了材質(zhì)):

string str_path = string{ path_name };
string str_base = String::EraseFilename(path_name);
const char* filename = str_path.c_str();
const char* basepath = str_base.c_str();

基本數(shù)據(jù):

debug(format("開(kāi)始加載obj文件:{},{}", filename, basepath));
bool triangulate = true;//三角化
tinyobj::attrib_t attrib; // 所有的數(shù)據(jù)放在這里
std::vector<tinyobj::shape_t> shapes;//子模型
std::vector<tinyobj::material_t> materials;//材質(zhì)
std::string warn;
std::string err;

加載并打印一些信息:

bool b_read = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename,
	basepath, triangulate);
//打印錯(cuò)誤
if (!warn.empty())
	debug_warn(warn);
if (!err.empty()) 
	debug_err(err);
if (!b_read)
{
	debug_err(format("讀取obj文件失?。簕}", path_name));
	return {};
}
debug(format("頂點(diǎn)數(shù):{}", attrib.vertices.size() / 3));
debug(format("法線數(shù):{}", attrib.normals.size() / 3));
debug(format("UV數(shù):{}", attrib.texcoords.size() / 2));
debug(format("子模型數(shù):{}", shapes.size()));
debug(format("材質(zhì)數(shù):{}", materials.size()));

這將打印以下數(shù)據(jù):

由于obj文件只產(chǎn)生一個(gè)ModelObject,我們?nèi)缦绿砑右粋€(gè),并返回頂點(diǎn)、索引、材質(zhì)等引用,用于后面填充:

//obj只有一個(gè)ModelObject
vector<ModelObject*> ret;
ModelObject* model_object = new ModelObject;
std::vector<Vertex>& mo_vertices = model_object->_allVertex;
std::vector<uint32_t>& mo_indices = model_object->_allIndex;
vector<ModelObjectMaterial>& mo_material = model_object->_allMaterial;
ret.push_back(model_object);

首先記錄材質(zhì)信息:

//------------------獲取材質(zhì)-------------------
mo_material.resize(materials.size());
for (size_t i = 0; i < materials.size(); ++i)
{
	tinyobj::material_t m = materials[i];
	debug(format("材質(zhì):{},{}", i, m.name));
	ModelObjectMaterial& material = model_object->_allMaterial[i];
	material._materialName = m.name;
	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;
}

這將產(chǎn)生以下輸出:

然后遍歷shape,按材質(zhì)記錄頂點(diǎn)。這里需要注意的是,一個(gè)obj文件有多個(gè)shape,每個(gè)shape由n個(gè)三角面組成。而每個(gè)三角形擁有獨(dú)立的材質(zhì)編號(hào),所以這里按材質(zhì)分別記錄,而不是一般的合并為整體:

//------------------獲取模型-------------------
//按 材質(zhì) 放入面的頂點(diǎn)
vector<vector<tinyobj::index_t>> all_sub;
all_sub.resize(1 + materials.size());//0為默認(rèn)
for (size_t i = 0; i < shapes.size(); i++) 
{//每一個(gè)子shape
	tinyobj::shape_t& shape = shapes[i];
	size_t num_index = shape.mesh.indices.size();
	size_t num_face = shape.mesh.num_face_vertices.size();
	debug(format("讀取子模型:{},{}", i, shape.name));
	debug(format("索引數(shù):{};面數(shù):{}", num_index, num_face));
	//當(dāng)前mesh下標(biāo)(每個(gè)面遞增3)
	size_t index_offset = 0;
	//每一個(gè)面
	for (size_t j = 0; j < num_face; ++j)
	{
		int index_mat = shape.mesh.material_ids[j];//每個(gè)面的材質(zhì)
		vector<tinyobj::index_t>& sub_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++]);
	}
}

按材質(zhì)記錄頂點(diǎn)的索引(tinyobj::index_t)后,接下來(lái)就是讀取頂點(diǎn)的實(shí)際數(shù)據(jù),并防止重復(fù)讀?。?/p>

//生成子模型,并填入頂點(diǎn)
std::unordered_map<tinyobj::index_t, size_t, hash_idx, equal_idx>
	uniqueVertices;//避免重復(fù)插入頂點(diǎn)
size_t i = 0;
for (vector<tinyobj::index_t>& sub_idx : all_sub)
{
	ModelObjectSub sub;
	sub._range._offsetIndex = i;
	sub._range._countTriangle = sub_idx.size() / 3;
	model_object->_allSub.push_back(sub);
	for (tinyobj::index_t& idx : sub_idx)
	{
		auto iter = uniqueVertices.find(idx);
		if (iter == uniqueVertices.end())
		{
			Vertex v;
			//v
			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()));
return ret;

上面用到的哈希函數(shù):

struct equal_idx
{
	bool operator()(const tinyobj::index_t& a, const tinyobj::index_t& b) const
	{
		return a.vertex_index == b.vertex_index
			&& a.texcoord_index == b.texcoord_index
			&& a.normal_index == b.normal_index;
	}
};
struct hash_idx 
{
	size_t operator()(const tinyobj::index_t& a) const
	{
		return ((a.vertex_index
			^ a.texcoord_index << 1) >> 1)
			^ (a.normal_index << 1);
	}
};

最后打印出來(lái)的數(shù)據(jù)如下:

對(duì)于材質(zhì)的處理,漫反射貼圖即是基本貼圖,而法線(凹凸)貼圖、漫反射率、菲涅耳系數(shù)、光滑度等需要渲染管線支持并與光照計(jì)算產(chǎn)生效果。

四、完整代碼

可以此處獲取最新的源碼(我會(huì)改用Assimp,并添加骨骼動(dòng)畫(huà)、Blinn-Phong光照模型),也可以用后面的:傳送門(mén)

如果有用,歡迎點(diǎn)贊、收藏、關(guān)注,我將更新更多C++相關(guān)的文章。

#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>
struct equal_idx
{
	bool operator()(const tinyobj::index_t& a, const tinyobj::index_t& b) const
	{
		return a.vertex_index == b.vertex_index
			&& a.texcoord_index == b.texcoord_index
			&& a.normal_index == b.normal_index;
	}
};
struct hash_idx 
{
	size_t operator()(const tinyobj::index_t& a) const
	{
		return ((a.vertex_index
			^ a.texcoord_index << 1) >> 1)
			^ (a.normal_index << 1);
	}
};
float ShininessToRoughness(float Ypoint)
{
	float a = -1;
	float b = 2;
	float c;
	c = (Ypoint / 100) - 1;
	float D;
	D = b * b - (4 * a * c);
	float x1;
	x1 = (-b + sqrt(D)) / (2 * a);
	return x1;
}
vector<ModelObject*> ModelObject::_load_obj_2(string_view path_name)
{
	string str_path = string{ path_name };
	string str_base = String::EraseFilename(path_name);
	const char* filename = str_path.c_str();
	const char* basepath = str_base.c_str();
	bool triangulate = true;
	debug(format("開(kāi)始加載obj文件:{},{}", filename, basepath));
	tinyobj::attrib_t attrib; // 所有的數(shù)據(jù)放在這里
	std::vector<tinyobj::shape_t> shapes;//子模型
	std::vector<tinyobj::material_t> materials;
	std::string warn;
	std::string err;
	bool b_read = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename,
		basepath, triangulate);
	//打印錯(cuò)誤
	if (!warn.empty())
		debug_warn(warn);
	if (!err.empty()) 
		debug_err(err);
	if (!b_read)
	{
		debug_err(format("讀取obj文件失?。簕}", path_name));
		return {};
	}
	debug(format("頂點(diǎn)數(shù):{}", attrib.vertices.size() / 3));
	debug(format("法線數(shù):{}", attrib.normals.size() / 3));
	debug(format("UV數(shù):{}", attrib.texcoords.size() / 2));
	debug(format("子模型數(shù):{}", shapes.size()));
	debug(format("材質(zhì)數(shù):{}", materials.size()));
	//obj只有一個(gè)ModelObject
	vector<ModelObject*> ret;
	ModelObject* model_object = new ModelObject;
	std::vector<Vertex>& mo_vertices = model_object->_allVertex;
	std::vector<uint32_t>& mo_indices = model_object->_allIndex;
	vector<ModelObjectMaterial>& mo_material = model_object->_allMaterial;
	ret.push_back(model_object);
	//------------------獲取材質(zhì)-------------------
	mo_material.resize(materials.size());
	for (size_t i = 0; i < materials.size(); ++i)
	{
		tinyobj::material_t m = materials[i];
		debug(format("材質(zhì):{},{}", i, m.name));
		ModelObjectMaterial& material = model_object->_allMaterial[i];
		material._materialName = m.name;
		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)更常見(jiàn)
			material._pathTexNormal = str_base + m.normal_texname;
	}
	//------------------獲取模型-------------------
	//按 材質(zhì) 放入面的頂點(diǎn)
	vector<vector<tinyobj::index_t>> all_sub;
	all_sub.resize(1 + materials.size());//0為默認(rèn)
	for (size_t i = 0; i < shapes.size(); i++) 
	{//每一個(gè)子shape
		tinyobj::shape_t& shape = shapes[i];
		size_t num_index = shape.mesh.indices.size();
		size_t num_face = shape.mesh.num_face_vertices.size();
		debug(format("讀取子模型:{},{}", i, shape.name));
		debug(format("索引數(shù):{};面數(shù):{}", num_index, num_face));
		//當(dāng)前mesh下標(biāo)(每個(gè)面遞增3)
		size_t index_offset = 0;
		//每一個(gè)面
		for (size_t j = 0; j < num_face; ++j)
		{
			int index_mat = shape.mesh.material_ids[j];//每個(gè)面的材質(zhì)
			vector<tinyobj::index_t>& sub_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++]);
		}
	}
	//生成子模型,并填入頂點(diǎn)
	std::unordered_map<tinyobj::index_t, size_t, hash_idx, equal_idx>
		uniqueVertices;//避免重復(fù)插入頂點(diǎn)
	size_t i = 0;
	for (vector<tinyobj::index_t>& sub_idx : all_sub)
	{
		ModelObjectSub sub;
		sub._range._offsetIndex = i;
		sub._range._countTriangle = sub_idx.size() / 3;
		model_object->_allSub.push_back(sub);
		for (tinyobj::index_t& idx : sub_idx)
		{
			auto iter = uniqueVertices.find(idx);
			if (iter == uniqueVertices.end())
			{
				Vertex v;
				//v
				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()));
	return ret;
}

到此這篇關(guān)于C++解析obj模型文件方法介紹的文章就介紹到這了,更多相關(guān)C++解析obj內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C語(yǔ)言scanf語(yǔ)句吃掉回車(chē)或者空格問(wèn)題及解決

    C語(yǔ)言scanf語(yǔ)句吃掉回車(chē)或者空格問(wèn)題及解決

    這篇文章主要介紹了C語(yǔ)言scanf語(yǔ)句吃掉回車(chē)或者空格問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 解讀C++編程中派生類(lèi)的構(gòu)成和創(chuàng)建

    解讀C++編程中派生類(lèi)的構(gòu)成和創(chuàng)建

    這篇文章主要介紹了解讀C++編程中派生類(lèi)的構(gòu)成和創(chuàng)建,是C++入門(mén)學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-09-09
  • C/C++ Qt TreeWidget 嵌套節(jié)點(diǎn)操作使用

    C/C++ Qt TreeWidget 嵌套節(jié)點(diǎn)操作使用

    本文主要介紹了TreeWidget的如何使用,實(shí)現(xiàn)對(duì)樹(shù)形框多節(jié)點(diǎn)的各種操作,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • C語(yǔ)言手把手教你實(shí)現(xiàn)貪吃蛇AI(中)

    C語(yǔ)言手把手教你實(shí)現(xiàn)貪吃蛇AI(中)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言手把手教你實(shí)現(xiàn)貪吃蛇AI的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲

    C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲

    這篇文章主要為大家詳細(xì)介紹了c語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • C++中rapidjson將嵌套map轉(zhuǎn)為嵌套json的講解

    C++中rapidjson將嵌套map轉(zhuǎn)為嵌套json的講解

    今天小編就為大家分享一篇關(guān)于C++中rapidjson將嵌套map轉(zhuǎn)為嵌套json的講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-04-04
  • C語(yǔ)言猜兇手的代碼實(shí)現(xiàn)

    C語(yǔ)言猜兇手的代碼實(shí)現(xiàn)

    本文主要介紹了C語(yǔ)言猜兇手的代碼實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • C++中策略模式和工廠模式的區(qū)別

    C++中策略模式和工廠模式的區(qū)別

    這篇文章介紹了C++中策略模式和工廠模式的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • 基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲

    基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲

    這篇文章主要為大家詳細(xì)介紹了基于C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Windows下Qt讀取系統(tǒng)的內(nèi)存、CPU、GPU等使用信息的示例代碼

    Windows下Qt讀取系統(tǒng)的內(nèi)存、CPU、GPU等使用信息的示例代碼

    在當(dāng)今計(jì)算機(jī)應(yīng)用廣泛的領(lǐng)域中,了解系統(tǒng)的內(nèi)存、CPU和GPU使用情況是非常重要的,本文將介紹如何使用Qt和Windows API來(lái)讀取系統(tǒng)的內(nèi)存、CPU和GPU使用詳細(xì)信息,將提供一個(gè)完整的示例代碼,需要的朋友可以參考下
    2024-01-01

最新評(píng)論

绿春县| 辽源市| 宁安市| 青神县| 错那县| 盱眙县| 阿瓦提县| 荆州市| 九龙城区| 惠州市| 密山市| 汾西县| 米脂县| 乾安县| 会理县| 奎屯市| 区。| 额济纳旗| 久治县| 麻江县| 榆树市| 仙游县| 三原县| 辽阳市| 务川| 瓦房店市| 兴安盟| 达拉特旗| 丰顺县| 西畴县| 西林县| 鄂尔多斯市| 永宁县| 永州市| 高陵县| 平顶山市| 咸丰县| 准格尔旗| 东港市| 醴陵市| 盐城市|