当前位置: 首页 > 工具软件 > Assimp > 使用案例 >

Assimp库代码存档

公孙阳文
2023-12-01

        个人感觉assimp存在bug,打算替换至tinygltf,所以备份一下代码。

/**
* @file		DND.ModelLoader.ixx
* @brief	模型加载器,基于Assimp库
*
*
* @version	1.0
* @author	lveyou
* @date		22-09-02
*
* @note		由于Assimp库过于庞大,我们只在windows平台使用动态链接库
* @note		Assimp默认为右手坐标系(+X 指向右侧,+Y 指向上方,+Z 指向屏幕外朝向观看者)
* @note		逆时针顶点绕序为正面
* @note		行优先矩阵
*/
#ifdef WIN32

module;
#include <assimp/Importer.hpp>      //C++导入接口
#include <assimp/scene.h>           //输出数据结构
#include <assimp/postprocess.h>     //后处理标志
#include <assimp/IOStream.hpp>
#include <assimp/IOSystem.hpp>
#include <assimp/Logger.hpp>
#include <assimp/DefaultLogger.hpp>
#include <assimp/Exceptional.h>		//异常处理
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "DND.h"
export module DND.ModelLoader;

import DND.Std;
import DND.Type;
import DND.ModelObject;
import DND.Debug;
import DND.String;
import DND.Image;
import DND.File;
import DND.Factory;
import DND.BoneAnimation;
import DND.Math;

namespace dnd 
{

static glm::vec3 vec3_cast(const aiVector3D& v) { return glm::vec3(v.x, v.y, v.z); }
static glm::vec2 vec2_cast(const aiVector3D& v) { return glm::vec2(v.x, v.y); }
static glm::quat quat_cast(const aiQuaternion& q) { return glm::quat(q.w, q.x, q.y, q.z); }
static glm::mat4 mat4_cast(const aiMatrix4x4& m) { return glm::transpose(glm::make_mat4(&m.a1)); }
static glm::mat4 mat4_cast(const aiMatrix3x3& m) { return glm::transpose(glm::make_mat3(&m.a1)); }



}

DND_NAMESPACE_EXPORT

using namespace Assimp;

constexpr uint8_t BONE_ID_NONE = (uint8_t)255;

class ModelLoaderLogStream : public LogStream
{
public:
	//日志
	void write(const char* message) {
		g_debug.Write(format("Assimp:{}", message));
	}
};

class ModelLoader
{

public:
	ModelLoader()
	{
		//日志等级
		const unsigned int severity = 
			Logger::Debugging | Logger::Info 
			| Logger::Err | Logger::Warn;
		DefaultLogger::create("", Logger::VERBOSE);
		//附加到默认logger
		DefaultLogger::get()->attachStream(new ModelLoaderLogStream, severity);
	}
	~ModelLoader()
	{
		DefaultLogger::kill();
	}

