Graphics Techniques

[Animation] 캐릭터 애니메이션의 원리

doyyy_0 2025. 3. 20. 14:45

캐릭터를 형성할 때는 bone(뼈)와 mesh들로 이루어져 있습니다.

 

다음 그림과 같은데요

Introduction to 3D Game Programming with DirectX 12 p731

뼈들을 보면 관절을 중심으로 모두 연결되어 있는 것을 볼 수있습니다. 뼈들은 트리구조로 이루어져 있으며 보통 골반이 루트노드가 됩니다. 

 

애니메이션을 그리는 순서는 다음과 같습니다.

 

1) FindDeformingBones => 모든 메쉬에 대해서 버텍스에 영향을 주는 뼈들의 목록을 만든다.
2) UpdateBoneIDs => 트리 구조를 따라 업데이트 순서대로 뼈들의 인덱스를 결정한다
3) ProcessNode => 메쉬의 vertex들을 어떤뼈에 얼만큼 영향 받는지 meshData에 넣어줌
4) ReadAnimation => 뼈 수랑 인덱스 부여됐으니 그걸 이용해서 애니메이션 데이터 넣음
=> AnimationClip에 넣음

 

1) 모든 메쉬는 뼈에 의해 영향을 받습니다. 한 메쉬당 영향을 받는 뼈가 저장되어있지요. 모든 메쉬들을 확인하며 뼈들의 목록을 만듭니다.

2) 그 뼈 목록을 트리순서에 따라 인덱스를 부과합니다. 루트노드가 0이고 그 다음 자식노드를 1 이렇게 부과합니다. 이렇게 해야만 부모가 누구인지 추후에 확인할 수 있습니다

3) 메쉬에는 영향을 받는 뼈들이 무엇인지와 메쉬를 이루는 vertex들이 존재합니다.  한 노드당 각 메쉬들의 뼈를 모두 확인하고, 각 뼈가 영향을 주는 vertex와 그에 해당하는 weight를 저장합니다. 이걸 다 진행하면 한 모델의 모든 메쉬에 대한 vertex와 그에 해당하는 weight가 vertexShader에 전달이 됩니다.

4) 한 AnimationData에 여러 AnimationClip이 있고, 각 클립에 알맞은 데이터를 넣어줍니다. 데이터는 key[boneId][frame]으로 이루어져 있는데 예를들어 3번뼈의 30프레임에 해당하는 position, rotation, scale값을 넣어줍니다. 이 과정은 위에서 이미 뼈 순서대로 인덱스를 부여했기 때문에 가능한 것입니다

 

 

애니메이션 업데이트는 한 프레임이 실행 될 때마다 업데이트 해주는데, 업데이트 될 때 어떤 애니메이션 클립이 이용되는지 설정하고 몇번째 프레임에 있는지 확인하여 데이터를 넣어줍니다. 4)번과정에서 AnimationClip에 데이터가 저장되어 있으니 가져다가 사용하면 됩니다. 이 때 boneTransforms[boneId] 즉, 몇번째 뼈에 어떤 변환과정을 넣어야할지 결정합니다. 코드는 다음과 같습니다.

boneTransforms[boneId] = key.GetTransform() * parentMatrix;

흔히 노드 순서대로라고 하면 부모 행렬이 앞에 와야할 것 같지만,

Introduction to 3D Game Programming with DirectX 12

 

이 순서대로 합니다. 

Introduction to 3D Game Programming with DirectX 12

 

 

뼈의 움직임을 보면 부모축을 기준으로 자식 뼈가 움직입니다. 이 때 부모가 움직이고 자식을 움직이는 구조가 아니라, 자식이 움직이고 그것을 부모가 다시 움직여주는 구조이죠. 따라서 자식 변환이 먼저 일어나고 뒤에 부모 변환을 해주는 구조입니다.

 

버텍스 쉐이더에서는 가중치에 따라 vertex를 움직여 조정해줍니다.

// Uniform Scaling 가정
// (float3x3)boneTransforms 캐스팅으로 Translation 제외
for(int i = 0; i < 8; ++i)
{
    // TODO:
    float4x4 boneMatrix = boneTransforms[indices[i]];

    // 현재 본의 가중치 적용
    posModel += mul(float4(input.posModel,1.0) , boneMatrix).xyz * weights[i];
    normalModel += mul(input.normalModel, (float3x3)boneMatrix) * weights[i];
    tangentModel += mul(input.tangentModel, (float3x3)boneMatrix) * weights[i];

}

 

여기서 normal과 tangent는 벡터이므로 translation을 제외해줍니다.

normal은 uniform Scaling이므로 inverse를 취한 후 Transpose를 해줄 필요는 없습니다.

 

강의 출처 : 홍정모 그래픽스 새싹코스 part4

https://www.honglab.ai/