애니메이션 데이터 추출
FBX파일에서 데이터를 추출
FBX파일에는 메쉬만 따로 있는 경우, 메쉬에다가 애니메이션이 같이 있는 경우, 메쉬는 없지만 애니메이션만 있는 경우 등 다양하게 있기에 어려움이 있음
- 대부분의 경우에는 애니메이션은 따로 빠져있는 경우가 많다
- 애니메이션의 정보를 가지고 있는 구조체 생성
AsTypes.h
struct asKeyframeData
{
float time;
Vec3 scale;
Quaternion rotation;
Vec3 translation;
};
struct asKeyframe
{
string boneName;
vector<asKeyframeData> transforms;
};
struct asAnimation
{
string name;
uint32 frameCount;
float frameRate;
float duration;
vector<shared_ptr<asKeyframe>> keyframes;
};
struct asAnimationNode
{
aiString name;
vector<asKeyframeData> keyframe;
};
Converter
Converter.h
public:
void ExportAnimationData(wstring savePath, uint32 index = 0);
private:
shared_ptr<asAnimation> ReadAnimationData(aiAnimation* srcAnimation);
shared_ptr<asAnimationNode> ParseAnimationNode(shared_ptr<asAnimation> animation, aiNodeAnim* srcNode);
void ReadKeyframeData(shared_ptr<asAnimation> animation, aiNode* srcNode, map<string, shared_ptr<asAnimationNode>>& cache);
void WriteAnimationData(shared_ptr<asAnimation> animation, wstring finalPath);
Converter.cpp
void Converter::ExportAnimationData(wstring savePath, uint32 index)
{
wstring finalPath = _modelPath + savePath + L".clip";
assert(index < _scene->mNumAnimations);
shared_ptr<asAnimation> animation = ReadAnimationData(_scene->mAnimations[index]);
WriteAnimationData(animation, finalPath);
}
shared_ptr<asAnimation> Converter::ReadAnimationData(aiAnimation* srcAnimation)
{
shared_ptr<asAnimation> animation = make_shared<asAnimation>();
animation->name = srcAnimation->mName.C_Str();
animation->frameRate = (float)srcAnimation->mTicksPerSecond;
animation->frameCount = (uint32)srcAnimation->mDuration + 1;
map<string, shared_ptr<asAnimationNode>> cacheAnimNodes;
for (uint32 i = 0; i < srcAnimation->mNumChannels; i++)
{
aiNodeAnim* srcNode = srcAnimation->mChannels[i];
shared_ptr<asAnimationNode> node = ParseAnimationNode(animation, srcNode);
animation->duration = max(animation->duration, node->keyframe.back().time);
cacheAnimNodes[srcNode->mNodeName.C_Str()] = node;
}
ReadKeyframeData(animation, _scene->mRootNode, cacheAnimNodes);
return animation;
}
shared_ptr<asAnimationNode> Converter::ParseAnimationNode(shared_ptr<asAnimation> animation, aiNodeAnim* srcNode)
{
std::shared_ptr<asAnimationNode> node = make_shared<asAnimationNode>();
node->name = srcNode->mNodeName;
uint32 keyCount = max(max(srcNode->mNumPositionKeys, srcNode->mNumScalingKeys), srcNode->mNumRotationKeys);
for (uint32 k = 0; k < keyCount; k++)
{
asKeyframeData frameData;
bool found = false;
uint32 t = node->keyframe.size();
if (::fabsf((float)srcNode->mPositionKeys[k].mTime - (float)t) <= 0.0001f)
{
aiVectorKey key = srcNode->mPositionKeys[k];
frameData.time = (float)key.mTime;
::memcpy_s(&frameData.translation, sizeof(Vec3), &key.mValue, sizeof(aiVector3D));
found = true;
}
if (::fabsf((float)srcNode->mRotationKeys[k].mTime - (float)t) <= 0.0001f)
{
aiQuatKey key = srcNode->mRotationKeys[k];
frameData.time = (float)key.mTime;
frameData.rotation.x = key.mValue.x;
frameData.rotation.y = key.mValue.y;
frameData.rotation.z = key.mValue.z;
frameData.rotation.w = key.mValue.w;
found = true;
}
if (::fabsf((float)srcNode->mScalingKeys[k].mTime - (float)t) <= 0.0001f)
{
aiVectorKey key = srcNode->mScalingKeys[k];
frameData.time = (float)key.mTime;
::memcpy_s(&frameData.scale, sizeof(Vec3), &key.mValue, sizeof(aiVector3D));
found = true;
}
if (found == true)
node->keyframe.push_back(frameData);
}
if (node->keyframe.size() < animation->frameCount)
{
uint32 count = animation->frameCount - node->keyframe.size();
asKeyframeData keyFrame = node->keyframe.back();
for (uint32 n = 0; n < count; n++)
node->keyframe.push_back(keyFrame);
}
return node;
}
void Converter::ReadKeyframeData(shared_ptr<asAnimation> animation, aiNode* srcNode, map<string, shared_ptr<asAnimationNode>>& cache)
{
shared_ptr<asKeyframe> keyframe = make_shared<asKeyframe>();
keyframe->boneName = srcNode->mName.C_Str();
shared_ptr<asAnimationNode> findNode = cache[srcNode->mName.C_Str()];
for (uint32 i = 0; i < animation->frameCount; i++)
{
asKeyframeData frameData;
if (findNode == nullptr)
{
Matrix transform(srcNode->mTransformation[0]);
transform = transform.Transpose();
frameData.time = (float)i;
transform.Decompose(OUT frameData.scale, OUT frameData.rotation, OUT frameData.translation);
}
else
{
frameData = findNode->keyframe[i];
}
keyframe->transforms.push_back(frameData);
}
animation->keyframes.push_back(keyframe);
for (uint32 i = 0; i < srcNode->mNumChildren; i++)
ReadKeyframeData(animation, srcNode->mChildren[i], cache);
}
void Converter::WriteAnimationData(shared_ptr<asAnimation> animation, wstring finalPath)
{
auto path = filesystem::path(finalPath);
filesystem::create_directory(path.parent_path());
shared_ptr<FileUtils> file = make_shared<FileUtils>();
file->Open(finalPath, FileMode::Write);
file->Write<string>(animation->name);
file->Write<float>(animation->duration);
file->Write<float>(animation->frameRate);
file->Write<uint32>(animation->frameCount);
file->Write<uint32>(animation->keyframes.size());
for (shared_ptr<asKeyframe> keyframe : animation->keyframes)
{
file->Write<string>(keyframe->boneName);
file->Write<uint32>(keyframe->transforms.size());
file->Write(&keyframe->transforms[0], sizeof(asKeyframeData) * keyframe->transforms.size());
}
}
void AssimpTool::Init()
{
{
shared_ptr<Converter> converter = make_shared<Converter>();
converter->ReadAssetFile(L"Kachujin/Mesh.fbx");
converter->ExportMaterialData(L"Kachujin/Kachujin");
converter->ExportModelData(L"Kachujin/Kachujin");
converter->ReadAssetFile(L"Kachujin/Idle.fbx");
converter->ExportAnimationData(L"kachujin/Idle");
converter->ReadAssetFile(L"Kachujin/Run.fbx");
converter->ExportAnimationData(L"kachujin/Run");
converter->ReadAssetFile(L"Kachujin/Slash.fbx");
converter->ExportAnimationData(L"kachujin/Slash");
}
}
결과

