1️⃣ 애니메이션 데이터를 불러오는 구조

📌 왜 Binary 파일을 사용하나요?

FBX 파일은 Assimp를 통해 불러올 수 있지만, 매번 파싱하는 건 속도와 효율성에서 큰 손해입니다.
따라서 Tank.anim이라는 Binary 애니메이션 파일을 미리 저장하고, 런타임에는 빠르게 읽어오는 구조를 설계합니다.

📦 코드로 보는 모델/애니메이션 로딩

Model* model = new Model();
model->ReadMaterial(L"Tank/Tank.material");
model->ReadModel(L"Tank/Tank.mesh");
model->ReadClip(L"Tank/Tank.anim");
  • ReadMaterial() → 머티리얼 데이터를 로딩합니다 (텍스처, 샘플러 등 포함).
  • ReadModel() → 메시와 본(Bone) 계층 구조를 포함한 모델 구조를 읽어옵니다.
  • ReadClip() → 애니메이션 키프레임 데이터를 읽어옵니다.

애니메이션 클립이 성공적으로 로딩되었다면, 이제 모델에 적용할 수 있습니다.


2️⃣ 모델을 애니메이션과 함께 렌더링하는 구조

🔧 ModelRender와 ModelAnimator

  • ModelRender 클래스는 정적 모델의 렌더링 전담 클래스입니다.
  • 여기에 애니메이션 기능을 넣은 확장 클래스가 ModelAnimator입니다.
ModelAnimator* tank = new ModelAnimator(model);
tank->Position(0, 0, 0);
tank->Scale(0.01f, 0.01f, 0.01f);
tank->PlayClip(0); // 0번째 클립 재생

여기서 중요한 점은 ModelAnimator는 단순히 Bone 정보를 사용하는 것이 아니라, 현재 시간에 따라 Bone Transform을 계산하고, 쉐이더에 넘겨줘야 한다는 점입니다.


3️⃣ 애니메이션 시간 계산과 본 변환 처리

애니메이션은 시간 기반으로 Bone의 위치/회전/스케일이 바뀌는 구조입니다.
이를 위해 ModelAnimator::Update()에서 매 프레임마다 애니메이션 시간을 갱신하고, 해당 시간에 맞는 Bone Transform을 구합니다.

🧠 핵심 로직 설명

void ModelAnimator::Update()
{
    if (clip == nullptr)
        return;

    // 1. 시간 갱신
    playTime += Time::Delta() * speed;

    // 2. 루프 처리
    if (playTime > clip->Duration())
    {
        if (bLoop == true)
            playTime = fmod(playTime, clip->Duration());
        else
            playTime = clip->Duration();
    }

    // 3. 현재 시간의 Bone Transform 계산
    transforms.clear();
    clip->GetKeyframe(playTime, transforms);
}
  • Time::Delta() : 프레임 간 시간 차이
  • clip->Duration() : 전체 애니메이션 길이
  • GetKeyframe(time, out) : 현재 시간에 대응되는 본 행렬 계산

이렇게 계산된 transforms는 GPU에 전송되어 스키닝을 처리하는 데 사용됩니다.


4️⃣ 스키닝(Skinning)이란?

스키닝은 모델 정점(Vertex)이 여러 본(Bone)의 영향을 받아 움직이도록 하는 기법입니다.
예를 들어, 캐릭터의 팔 정점은 어깨 본과 팔꿈치 본에 걸쳐 영향을 받습니다. 이때 각 본의 영향력을 숫자로 나타낸 것이 BlendWeight입니다.

📐 수학적으로 보면?

정점 위치 =
= bone1의 변환 가중치1 + bone2의 변환 가중치2 + ...


5️⃣ 쉐이더에서 스키닝 구현

쉐이더에서 본 행렬을 적용하기 위해 먼저 C++ 측에서 본 데이터를 넘겨야 합니다.

cbuffer BoneBuffer : register(b1)
{
    float4x4 boneTransforms[MAX_MODEL_TRANSFORMS];
};

그리고 정점 쉐이더에서는 다음과 같이 처리합니다:

float4x4 skinTransform = 0;

[unroll]
for (int i = 0; i < 4; i++)
{
    int index = input.BlendIndices[i];
    float weight = input.BlendWeights[i];
    skinTransform += weight * boneTransforms[index];
}

output.Position = mul(input.Position, skinTransform);

💡 핵심 포인트

  • 정점 하나당 최대 4개의 본을 가질 수 있음.
  • 본 인덱스와 가중치를 통해 해당 본 변환을 가중 합산.
  • 그 결과를 원래 정점 위치에 적용하면, 해당 본의 움직임에 맞춰 정점도 움직임.

6️⃣ 트윈 애니메이션: 애니메이션 간 부드러운 전환

애니메이션 A에서 B로 즉시 바뀌면 어색한 전환이 발생합니다.
이를 막기 위해 두 클립을 시간을 두고 보간하는 기능이 트윈 애니메이션입니다.

animator->PlayTweenMode(0, 1, 2.0f); // 0번 클립에서 1번 클립으로 2초간 전환

내부적으로는 두 애니메이션의 Bone 데이터를 Lerp/Slerp 보간하여 처리합니다.

Matrix S = XMMatrixScalingFromVector(Lerp(scale1, scale2, tweenTime));
Matrix R = XMMatrixRotationQuaternion(Slerp(rot1, rot2, tweenTime));
Matrix T = XMMatrixTranslationFromVector(Lerp(trans1, trans2, tweenTime));

Matrix result = S * R * T;

🧩 전체 구조

컴포넌트설명
ModelFBX 기반 모델/애니메이션/머티리얼 로딩
ModelRender정적인 메시 렌더링 담당
ModelAnimator애니메이션 시간 처리, 본 행렬 계산, 쉐이더 전송
ModelSkinning.fx스키닝 계산 전용 쉐이더 (boneTransforms 적용)
ModelAnimation.fx트윈 애니메이션 포함 애니메이션 렌더링 쉐이더

profile
李家네_공부방

0개의 댓글