애니메이션
- 그동안 해왔던 방식은 스프라이트를 보여주는 방식으로 구현
- 하지만 3D에서는 각 노드, 뼈를 움직이는 것으로 애니메이션을 구현
- 뼈를 움직이면, 다른 뼈를 어떻게 처리할지 중요
- 정점을 사용하게 되면 모든 정점을 움직이기에는 용량이 너무크기 때문에 정점을 뼈대에 따라 움직이게 해준다.
- 한 정점이 여러 뼈대에 영향을 받을 경우에는 비율을 정해두고 영향을 받도록 설정
-> 스키닝
스키닝
- 어떠한 뼈에 영향을 받아서 우직일 것인지에 대해 정보
- 스킨에 대한 정보를 추불 해주기 위해 메쉬를 순회하면서 뼈대가 있다면 본을 순회하고, 연관된 정점과 가중치 정보를 전달해주는 방식
- 현재 정점이 아닌 뼈대에 정보가 있기에 정보를 추출해서 정점에 파싱 작업이 필요
AsTypes.h
- 정점마다 뼈번호와 가중치를 저장할 구조체를 선언과이를 파싱해줄 구조체 생성
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(boneWeights.begin(), boneWeights.end(),
[weight](const Pair& p) {return weight > p.second; });
boneWeights.insert(findit, Pair(boneIndex, weight));
}
asBlendWeight GetBlendWeights()
{
asBlendWeight blendWeights;
for (uint32 i = 0; i < boneWeights.size(); i++)
{
if (i >= 4)
break;
blendWeights.Set(i, boneWeights[i].first, boneWeights[i].second);
}
return blendWeights;
}
void Normalize()
{
if (boneWeights.size() >= 4)
boneWeights.resize(4);
float totalWeight = 0.f;
for (const auto& item : boneWeights)
totalWeight += item.second;
for (auto& item : boneWeights)
item.second /= totalWeight;
}
using Pair = pair<int32, float>;
vector<Pair> boneWeights;
};
Converter.cpp
- 본의 정보를 정점 기준으로 다시 모아서 정규화한 뒤, 각 정점의
blendIndices / blendWeights에 스키닝 데이터를 채워 넣는 함수
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> tempVertexBoneWeights;
tempVertexBoneWeights.resize(mesh->vertices.size());
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;
tempVertexBoneWeights[index].AddWeights(boneIndex, weight);
}
}
for (uint32 v = 0; v < tempVertexBoneWeights.size(); v++)
{
tempVertexBoneWeights[v].Normalize();
asBlendWeight blendWeight = tempVertexBoneWeights[v].GetBlendWeights();
mesh->vertices[v].blendIndices = blendWeight.indices;
mesh->vertices[v].blendWeights = blendWeight.weights;
}
}
}
csv파일 생성
Converter.cpp
void Converter::ExportModelData(wstring savePath)
{
wstring finalPath = _modelPath + savePath + L".mesh";
ReadModelData(_scene->mRootNode, -1, -1);
ReadSkinData();
{
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);
}
WriteModelFile(finalPath);
}
결과


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