1. 3D Direct Application
1.1 Character Animation
1.1.1 Character Animation System
1.1.1.1 Frame Hierarchy
- 사물은 여러 개의 부분 또는 부품들이 일종의 부모-자식 관계로 연결되어 있는 것들이 많음
- 한 부모부품에는 하나 이상의 자식부품이 연결, 각각의 자식부품은 각자 방식으로 움직이나 부모가 함께 움직여야 함
- 하나의 팔은 Uppper Arm, Fore Arm, Hand 라는 세 부품으로 나눌 수 있음
- 손은 손목 관절을 중심으로 독자적으로 회전하나 팔뚝이 팔꿈치 관절에 대해 회전하면 손도 반드시 그에 따라 움직임
- 위팔이 어깨 관절에 대해 회전하면 팔뚝도 그에 따라 회전하며 팔뚝이 회전하므로 손도 그에 따라 회전
- 하나의 물체를 장면에 배치하되 물체 자신의 위치는 물론 그 조상들의 위치에서 기초해서 배치하는 방법을 사용
- 계통구조의 한 물체가 주어졌을 때 그것을 세계 공간으로 어떻게 변환해야 하는 지 확인
- 한 부품의 위치와 방향은 조상들에도 영향을 받으므로조상들의 변환도 반드시 고려
- 각 국소 좌표계를 그 부모 좌표계를 기준으로 서술 가능, 즉 뼈대 0의 좌표계의 경우 세계 공간을 기준으로 서술
- 부모 좌표계와 자식 좌표계를 연관, 자식의 공간에서 부모의 공간으로 변환을 변환 행렬로 수행
- 팔 계통구조의 한 물체를 변환하기 위해 물체가 세계 공간에 도달할 때까지 모든 조상의 부모 변환을 오름차순으로 적용
1.1.1.2 Mesh Skinning
- 연결된 뼈대들의 사슬을 골격, 캐릭터 애니메이션을 구동하는 자연스러운 계통구조를 제공
- 골격을 외부의 스킨, 즉 표피가 감싸고 있음, 표피는 3차원 기하구조로 정의
- 애니메이션 초기에 표피 정점들은 결속 공간을 기준, 결속 공간은 전체 표피가 정의된 국소 좌표계
- 애니메이션되는 골격에 표피를 입혀 캐릭터 전체의 애니메이션을 표현하는 기법
1.1.1.2.1 to - root
- 뿌리 좌표계에서 세계 공간 좌표계로의 변환을 개별적인 단계로 수행
- 각 뼈대마다 세계 공간으로의 변환 행렬이 아닌 뿌리 공간으로의 변환 행렬을 구함
- 뿌리에서 시작해서 개별 뼈대로 내려가는 것이 더 효율적임
- 하향식으로 운행하면 항상 부모의 뿌리 변환이 자식의 뿌리 변환 보다 먼저 계산되므로 문제X
1.1.1.2.2 Offset Transformation
- 스키닝에서 한 뼈대에 영향을 받는 정점들의 기준 좌표계가 변환을 원하는 뼈대의 국소 좌표계와 동일하지 않다는 점
- 정점들을 결속 공간에서 그 정점들에 영향을 주는 뼈대의 공간으로 변환
- 즉 결속 공간인 Root 뼈대에서 움직임을 원하는 손의 뼈에 대한 공간으로 변환
- 정점들을 어떤 임의의 뼈대 B의 오프셋 행렬로 변환함으로써 그 정점들을 결속 공간에서 B의 뼈대 공간으로 옮김
- 정점들이 B의 뼈대 공간에 있게 되면, B의 뿌리 변환을 이용하여 정점들을 캐릭터 공간에서 현재의 애니메이션 자세에 맞는 위치로 다시 배치, 뼈대의 오프셋 변환에 뼈대의 뿌리 변환을 결합한 것을 최종 변환이라 부름
1.1.1.2.3 Animation Clip
- 애니메이션은 주어진 한 순간에서의 물체의 위치와 방향, 축척을 지정하는 키 프레임들의 시간순 목록으로 정의
- 키 프레임들 사이를 보간함으로써 임의의 시간에서의 물체의 위치와 방향, 축척을 알아냄
- 키 프레임 애니메이션 시스템을 골격의 애니메이션으로 확장
- 단일 물체의 애니메이션이 뼈대 하나를 변환하는 것이라면, 골격 애니메이션은 연결된 여러 개의 뼈대들을 변환
- 골격 애니메이션에서 각 뼈대가 독립적으로 움직일 수 있다고 가정
- 골격 애니메이션을하려면 각 뼈대를 국소적으로 애니메이션하면 되는 것, 뼈대의 국소 애니메이션을 마친 후 뼈대의 조상들의 움직임을 고려해서 뼈대를 뿌리 공간으로 변환
struct AnimationClip;
- 캐릭터는 다양한 동작을 할 수 있음, 개별 동작을 구성하는 뼈대별 애니메이션들의 집합을 애니메이션 클립이라 부름
- 인간의 경우 걷기, 달리기, 싸우기, 숙이기, 뛰기 같은 애니메이션 클립들이 있을 것
float GetClipStartTime()const; /* 클립의 모든 뼈대 중 가장 이른 시작 시간을 돌려줌 */
float GetClipEndTime()const; /* 클립의 모든 뼈대 중 가장 늦은 종료 시간을 돌려줌 */
void Interpolate(float t, std::vector<XMFLOAT4X4>& boneTransforms)const;
std::vector<BoneAnimation> BoneAnimations;
1.1.2 Final Transformation
- 앞에서 언급했듯이 각 뼈대마다 정점들을 결속 공간에서 뼈대 공간으로 변환하는 오프셋 변환이 있어야 함
- 또한 골격 계통구조를 표현하는 수단도 필요
class SkinnedData
{
/* ... */
void GetFinalTransforms(const std::string& clipName, float timePos,
std::vector<XMFLOAT4X4>& finalTransforms)const;
}
- 계통구조를 하나의 정수 배열로 표현하되, 배열의 i번째 원소가 i번째 뼈대의 부모의 Index가 되도록 설정
- 뼈대의 번호 i는 해당 애니메이션 클립에서 그 뼈대의 Bone Animation 인스턴스의 Index와 일치
- 오프셋 변환 배열에서 그 뼈대의 오프셋 변환의 Index도 일치
- 뿌리 뼈대는 항상 0번 원소이며 부모는 없음 i번째 조부모의 오프셋 변환을 얻는 코드 생성
int parentIndex = mBoneHierarchy[i]; int grandparentIndex = mBoneHierarchy[parentIndex];
XMFLOAT4X4 offset = mBoneOffsets[grandParentIndex];
AnimationClip& clip = mAnimations["attack"];
BoneAnimation& anim = clip.BoneAnimations[grandParentIndex];
- 위의 코드를 바탕으로 각 뼈대마다 최종 변환을 구하는 함수 생성
- 뼈대의 개수를 이용하여 부모 변환행렬을 수행할 배열을 생성하여 클립의 모든 뼈대를 주어진 시간에 맞게 보간
uint32 numBones = _boneOffsets.size(); std::vector<XMFLOAT4X4> toParentTransforms(numBones);
auto clip = _animations.find(clipName); clip->second.Interpolate(timePos, toParentTransforms);
- 골격의 계통구조를 훑으면서 모든 뼈대를 뿌리 공간으로 변환
- 뿌리 뼈대의 Index의 경우 0이고 뿌리 뼈대에는 부모가 없으므로 뿌리 변환은 수행하지 않음
std::vector<XMFLOAT4X4> toRootTransforms(numBones); toRootTransforms[0] = toParentTransforms[0];
- 자식 뼈대들의 뿌리 변환을 구함
- 자식 행렬 좌표계를 기준으로 부모 행렬을 곱해 자식 뼈대에서 부모 뼈대로 이동할 수 있는 행렬 생성
- 반대로 생각하면 부모 뼈대, 즉 뿌리 뼈대에서 자식 뼈대로 이동할 때 이 행렬을 곱하게 되면 자식 뼈대로 이동
for (uint32 i = 1; i < numBones; ++i)
{
XMMATRIX toParent = XMLoadFloat4x4(&toParentTransforms[i]);
int parentIndex = _boneHierarchy[i];
XMMATRIX parentToRoot = XMLoadFloat4x4(&toRootTransforms[parentIndex]);
XMMATRIX toRoot = XMMatrixMultiply(toParent, parentToRoot);
XMStoreFloat4x4(&toRootTransforms[i], toRoot);
}
- 뼈대 오프셋 변환을 앞에서 곱해 최종 변환을 구함
- 뿌리 뼈대에서 부터 위에서 구한 뿌리 변환을 통해 자식 뼈대의 위치를 곱하여 자식 뼈대의 위치를 알 수 있음
- 즉 세계 행렬의 자식 뼈대의 위치를 알 수 있음
for (uint32 i = 0; i < numBones; ++i)
{
XMMATRIX offset = XMLoadFloat4x4(&_boneOffsets[i]);
XMMATRIX toRoot = XMLoadFloat4x4(&toRootTransforms[i]);
XMStoreFloat4x4(&finalTransforms[i], XMMatrixMultiply(offset, toRoot));
}
'C++ Algorithm & Study > Game Math & DirectX 11' 카테고리의 다른 글
[Direct11] 48. 3D Direct Application - Character Animation #3 (0) | 2023.07.04 |
---|---|
[Direct11] 47. 3D Direct Application - Character Animation #2 (0) | 2023.07.04 |
[Direct11] 45. 3D Direct Application - Mesh Basic Model (0) | 2023.07.04 |
[Direct11] 44. 3D Direct Application - Shadow Mapping #2 (0) | 2023.07.03 |
[Direct11] 43. 3D Direct Application - Shadow Mapping #1 (0) | 2023.07.03 |