📌 스키닝(Skinning)이란?

스키닝은 정점(Vertex)이 어떤 뼈대(Bone)에 얼마나 영향을 받는지를 정의하는 기술입니다.

✦ 왜 필요한가?

  • 2D에서는 이미지(Sprite)를 프레임별로 교체해 애니메이션을 표현하지만,
  • 3D에서는 하나의 메쉬 모델이 뼈대의 움직임에 따라 함께 움직입니다.

그래서 정점은 뼈대의 움직임을 따라가야 하며, 이 영향 비율을 스키닝 데이터로 저장해야 합니다.

✦ 핵심 구성

  • blendIndices: 이 정점이 영향을 받는 뼈의 번호
  • blendWeights: 해당 뼈들로부터 받는 영향력의 가중치 (0.0 ~ 1.0)

🛠 구조체 설계 (AsTypes.h)

정점에 뼈 정보(번호/가중치)를 저장하고 쉐이더에 넘길 수 있도록 하기 위한 구조체입니다.

struct asBlendWeight
{
	Vec4 indices = Vec4(0, 0, 0, 0);
	Vec4 weights = Vec4(0, 0, 0, 0);

	void Set(uint32 index, uint32 boneIndex, float weight);
};

뼈 인덱스와 가중치 목록을 저장하는 중간 구조체:

struct asBoneWeights
{
	using Pair = pair<int32, float>; // (boneIndex, weight)
	vector<Pair> boneWeights;

	void AddWeights(uint32 boneIndex, float weight);
	void Normalize();
	asBlendWeight GetBlendWeights();
};
  • 최대 4개의 뼈 정보만 유지 (쉐이더 Vec4 제한)
  • Normalize를 통해 가중치 합을 1로 맞춤

🔍 스킨 정보 추출 (Converter::ReadSkinData)

Assimp에서는 정점이 아닌 뼈에 연결된 정점 정보를 제공하므로, 이를 역으로 정점 기준으로 재정렬해야 합니다.

전체 흐름 요약:

  1. Mesh를 순회하며 Bone이 있는지 확인
  2. Bone을 순회하며 연결된 VertexId, Weight 추출
  3. 정점 기준으로 정리해서 blendIndices, blendWeights에 저장
void Converter::ReadSkinData()
{
	for (uint32 i = 0; i < _scene->mNumMeshes; i++)
	{
		if (!_scene->mMeshes[i]->HasBones()) continue;

		auto mesh = _meshes[i];
		vector<asBoneWeights> tempVertexBoneWeights(mesh->vertices.size());

		for (uint32 b = 0; b < srcMesh->mNumBones; b++)
		{
			aiBone* bone = srcMesh->mBones[b];
			uint32 boneIndex = GetBoneIndex(bone->mName.C_Str());

			for (uint32 w = 0; w < bone->mNumWeights; w++)
			{
				uint32 vertexId = bone->mWeights[w].mVertexId;
				float weight = bone->mWeights[w].mWeight;

				tempVertexBoneWeights[vertexId].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로 정점 스키닝 데이터 출력 (ExportModelData)

모든 정점의 위치 + 뼈 인덱스 + 가중치를 Vertices.csv로 출력합니다.

::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);

엑셀로 열어보면 정점마다 어떤 뼈에 얼마만큼 영향을 받는지 쉽게 확인할 수 있습니다.


👨‍💻 실습: 인간형 모델 적용

Kachujin/Mesh.fbx 모델을 불러와서 ReadModelData와 ReadSkinData를 순차적으로 호출합니다.

void AssimpTool::Init()
{
	auto converter = make_shared<Converter>();

	converter->ReadAssetFile(L"Kachujin/Mesh.fbx");
	converter->ExportMaterialData(L"Kachujin/Kachujin");
	converter->ExportModelData(L"Kachujin/Kachujin");
}

모델에서 정점 - 뼈대 매핑 관계를 완전히 추출하게 됩니다.


🧠 핵심

개념설명
스키닝정점이 어떤 뼈의 영향을 얼마나 받는지를 표현하는 기법
blendIndices영향을 받는 뼈의 번호들 (최대 4개)
blendWeights뼈의 영향력 비율 (합 = 1)
asBoneWeights뼈번호-가중치 쌍을 정점 기준으로 모은 구조체
ReadSkinDataAssimp에서 정점-뼈대 매핑을 파싱해서 정점 구조체에 세팅

profile
李家네_공부방

0개의 댓글