void AssimpTool::Init()
{
shared_ptr<Converter> converter = make_shared<Converter>();
converter->ReadAssetFile(L"Tank/Tank.fbx");
converter->ExportMaterialData(L"Tank/Tank");
converter->ExportModelData(L"Tank/Tank");
}
실행하면 .mesh, .xml 형식의 커스텀 바이너리/텍스트 파일이 생성됩니다. 이는 FBX로부터 추출한 모델 및 머티리얼 정보를 우리 엔진 형식으로 저장한 결과입니다.
void StaticMeshDemo::CreateTank()
{
shared_ptr<Model> model = make_shared<Model>();
model->ReadModel(L"Tank/Tank");
model->ReadMaterial(L"Tank/Tank");
_obj = make_shared<GameObject>();
_obj->GetOrAddTransform()->SetPosition(Vec3(0, 0, 50));
_obj->GetOrAddTransform()->SetScale(Vec3(0.1f)); // 너무 크면 축소
_obj->AddComponent(make_shared<ModelRenderer>(_shader));
_obj->GetModelRenderer()->SetModel(model);
_obj->GetModelRenderer()->SetPass(1); // 와이어프레임 출력용
}
이제 실행하면 화면에 탱크가 출력됩니다. 하지만 모양이 이상하게 왜곡되어 중앙에 몰려 있는 듯한 모습이 보입니다.
탱크 모델은 여러 개의 메시로 구성되어 있으며, 각 메시들은 Bone(Node)를 기준으로 좌표계가 설정되어 있습니다.
하지만 현재 우리는 Bone 정보를 사용하지 않고 메시를 출력했기 때문에, 각 파츠가 자신의 로컬 좌표계 기준으로만 출력되어 모두 원점에 겹쳐보이게 된 것입니다.
#define MAX_MODEL_TRANSFORMS 50
cbuffer BoneBuffer
{
matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
};
uint BoneIndex;
각 메시가 어느 Bone에 소속되어 있는지 알려주는 인덱스를 통해, 메시 출력 전에 해당 Bone의 루트 기준 Transform 행렬을 곱해줍니다.
struct BoneDesc { Matrix transform[50]; };
void RenderManager::PushBoneData(const BoneDesc& desc)
{
_boneDesc = desc;
_boneBuffer->CopyData(_boneDesc);
_boneEffectBuffer->SetConstantBuffer(_boneBuffer->GetComPtr().Get());
}
BoneDesc boneDesc;
for (uint32 i = 0; i < _model->GetBoneCount(); i++)
{
auto bone = _model->GetBoneByIndex(i);
boneDesc.transform[i] = bone->transform;
}
RENDER->PushBoneData(boneDesc);
// 메시 그리기 시 BoneIndex 전달
_shader->GetScalar("BoneIndex")->SetInt(mesh->boneIndex);
이제 GPU는 BoneIndex와 BoneTransforms를 바탕으로 로컬 좌표를 루트 기준으로 보정할 수 있게 됩니다.
output.position = mul(input.position, BoneTransforms[BoneIndex]); // 루트 기준 보정
output.position = mul(output.position, W); // 월드 변환
output.position = mul(output.position, VP); // 뷰 & 프로젝션
이제 탱크는 모든 파츠가 본래의 위치와 계층 구조를 지켜 올바르게 배치된 모습으로 출력됩니다.
포신, 바퀴, 차체 등이 각각의 노드에 따라 위치가 조정되어 계층적 구조가 표현된 결과입니다.
Converter::ReadMeshData()의 정점은 자기 자신을 기준으로 한 로컬 좌표계를 따릅니다.Converter::ReadModelData()에서 구한 bone->transform = bone->transform * parentTransform은 루트를 기준으로 하는 좌표계로의 변환 행렬입니다.VertexShader에서 한 번 곱해줌으로써, 정점들을 정확한 위치에 배치하게 됩니다.