个人感觉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 //C++导入接口
#include //输出数据结构
#include //后处理标志
#include
#include
#include
#include
#include //异常处理
#include
#include
#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 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 ret;
ModelObject* model_object = new ModelObject;
std::vector& mo_vertices = model_object->_allVertex;
std::vector& mo_vertices_bone = model_object->_allVertexBone;
std::vector& mo_indices = model_object->_allIndex;
vector& 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 map_node;
if (has_bone)
{
//读取Node关系(aiNode和网格无关,只是层次关系,如果它是骨骼,则同名)
//有名字的node,我们分配一个id
//广度优先遍历
list 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::max();
ani_clip._t1 = std::numeric_limits::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 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