애니메이션은 그 전에 했던 뼈대에 대한 이해가 상당히 중요하다.
모델을 로드할 때 좌표계를 변환해서 최상위 부모를 기준으로 한 좌표계로 변환행렬을 구한 후 이걸 Vs 단계에서 처리했다. 이 개념이 애니메이션과 정확히 동일하다.
2D의 경우는 여러개의 이미지를 연속해서 보여주는 반면 3D의 경우 관절을 움직여서 움직임을 표현하는 식이다.
스키닝
먼저 모델을 로드할것인데 사용할 모델은 kachujin으로 가장 유명한 모델을 이용할 것이다.
그런데 작성한 코드에 Skin을 추출하는 코드가 없기에 이걸 먼저 작성하고 가겠다.
여기서 말하는 Skin은 Vertex들이 어떤 뼈대에 따라 움직일 것인지에 대한 정보를 의미한다.
애니메이션은 뼈대를 움직여서 표현하는데 뼈대가 변함과 동시에 수많은 Vertex들이 같이 움직이여한다. 이를 계산하기 위해 Vertex에 영향을 줄 뼈대에 대한 정보를 지정하고 그에따라 움직이게 하는 것이다.
void Converter::ReadSkinData()
{
for (uint32 i = 0; i < _scene->mNumMeshes; i++)
{
aiMesh* srcMesh = _scene->mMeshes[i];
if (srcMesh->HasBones() == false)
continue;
shared_ptr<asMesh> mesh = _meshes[i];
vector<asBoneWeights> tempVertexBoneWeight;
tempVertexBoneWeight.resize(mesh->vertices.size());
// Bone을 순회하면서 연관된 Vertexid, Weight를 구해서 기록한다.
for (uint32 b = 0; b < srcMesh->mNumBones; b++)
{
aiBone* srcMeshBone = srcMesh->mBones[b];
uint32 boneIndex = GetBoneIndex(srcMeshBone->mName.C_Str());
for (uint32 w = 0; w < srcMeshBone->mNumWeights; w++)
{
uint32 index = srcMeshBone->mWeights[w].mVertexId;
float weight = srcMeshBone->mWeights[w].mWeight;
tempVertexBoneWeight[index].AddWeights(boneIndex, weight);
}
}
for (uint32 v = 0; v < tempVertexBoneWeight.size(); v++)
{
tempVertexBoneWeight[v].Normalize();
asBlendWeight blendWeight = tempVertexBoneWeight[v].GetBlendWeights();
mesh->vertices[v].blendIndices = blendWeight.indices;
mesh->vertices[v].blendWeights = blendWeight.weights;
}
}
}
struct asBlendWeight
{
void Set(uint32 index, uint32 boneIndex, float weight)
{
float i = (float)boneIndex;
float w = weight;
switch (index)
{
case 0: indices.x = i; weights.x = w; break;
case 1: indices.y = i; weights.y = w; break;
case 2: indices.z = i; weights.z = w; break;
case 3: indices.w = i; weights.w = w; break;
}
}
Vec4 indices = Vec4(0, 0, 0, 0);
Vec4 weights = Vec4(0, 0, 0, 0);
};
struct asBoneWeights
{
void AddWeights(uint32 boneIndex, float weight)
{
if (weight <= 0.0f) return;
auto findit = std::find_if(boneWeight.begin(), boneWeight.end(),
[weight](const Pair& p) { return weight > p.second; });
boneWeight.insert(findit, Pair(boneIndex, weight));
}
asBlendWeight GetBlendWeights()
{
asBlendWeight blendWeights;
for (uint32 i = 0; i < boneWeight.size(); i++)
{
if (i >= 4)break;
blendWeights.Set(i, boneWeight[i].first, boneWeight[i].second);
}
return blendWeights;
}
void Normalize()
{
if (boneWeight.size() >= 4)
boneWeight.resize(4);
float totalWeight = 0.f;
for (const auto& item : boneWeight)
{
totalWeight += item.second;
}
float scale = 1.f / totalWeight;
for (auto& item : boneWeight)
{
item.second *= scale;
}
}
using Pair = pair<int32, float>;
vector<Pair> boneWeight;
};
이렇게 하면 각 뼈대에 따른 Vertex 들이 어떻게 움직이는지에 대한 정보를 메모리에 올릴 수 있다.
이걸 보기 편하게 csv 파일로 추출하자.
//Write CSV File
{
FILE* file;
::fopen_s(&file, "../Vertices.csv", "w");
for (shared_ptr<asBone>& bone : _bones)
{
string name = bone->name;
::fprintf(file, "%d,%s\n", bone->index, bone->name.c_str());
}
::fprintf(file, "\n");
for (shared_ptr<asMesh>& mesh : _meshes)
{
string name = mesh->name;
::printf("%s\n", name.c_str());
for (UINT i = 0; i < mesh->vertices.size(); i++)
{
Vec3 p = mesh->vertices[i].position;
Vec4 indices = mesh->vertices[i].blendIndices;
Vec4 weights = mesh->vertices[i].blendWeights;
::fprintf(file, "%f,%f,%f,", p.x, p.y, p.z);
::fprintf(file, "%f,%f,%f,%f,", indices.x, indices.y, indices.z, indices.w);
::fprintf(file, "%f,%f,%f,%f\n", weights.x, weights.y, weights.z, weights.w);
}
}
::fclose(file);
}
대략 이렇게 확인할 수 있다.
애니메이션 데이터
이전 작업과 동일하게 fbx에 있는 애니메이션 데이터를 가공해 우리만의 포멧으로 저장하고 메모리에 올리는 작업을 해보자.
struct asKeyframeData
{
float time;
Vec3 scale;
Quaternion rotation;
Vec3 transform;
};
struct asKeyframe
{
string boneName;
vector<asKeyframeData> transforms;
};
struct asAnimation
{
string name;
uint32 frameCount;
float frameRate;
float duration;
vector<asKeyframe> keyframes;
};
std::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;
}
std::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();
// Position
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;
}
// Rotation
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;
}
// Scale
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);
}
// Keyframe 늘려주기
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());
}
}
'C++ > DirectX 11' 카테고리의 다른 글
[DirectX] 인스턴싱 - 드로우 콜 (1) | 2024.10.10 |
---|---|
[DirectX] 애니메이션 - 모델 애니메이션 (0) | 2024.10.09 |
[DirectX] 모델 - 모델 띄우기, 계층 구조, ImGuI (1) | 2024.09.30 |
[DirectX] 모델 - Assimp, Material 로딩, Mesh 로딩 (0) | 2024.09.27 |
[DirectX] Lighting & Material - Material, Normal Mapping (0) | 2024.09.25 |