텍스처 저장경로 수정
Resources/Textrues폴더에 Kachujin의 Texture가 저장되어 있음
- 우리가 원하던 방향은
Resources/Textrues/Kachujin폴더에 .xml과 같이 Texture가 저장되길 원함
pathStr을 saveFolder + fileName이 아닌 filesystem을 활용해서 가져오기
string Converter::WriteTexture(string saveFolder, string file)
{
string fileName = filesystem::path(file).filename().string();
string folderName = filesystem::path(saveFolder).filename().string();
const aiTexture* srcTexture = _scene->GetEmbeddedTexture(file.c_str());
if (srcTexture)
{
string pathStr = (filesystem::path(saveFolder) / fileName).string();
if (srcTexture->mHeight == 0)
{
shared_ptr<FileUtils> file = make_shared<FileUtils>();
file->Open(Utils::ToWString(pathStr), FileMode::Write);
file->Write(srcTexture->pcData, srcTexture->mWidth);
}
else
{
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
desc.Width = srcTexture->mWidth;
desc.Height = srcTexture->mHeight;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_IMMUTABLE;
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = srcTexture->pcData;
ComPtr<ID3D11Texture2D> texture;
HRESULT hr = DEVICE->CreateTexture2D(&desc, &subResource, texture.GetAddressOf());
CHECK(hr);
DirectX::ScratchImage img;
::CaptureTexture(DEVICE.Get(), DC.Get(), texture.Get(), img);
hr = DirectX::SaveToDDSFile(*img.GetImages(), DirectX::DDS_FLAGS_NONE, Utils::ToWString(fileName).c_str());
CHECK(hr);
}
}
else
{
string originStr = (filesystem::path(_assetPath) / folderName / file).string();
Utils::Replace(originStr, "\\", "/");
string pathStr = (filesystem::path(saveFolder) / fileName).string();
Utils::Replace(pathStr, "\\", "/");
::CopyFileA(originStr.c_str(), pathStr.c_str(), false);
}
return fileName;
}
결과
Resources/Textrues/Kachujin폴더에 잘 들어와 있는 것을 볼 수 있음

참고 강의
https://www.inflearn.com/courses/lecture?courseId=329791&type=LECTURE&unitId=161147&tab=curriculum&subtitleLanguage=ko