	vector<ModelObject*> Load(string_view path_name, string_view res_name)
	{
		string str_path = string{ path_name };
		string str_base = String::EraseFilename(path_name);
		debug(format("开始加载模型文件:{},{}", str_path, str_base));

		//创建导入类实例
		Assimp::Importer importer;
		const aiScene* scene = importer.ReadFile(str_path,
			aiProcess_CalcTangentSpace |
			aiProcess_Triangulate |
			aiProcess_JoinIdenticalVertices |
			aiProcess_SortByPType |
			aiProcess_FlipUVs);
		//后处理标记
		//aiProcess_MakeLeftHanded 可修改为左手坐标系
		//aiProcess_FlipWindingOrder 修改为顺时针为正面
		//aiProcess_FlipUVs 默认左下角,设置后为左上角
		//aiProcess_GenNormals 生成法线
		//aiProcess_LimitBoneWeights 限制骨骼权重数为4(这个有bug)
		//aiProcess_FindInvalidData 除错一些数据
		//aiProcess_SplitByBoneCount 分割具有多个骨骼的网格
		//aiProcess_GlobalScale 执行全局缩放
		//aiProcess_EmbedTextures 将纹理变为嵌入形式,文件不存在,还会检查根目录同名文件
	
		if (!scene)
		{
			debug_err(format("导入场景失败:{}", importer.GetErrorString()));
			return {};
		}
		//debug_err(importer.GetErrorString());
		debug_msg(format("打开模型文件成功,开始加载模型:{}", path_name));
		debug(format("网格 数:{}", scene->mNumMeshes));
		debug(format("材质 数:{}", scene->mNumMaterials));
		debug(format("贴图 数:{}", scene->mNumTextures));
		debug(format("动画 数:{}", scene->mNumAnimations));

		//有动画则假设有骨骼
		bool has_bone = scene->mNumAnimations;

		//返回结构
		vector<ModelObject*> ret;
		ModelObject* model_object = new ModelObject;
		std::vector<Vertex>& mo_vertices =  model_object->_allVertex;
		std::vector<VertexBone>& mo_vertices_bone = model_object->_allVertexBone;
		std::vector<uint32_t>& mo_indices = model_object->_allIndex;
		vector<ModelObjectMaterial>& mo_material = model_object->_allMaterial;
		
		model_object->_boneData = has_bone ? new BoneData : nullptr;
		BoneData* bone_data = model_object->_boneData;

		ret.push_back(model_object);



		//读取材质(我们不读取DefaultMaterial)
		const char NAME_DEFAULT_MATERIAL[] = "DefaultMaterial";
		bool has_default_material = false;
		for (unsigned i = 0; i < scene->mNumMaterials; ++i)
		{
			const aiMaterial* m = scene->mMaterials[i];
			if (strcmp(m->GetName().C_Str(), NAME_DEFAULT_MATERIAL) == 0)
			{
				has_default_material = true;
				assert(i == 0);//它的id必为0
				continue;
			}

			unsigned count_diffuse = m->GetTextureCount(aiTextureType_DIFFUSE);
			unsigned count_normal = m->GetTextureCount(aiTextureType_NORMALS);

			aiColor3D col_diffuse;
			m->Get(AI_MATKEY_COLOR_DIFFUSE, col_diffuse);
			aiColor3D col_specular;
			m->Get(AI_MATKEY_COLOR_SPECULAR, col_specular);
			float shininess;
			m->Get(AI_MATKEY_SHININESS, shininess);
			//记录
			ModelObjectMaterial material;
			material._materialName = m->GetName().C_Str();
			material._material._diffuseAlbedo = { col_diffuse.r, col_diffuse.g, col_diffuse.b, 1.0f };
			material._material._fresnelR0 = { col_specular.r, col_specular.g, col_specular.b };
			material._material._roughness = ShininessToRoughness(shininess);

			if (count_diffuse)
			{
				_read_texture(scene, res_name, m, aiTextureType_DIFFUSE, str_base, material._pathTexDiffuse);
			}
			if (count_normal)
			{
				_read_texture(scene, res_name, m, aiTextureType_NORMALS, str_base, material._pathTexNormal);
			}
			for (unsigned j = 0; j < m->mNumProperties; ++j)
			{
				const aiMaterialProperty* prop = m->mProperties[j];
				debug(format("材质属性:{},{}", j, prop->mKey.C_Str()));
			}
			PrintMaterial(material, i);
			mo_material.emplace_back(material);
		}
		//当前mesh的索引下标
		size_t index = 0;
		//当前mesh的起始顶点下标
		size_t offset_vertex = 0;
		//所有Node -> node_id
		unordered_map<const aiNode*, size_t> map_node;

		if (has_bone)
		{
			//读取Node关系(aiNode和网格无关,只是层次关系,如果它是骨骼,则同名)
			//有名字的node,我们分配一个id

			//广度优先遍历
			list<aiNode*> all_node;
			//记录
			map_node[scene->mRootNode] = bone_data->_allNodeParent.size();
			size_t id_parent = -1;
			bone_data->_allNodeParent.push_back(id_parent);
			all_node.push_back(scene->mRootNode);//添加到栈顶
			//直至栈空
			while (!all_node.empty())
			{
				//得到栈顶
				aiNode* node = all_node.front();
				all_node.pop_front();

				assert(map_node.find(node) != map_node.end());
				size_t id_parent = map_node[node];

				//子节点,分配id,设置父节点id,并添加到栈尾
				for (unsigned i = 0; i < node->mNumChildren; ++i)
				{
					aiNode* node_child = node->mChildren[i];
					//分配id
					map_node[node_child] = bone_data->_allNodeParent.size();
					//指向父骨骼
					bone_data->_allNodeParent.push_back(id_parent);
					all_node.push_back(node_child);//添加到栈尾
				}
			}
			//父节点必须在前面
#ifndef NDEBUG
			for (size_t i = 0; i < bone_data->_allNodeParent.size(); ++i)
			{
				if (bone_data->_allNodeParent[i] != -1
					&& bone_data->_allNodeParent[i] >= i)
				{//如果 父id 大于等于 自己
					debug_err(format("节点关系错误:自己{}先于父节点{}出现",
						i, bone_data->_allNodeParent[i]));
				}
			}
#endif
			bone_data->_allBoneOffset.resize(map_node.size(), glm::mat4(1.0f));

			//读取动画
			for (unsigned i = 0; i < scene->mNumAnimations; ++i)
			{
				const aiAnimation* animation = scene->mAnimations[i];
				string ani_name = animation->mName.C_Str();
				if (ani_name.empty())
				{
					ani_name = format("{}#{}", res_name, i);
					debug_msg(format("动画名为空,生成名字为:{}", ani_name));
				}
				debug(format("#{}{:=^32}动画", i, ani_name));
				//每个Channel影响一个node
				assert(animation->mNumChannels && animation->mNumChannels <= map_node.size());
				debug(format("节点数:{}", animation->mNumChannels));
				double tick_per_second;
				if (animation->mTicksPerSecond)
					tick_per_second = animation->mTicksPerSecond;
				else
				{
					tick_per_second = 30;
					debug_warn("动画不存在tick每s值,将使用30!");
				}
				double ani_t = animation->mDuration / tick_per_second;
				debug(format("时长:{},{}/{}", ani_t, animation->mDuration, tick_per_second));

				//添加一个AnimationClip
				AnimationClip& ani_clip = bone_data->_allAnimation[ani_name];
				//以node的大小,而不是NumChannels
				ani_clip._allBoneKeyFrameMulti.resize(map_node.size());
				ani_clip._t0 = std::numeric_limits<real_time>::max();
				ani_clip._t1 = std::numeric_limits<real_time>::min();

				for (unsigned j = 0; j < animation->mNumChannels; ++j)
				{
					const aiNodeAnim* node_ani = animation->mChannels[j];
					//找到node_id
					aiNode* node = scene->mRootNode->FindNode(node_ani->mNodeName);
					assert(node);
					assert(map_node.find(node) != map_node.end());
					//写入对应node
					BoneKeyFrameMulti& all_key_frame = 
						ani_clip._allBoneKeyFrameMulti[map_node[node]];
					//取最大者
					unsigned num_key = max(max(node_ani->mNumPositionKeys,
						node_ani->mNumRotationKeys), node_ani->mNumRotationKeys);
					debug(format("节点,关键帧:{},{}", node_ani->mNodeName.C_Str(), num_key));

					all_key_frame._allKeyFrame.resize(num_key);
					for (unsigned x = 0; x < node_ani->mNumPositionKeys; ++x)
					{
						BoneKeyFrame& key_frame = all_key_frame._allKeyFrame[x];
						const aiVectorKey& t = node_ani->mPositionKeys[x];
						if(key_frame._t < 0)
							key_frame._t = t.mTime / tick_per_second;
						key_frame._translation = { t.mValue.x, t.mValue.y, t.mValue.z };
					}
					for (unsigned x = 0; x < node_ani->mNumRotationKeys; ++x)
					{
						BoneKeyFrame& key_frame = all_key_frame._allKeyFrame[x];
						const aiQuatKey& r = node_ani->mRotationKeys[x];
						if (key_frame._t < 0)
							key_frame._t = r.mTime / tick_per_second;
						key_frame._roationQuat = { r.mValue.w, r.mValue.x, r.mValue.y, r.mValue.z };
					}
					for (unsigned x = 0; x < node_ani->mNumScalingKeys; ++x)
					{
						BoneKeyFrame& key_frame = all_key_frame._allKeyFrame[x];
						const aiVectorKey& s = node_ani->mScalingKeys[x];

						if (key_frame._t < 0)
							key_frame._t = s.mTime / tick_per_second;
						key_frame._scaling = { s.mValue.x, s.mValue.y, s.mValue.z };
					}
#ifndef NDEBUG
					//时间检查
					for (BoneKeyFrame& key_frame : all_key_frame._allKeyFrame)
					{
						if (key_frame._t < 0)
						{
							debug_err("有关键帧未读取到时间!");
						}
					}
#endif
					//取上下界作为动画时间
					BoneKeyFrame& kf_beg = all_key_frame._allKeyFrame.front();
					BoneKeyFrame& kf_end = all_key_frame._allKeyFrame.back();
					ani_clip._t0 = min(ani_clip._t0, kf_beg._t);
					ani_clip._t1 = max(ani_clip._t1, kf_end._t);
				}
			}
			//assert(bone_data->_allBoneParent.size() == bone_data->GetBoneSize());
			
			int pause = 3;
		}

		//所有mesh数据都在这里(由于mesh对应1个材质,所以简单生成sub即可
		for (unsigned i = 0; i < scene->mNumMeshes; ++i)
		{
			aiMesh* mesh = scene->mMeshes[i];

			assert(mesh->HasPositions());
			bool has_uv = mesh->HasTextureCoords(0);
			bool has_normal = mesh->HasNormals();
			bool has_tangent = mesh->HasTangentsAndBitangents();

			const aiVector3D POS_ZERO = { 0,0,0 };
			for (unsigned j = 0; j < mesh->mNumVertices; ++j)
			{
				const aiVector3D& pos = mesh->mVertices[j];
				const aiVector3D& uv = has_uv ? mesh->mTextureCoords[0][j] : POS_ZERO;
				const aiVector3D& normal = has_normal ? mesh->mNormals[j] : POS_ZERO;
				const aiVector3D& tangent = has_tangent ? mesh->mTangents[j] : POS_ZERO;
				if (has_bone)
				{
					VertexBone v;
					v._pos = { pos.x, pos.y, pos.z };
					v._uv = { uv.x, uv.y };
					v._normal = { normal.x,normal.y,normal.z };
					v._tangent = { tangent.x, tangent.y, tangent.z };
					v._boneWeight = { 0, 0, 0 };
					for (uint8_t& iter : v._boneID)
						iter = BONE_ID_NONE;//最后需要转换为0
					mo_vertices_bone.emplace_back(v);
				}
				else
				{
					Vertex v;
					v._pos = { pos.x, pos.y, pos.z };
					v._uv = { uv.x, uv.y };
					v._normal = { normal.x,normal.y,normal.z };
					v._tangent = { tangent.x, tangent.y, tangent.z };

					mo_vertices.emplace_back(v);
				}
				
			}

			//以face读取则是索引(为mesh的索引,而不是整体)
			for (unsigned k = 0; k < mesh->mNumFaces; ++k)
			{
				const aiFace& face = mesh->mFaces[k];
				assert(face.mNumIndices == 3);
				mo_indices.push_back((uint32_t)(face.mIndices[0] + offset_vertex));
				mo_indices.push_back((uint32_t)(face.mIndices[1] + offset_vertex));
				mo_indices.push_back((uint32_t)(face.mIndices[2] + offset_vertex));
			}

			ModelObjectSub sub;
			sub._range._offsetIndex = index;
			sub._range._countTriangle = mesh->mNumFaces;
			if (has_default_material)
				sub._indexMaterial = (size_t)mesh->mMaterialIndex - 1;
			else
				sub._indexMaterial = mesh->mMaterialIndex;
			model_object->_allSub.push_back(sub);


			if (has_bone)
			{//读取骨骼
				for (unsigned k = 0; k < mesh->mNumBones; ++k)
				{
					const aiBone* bone = mesh->mBones[k];
					//找到node_id
					aiNode* node = scene->mRootNode->FindNode(bone->mName);
					assert(node);
					assert(map_node.find(node) != map_node.end());
					size_t node_id = map_node[node];

					debug(format("骨骼,下标,节点,顶点:{},{},{},{}",
						bone->mName.C_Str(), k, node_id, bone->mNumWeights));
					//记录偏移矩阵
					bone_data->_allBoneOffset[node_id] = mat4_cast(bone->mOffsetMatrix);
					//写入顶点骨骼相关数据
					for (unsigned m = 0; m < bone->mNumWeights; ++m)
					{
						const aiVertexWeight& weight = bone->mWeights[m];
						if(weight.mWeight == 0)
							continue;//有可能它本身为0
						VertexBone& v = mo_vertices_bone[weight.mVertexId + offset_vertex];
						size_t n = 0;
						for (; n < NUM_BONE; ++n)
						{//写入为255的位置
							if (v._boneID[n] == node_id)
								break;//有可能会重复(冗余)
							if (v._boneID[n] == BONE_ID_NONE)
								break;
						}
						if (n == NUM_BONE)
						{//越界
							debug_warn(format("同一个顶点的骨骼数超过{},将忽略多余的!", NUM_BONE));
							continue;
						}
						if (n != NUM_BONE - 1)
						{//不是最后一个位置才写入
							v._boneWeight[n] = weight.mWeight;
						}
						else
						{//最后一个进行求和验证
#ifndef NDEBUG
							float sum = v._boneWeight[0] + v._boneWeight[1]
								+ v._boneWeight[2] + weight.mWeight;
							if (abs(sum - 1.0f) > 0.1f)
							{
								debug_err("骨骼权重和不为1!");
								Vector4 v_n = {
							v._boneWeight[0], v._boneWeight[1],
							v._boneWeight[2], weight.mWeight };
								Math::Normalize(v_n);
								v._boneWeight = glm::make_vec3(v_n.data());
							}
#endif
						}
						v._boneID[n] = node_id;
					}
				}
			}

			debug(format("#{}{:=^32}网格", i, mesh->mName.C_Str()));
			debug(format("面数,材质下标:{},{}", mesh->mNumFaces, mesh->mMaterialIndex));
			debug(format("顶点数:{}", mesh->mNumVertices, has_normal, has_tangent));
			debug(format("法线,切线:{},{}", has_normal, has_tangent));
			debug(format("起始,三角数:{},{}", index, mesh->mNumFaces));

			index = mo_indices.size();
			offset_vertex = has_bone ? mo_vertices_bone.size() : mo_vertices.size();
		}
		//记录到父节点矩阵
		if (has_bone)
		{
			bone_data->_allNodeTrans2Parent.resize(map_node.size());
			for (auto& [node, id] : map_node)
			{
				bone_data->_allNodeTrans2Parent[id] = mat4_cast(node->mTransformation);
			}
		}
		//顶点骨骼id置0
		for (VertexBone& v : mo_vertices_bone)
		{
			for (uint8_t& id : v._boneID)
			{
				if (id == BONE_ID_NONE)
					id = 0;
			}
		}
		//子网格合并
		if (true)
		{//合并网格连续,且材质相同的项
			vector<ModelObjectSub> vec_sub;
			ModelObjectSub* pre = nullptr;
			//
			for (auto iter = model_object->_allSub.begin();
				iter != model_object->_allSub.end(); ++iter)
			{
				ModelObjectSub& sub = *iter;

				if (pre
					&& sub._indexMaterial == pre->_indexMaterial
					&& (sub._range._offsetIndex 
						== pre->_range._offsetIndex + pre->_range._countTriangle * 3))
				{//是连续的
					pre->_range._countTriangle += sub._range._countTriangle;
				}
				else
				{
					vec_sub.push_back(sub);
					pre = &vec_sub.back();
				}
			}
			debug(format("子网格合并:{}->{}", model_object->_allSub.size(), vec_sub.size()));
			for (ModelObjectSub& sub : vec_sub)
			{
				debug(format("合并后网格,起始:{},三角数:{},材质:{}", 
					sub._range._offsetIndex, sub._range._countTriangle, sub._indexMaterial));
			}

			swap(model_object->_allSub, vec_sub);
			int pause = 3;
		}
		return ret;
	}

private:
	struct SceneObject
	{

	};
	//递归遍历node
	void _process_node(aiNode* node, SceneObject* targetParent, aiMatrix4x4 accTransform)
	{
		SceneObject* parent;
		aiMatrix4x4 transform;

		//如果node有mesh,则创建一个SceneObject
		if (node->mNumMeshes > 0) 
		{
			SceneObject* newObject = new SceneObject;
			//targetParent.addChild(newObject);
			//读取mesh到SceneObject
			_read_node_mesh(node, newObject);

			//这个SceneObject是所有子节点的父亲
			parent = newObject;
			//transform.SetUnity();
		}
		else
		{
			//如果nodem没有mesh,则跳过,但应用变换
			parent = targetParent;
			transform = node->mTransformation * accTransform;
		}

		//继续遍历所有子节点(广度优先)
		for (size_t i = 0; i < node->mNumChildren; ++i)
		{
			_process_node(node->mChildren[i], parent, transform);
		}
	}

