🚶🏻Animation 실행
이전에는 애니메이션의 한 프레임, 한 프레임을 출력했다면 이젠 존재하는 프레임을 자동으로 전부 출력하여 애니메이션을 실행시키도록 구현하였다.

애니메이션의 시간 비율을 만들어서 비율이 1을 넘어가는 순간 다음 Frame을 출력하도록 만들었다.
그러나 이렇게 현재 프레임만을 출력하면 언뜻볼 때 괜찮아보이지만, 속도를 0.1로 늦춰 재생하면 한가지 문제점이 발생한다.
직접 확인을 위해 현재 프레임인 CurrFrame의 애니메이션을 출력한 결과는 아래와 같다.

느리게 재생되는 애니메이션을 보면, 현재 프레임 프레임이 뚝뚝 끊겨서 출력되는 것을 볼 수 있었다.
그러나, 애니메이션은 동작 사이사이가 부드럽게 연결되어야 퀄리티 좋은 게임을 만들어낼 수 있다.
🏃🏻Lerp Animation
부드러운 애니메이션을 위해 현재 프레임과 다음 프레임 사이를 Lerp 보간하여 현재의 애니메이션을 구현하였다.

쉐이더 파일에서는 현재 프레임과 다음 프레임을 보간하여 현재의 애니메이션을 초기화하는 코드를 작성하였다.
실행 결과를 확인하면 아래와 같다.
확실히 처음보다 훨 부드러워진 애니메이션을 확인할 수 있었다.
🏃🏻Tweening - Tween Frame
현재 동작이 쭉 플레이되며, 스페이스바를 누르는 순간 다음 동작을 수행하도록 Clip을 변경하였다.

다음 동작은 0에서 시작해서 1까지 올라가는데, 현재 동작은 반대로 1에서 0으로 내려간다.
이처럼, 현재 동작에서 다음 동작까지 넘어가기에 걸리는 소요시간을 TakeTime이라고 한다.
다음 동작의 TakeTime에 대한 비율이 1이 되면, 전환이 완료됨으로 판별하여 다음 동작으로 완전히 전환하여 플레이한다.
이렇게 현재와 다음 동작을 섞어 변환하는 방식을 Tween 동작이라고 한다.
해당 구간(Take Time)값을 Lerp로 보간하여 부드럽게 동작이 변환될 수 있도록 구현하였다.

먼저, Tweening을 구현하기 위해 필요한 데이터를 TweenDesc 구조체로 선언하였다.
현재 프레임인 Curr과, 다음 프레임인 Next를 선언하여 혼합한 뒤, Next 프레임을 출력해주었다.

ModelAnimator 클래스의 Update 함수에서 다음 애니메이션을 구하는 소스코드를 추가하였다.
다음 애니메이션도 마찬가지로 Frame 비율을 이용하여 다음 애니메이션으로 전환이 완료되었는지, 진행중인지를 조건문으로 판별하였고, 각각에 조건에 맞춰 TweenDesc 구조체 값을 초기화해주었다.


또한 여러 개의 애니메이션을 실행하며 테스트하기 위해 SPACE를 입력한 순간 다음 Clip을 재생하는 PlayTweenMode 함수를 작성하였다.
📃45_Animation.fx
// inout : 파라미터를 입력받아서 출력용으로 보낸다는 의미
void SetAnimationWorld(inout matrix world, VertexModel 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 };
// 0: 현재동작, 1: 다음동작
int clip[2];
int currFrame[2];
int nextFrame[2];
float time[2];
clip[0] = TweenFrames.Curr.Clip;
currFrame[0] = TweenFrames.Curr.CurrFrame;
nextFrame[0] = TweenFrames.Curr.NextFrame;
time[0] = TweenFrames.Curr.Time;
clip[1] = TweenFrames.Next.Clip;
currFrame[1] = TweenFrames.Next.CurrFrame;
nextFrame[1] = TweenFrames.Next.NextFrame;
time[1] = TweenFrames.Next.Time;
float4 c0, c1, c2, c3;
float4 n0, n1, n2, n3;
matrix curr = 0, next = 0;
matrix currAnim = 0;
matrix nextAnim = 0;
matrix transform = 0;
[unroll(4)]
for (int i = 0; i < 4; i++)
{
// X값-열 (indices[i] * 4 + 0) : 4픽셀로 나눠서 들어온 Bone 번호
// Y값-행 (currFrame) : 키 프레임
// Z값-면 (clip) : 애니메이션 클립
c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[0], clip[0], 0));
c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[0], clip[0], 0));
c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[0], clip[0], 0));
c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[0], clip[0], 0));
curr = matrix(c0, c1, c2, c3);
n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[0], clip[0], 0));
n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[0], clip[0], 0));
n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[0], clip[0], 0));
n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[0], clip[0], 0));
next = matrix(n0, n1, n2, n3);
currAnim = lerp(curr, next, time[0]); // 부드러운 애니메이션을 위해 현재 프레임과 다음 프레임을 보간
[flatten]
if(clip[1] > -1)
{
c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[1], clip[1], 0));
c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[1], clip[1], 0));
c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[1], clip[1], 0));
c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[1], clip[1], 0));
curr = matrix(c0, c1, c2, c3);
n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[1], clip[1], 0));
n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[1], clip[1], 0));
n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[1], clip[1], 0));
n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[1], clip[1], 0));
next = matrix(n0, n1, n2, n3);
nextAnim = lerp(curr, next, time[1]);
currAnim = lerp(currAnim, nextAnim, TweenFrames.TweenTime);
}
// weights행렬에 가중치를 누적시켜서 곱하기 => 최종행렬 transform 도출
transform += mul(weights[i], currAnim);
}
// transform에 World를 구하여 World 상태 행렬 구하기
// transform: 애니메이션이 이동할 행렬 / world: 모델(애니메이션)이 출력될 행렬
// World는 Bone 번호와 키프레임, 클립을 전부 곱한 가중치가 전부 결합되어 만들어진 최종 행렬이 된다.
world = mul(transform, world);
}
clip과 currFrame, nextFrame, time 변수를 모두 2차원 배열로 만들어 현재 동작(애니메이션)과 다음 동작(애니메이션) 사이의 Tweening을 진행하였다.

TweenFrame 구조체를 이용하여 현재 프레임과 다음 프레임의 정보들을 저장하였고, currAnim과 nextAnim을 각각 lerp한 결과 값을 한번 더 lerp하여 최종적으로 현재의 애니메이션을 구했다.
프로그램 실행 결과를 확인하면 아래와 같다.

스페이스바를 누를때마다 다음 애니메이션으로 전환되며, 애니메이션 사이사이에 TweenFrame의 TweenTime을 이용해 보간해주어 부드럽게 전환되었다.
'🎨DirectX' 카테고리의 다른 글
[DX] Animator 작업 : Animation Frame 출력 (SRV, Shader) (0) | 2023.07.12 |
---|---|
[DX] Animator 작업 : Create Texture (Texture2D) (0) | 2023.06.27 |
[DX] Animator 작업 : Create Clip Transform (bones 연산) (0) | 2023.06.27 |
[DX] Animation Read Clip (0) | 2023.05.25 |
[DX] Animation Skin 데이터 : Skinning 기법 (0) | 2023.05.11 |