일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 구현
- 브루트포스
- 그리디 알고리즘
- DFS
- 우선순위 큐
- 유니티
- 백준
- 정렬
- 누적 합
- 재귀
- BFS
- ue5
- 문자열
- 투 포인터
- XR Interaction Toolkit
- Team Fortress 2
- 유니온 파인드
- c++
- 자료구조
- 스택
- 다이나믹 프로그래밍
- 시뮬레이션
- 백트래킹
- 알고리즘
- VR
- 트리
- Unreal Engine 5
- 다익스트라
- 수학
- 그래프
- Today
- Total
1일1알
DirectX11 학습 - 애니메이션 본문
3D 캐릭터에 애니메이션을 적용하려면 어떻게 해야할까?
2D라면 여러개의 이미지를 준비하여 이미지들을 빠르게 보여주면 애니메이션 효과를 낼 수 있지만, 3D는 수많은 정점들로 이루어져있어서 모든 장면의 정점 정보들을 따로 저장하는 것은 매우 비효율적일것이다.
3D 오브젝트에는 모델에 뼈대를 심어서 정점들이 어떤 뼈대의 영향을 받아서 움직일 것을 정하는 리깅 작업을 통해 애니메이션을 적용한다.
{
shared_ptr<Converter> converter = make_shared<Converter>();
converter->ReadAssetFile(L"Kachujin/Run.fbx");
converter->ExportAnimationData(L"Kachujin/Run");
}
// f : keyfrmae
// b : bone
Matrix toRootMatrix = bone->transform;
Matrix invGlobal = toRootMatrix.Invert(); // Root -> Bone 변환
int32 parentIndex = bone->parentIndex;
Matrix matParent = Matrix::Identity;
if (parentIndex >= 0)
matParent = tempAnimBoneTransforms[parentIndex];
tempAnimBoneTransforms[b] = matAnimation * matParent; // Bone -> Root 변환
// 결론 (Root -> Bone -> Root 변환)
_animTransforms[index].transforms[f][b] = invGlobal * tempAnimBoneTransforms[b];
애니메이션 변환 정보들은 앞서 다룬 계층구조가 있는 오브젝트와 마찬가지로 Bone들이 계층구조를 이루며 저장되어있다. 애니메이션 정보도 Root를 기준으로 하는 좌표로 변환하기 위한 행렬을 저장해 놓는다.
하지만 이 변환은 각각의 Bone에서 Root로 변환하는 행렬이고, 정점은 애초에 Root를 기준으로 하고있기 때문에 이 변환을 정점에 적용할 순 없다.
이전에 Bone에서 Root로 변환하는 행렬을 구한적이 있다. 이것의 역행렬을 적용하면 Root에서 Bone으로 돌아가는 변환이고, 여기서 또 Root로 가는 애니메이션 변환을 정점에 적용해주면 정점에 애니메이션 변환을 적용할 수 있다.
이 변환들은 각각의 키프레임, 각각의 Bone마다 따로 있고 이것을 2차원 배열로 저장한 뒤 쉐이더로 넘겨줘서 적용하고 World변환행렬을 적용하면 최종적으로 애니메이션을 적용한 좌표로 변환된다.
struct KeyframeDesc
{
int animIndex;
uint currFrame;
uint nextFrame;
float ratio;
float sumTime;
float speed;
float2 padding;
};
cbuffer KeyframeBuffer
{
KeyframeDesc Keyframes;
};
Texture2DArray TransformMap; // 2차원 애니메이션 변환행렬 정보
matrix GetAnimationMatrix(VertexTextureNormalTangentBlend_Instance input)
{
float indices[4] = { input.blendIndices.x, input.blendIndices.y, input.blendIndices.z, input.blendIndices.w };
float weights[4] = { input.blendWeights.x, input.blendWeights.y, input.blendWeights.z, input.blendWeights.w };
int animIndex = Keyframes.animIndex;
int currFrame = Keyframes.currFrame;
float4 c0, c1, c2, c3;
matrix curr = 0;
matrix transform = 0;
for (int i = 0; i < 4; i++)
{
c0 = TransformMap.Load(int4(indices[i] * 4 + 0, currFrame, animIndex, 0));
c1 = TransformMap.Load(int4(indices[i] * 4 + 1, currFrame, animIndex, 0));
c2 = TransformMap.Load(int4(indices[i] * 4 + 2, currFrame, animIndex, 0));
c3 = TransformMap.Load(int4(indices[i] * 4 + 3, currFrame, animIndex, 0));
curr = matrix(c0, c1, c2, c3); // 현재 프레임
transform += mul(weights[i], curr);
}
return transform;
}
MeshOutput VS(VertexTextureNormalTangentBlend_Instance input)
{
MeshOutput output;
matrix m = GetAnimationMatrix(input);
output.position = mul(input.position, m);
output.position = mul(output.position, input.world);
output.worldPosition = output.position.wyz;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, m);
output.normal = mul(output.normal, (float3x3)input.world);
output.tangent = mul(input.tangent, m);
output.tangent = mul(output.tangent, (float3x3)input.world);
return output;
}
위에서 만든 변환의 2차원 배열은 크기가 상당히 크기때문에 ConstanctBuffer로 넘겨줄 수 없다.
따라서 텍스쳐로 만들어준 뒤 쉐이더로 전달하고, 쉐이더에서 정보를 추출해서 사용한다.
GetAnimationMatrix 함수에서 넘겨받은 텍스쳐의 정보를 추출하고 각 정점에 어떤 Bone에 얼마만큼 영향을 받는지 저장된 정보를 이용해서 Root로 가는 최종 애니메이션 변환을 구해준다.
애니메이션이 적용된 모습이다. 핵심이라고 생각되는 부분만 작성하다 보니 설명이 조금 부족한 것 같긴 하다.
'DirectX11' 카테고리의 다른 글
DirectX11 학습 - StaticMesh (0) | 2024.03.31 |
---|---|
DirectX11 학습 - Normal Mapping (0) | 2024.03.30 |
DirectX11 학습 - Light (0) | 2024.03.30 |
DirectX11 학습 - Normal Vector (0) | 2024.03.28 |
DirectX11 학습 - 텍스쳐 입히기, 카메라 (0) | 2024.03.28 |