	//读取网格
	void _read_node_mesh(aiNode* node, SceneObject* object)
	{

	}
	//光滑度 -> 粗糙度
	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;
	}
	//打印材质信息
	void PrintMaterial(const ModelObjectMaterial& m, unsigned i)
	{
		debug(format("#{}{:=^32}材质", i, m._materialName));
		debug(format("漫反射率:{}, {}, {}, {}",
			m._material._diffuseAlbedo[0], m._material._diffuseAlbedo[1],
			m._material._diffuseAlbedo[2], m._material._diffuseAlbedo[3]));
		debug(format("菲涅耳系数:{}, {}, {}",
			m._material._fresnelR0[0], m._material._fresnelR0[1], m._material._fresnelR0[2]));
		debug(format("粗糙度:{}", m._material._roughness));
		debug(format("漫反射贴图:{}", m._pathTexDiffuse));
		debug(format("法线贴图:{}", m._pathTexNormal));
		//debug(format("{:=^32}", ""));
	}

	//读取纹理,处理内嵌文件的情况,返回到str_path
	void _read_texture(const aiScene* scene, string_view res_name, const aiMaterial* m,
		aiTextureType tex_type, string_view str_base, string& str_path)
	{
		aiString path;
		m->GetTexture(tex_type, 0, &path);
		const aiTexture* texture = scene->GetEmbeddedTexture(path.C_Str());
		if (texture)
		{
			if (texture->mHeight == 0)
			{//高为0表示它是文件数据,比如png或dds
				string str_fmt = texture->achFormatHint;
				if (Image::IsSupportFormat(str_fmt, true))
				{
					Buffer buf((byte*)texture->pcData, (size_t)texture->mWidth);
					//使用它的名字避免重复导出
					string ai_name = path.C_Str();
					String::EraseNotFileName(ai_name);
					string str = format("{}{}#{}.{}", 
						g_factory->GetPathTemp(), res_name, ai_name, str_fmt);
					if (g_file->SaveBuffer(buf, str))
						str_path = str;
					else
						debug_warn(format("导出贴图失败:{}", str));
				}
				else
					debug_warn(format("不支持的贴图格式:{}", str_fmt));
			}
			else
			{//为rbga32位数据
				assert(0 && "暂未实现!");
			}
		}
		else
		{
			str_path = format("{}{}", str_base, path.C_Str());
			String::CvtPathSlash(str_path);
		}
	}
};

ModelLoader* g_modelLoader;

DND_NAMESPACE_END

#endif

 类似资料: