DirectX

61. 애니메이션_애니메이션#1_ReadAnimation, ModelAnimator

devRiripong 2024. 3. 5.
반응형

애니메이션을 파일로 만드는 것에 이어서

본격적으로 사용할 수 있게끔 준비를 해본다.

 

애니메이션을 파일로 만드는 것에 이어서

본격적으로 사용할 수 있게끔 준비를 해본다.

 

1. AnimationDemo 클래스 생성하고 세팅하기

탐색기에서 StaticMeshDemo를 복붙 해서 이름을 AnimationDemo로 바꿔치기한다.

AssimpTool/Game에 넣는다. 코드를 AnimationDemo에 맞게 수정한다.

CreateTank, CreateTower 관련 부분은 삭제한다.

AnimationDemo::Init에서

	_shader = make_shared<Shader>(L"16. AnimationDemo.fx");

이렇게 수정한다.

 

Client/Shaders/Week3의 15. ModelDemo.fx를 탐색기에서 복붙 해서 이름을 16. AnimationDemo로 해서 같은 필터에 넣어준다.

 

지금까지 작업하던 거는 내버려둔 상태에서 살펴보면

애니메이션 코드가 실행이 되고 이거에 따라 가지고 우리가 kachujin이라고 했던 아이를 만들 것이다.

AnimationDemo.h에

	void CreateKachujin(); 

선언하고

 

AnimationDemo::Init에서

	CreateKachujin(); 

호출한다.

 

void AnimationDemo::CreateKachujin()
{
	shared_ptr<class Model> m1 = make_shared<Model>();
	m1->ReadModel(L"Kachujin/Kachujin");
	m1->ReadMaterial(L"Kachujin/Kachujin");
	/*m1->ReadAnimation(L"Kachujin/Idle");
	m1->ReadAnimation(L"Kachujin/Run");
	m1->ReadAnimation(L"Kachujin/Slash");*/

	_obj = make_shared<GameObject>();
	_obj->GetOrAddTransform()->SetPosition(Vec3(0, 0, 1));
	_obj->GetOrAddTransform()->SetScale(Vec3(0.01f));

	_obj->AddComponent(make_shared<ModelRenderer>(_shader));
	{
		_obj->GetModelRenderer()->SetModel(m1);
		//_obj->GetModelRenderer()->SetPass(1);
	}
}

 

3개의 애니메이션을 받아주게끔 만들어 준 다음에 그걸 이용해서 뭔가가 재생이 되게끔 만들어 주면 된다.

그 작업을 해본다.

 

Main.cpp에서

#include "AnimationDemo.h"
	desc.app = make_shared<AnimationDemo>(); 

이렇게 바꿔주고 시작을 한다.

 

지금은 실행을 하면 에러가 나는데 고치면 진행한다.

 

 

Model에서 ReadAnimation 하는 부분을 추가를 해야 하고,

ModelRenderer는 Model을 그리는 용도로 활용하고 있는데 Animation이 들어간다고 하면 조금 머리가 복잡해진다.

유니티 같은 경우 어떻게 하는지 보면, Animator라는 컴포넌트가 있어서 사실상 애니메이션을 틀어주는 역할을 해준다.

Mesh 자체는 SkinnedMeshRenderer라는 컴포넌트가 있어서 일반 MeshRenderer랑은 조금 다르다고 볼 수 있다. 뼈대에 붙어 스키닝이 들어간 mesh는 Skinned mesh renderer와 애니메이터의 합작품으로 뭔가 일어난다고 볼 수 있다.

사실상 ModelRenderer하나로 모든 걸 처리하는 게 아니라 유니티 내부에서도 여러 개가 준비가 되어 있다.

기본적으로 일반 모델을 렌더링 하는 것과 애니메이션을 렌더링 하는 것을 구분해서 작업을 해본다.

 

 

2. MAX_MODEL_TRANSFORMS를 큰 수로 바꿔 에러 수정하기

지금 실행을 하면 Crash가 나는데

ModelRenderer의 BoneDesc boneDesc에서 잡아 놨던 건

// Bone
#define MAX_BONE_TRANSFORMS 50

struct BoneDesc
{
	Matrix transform[MAX_BONE_TRANSFORMS]; 
};

MAX_BONE_TRANSFORMS를 50개로 잡아 놨었는데

	const uint32 boneCount = _model->GetBoneCount(); 

	for (uint32 i = 0; i < boneCount; i++)
	{
		shared_ptr<ModelBone> bone = _model->GetBoneByIndex(i); 
		boneDesc.transform[i] = bone->transform; 
		// 안채워 준 값은 쓰레기값이 들어가겠지만 쉐이더에서 사용 안할 예정이라서 일단은 냅둔다.
		// 깔끔하게 하려면 identity 행렬을 밀어 준다거나 하는 식으로 작업을 하면 된다.
	}

boneCount가 67개인데 50개인 BoneDesc에 쓰려고 하니까 크래시가 난 것이었다.

#define MAX_BONE_TRANSFORMS 250

이렇게 늘려준다.

이름을 MAX_MODEL_TRANSFORMS로 바꿔준다.

 

이제 그 부분은 넘어가지만 다른 데서 문제가 생긴다.

 

 

3. Assimp로 파일을 읽어 저장해 놓은 것을 로드해서 데이터를 들고 있기 위해 필요한 struct ModelAnimation를 생성하기 

일단 하던 작업을 계속해본다.

ModelAnimator를 만들어서 그 부분을 코드를 나눠서 분류를 해본다.

Model에서 ReadMaterial, ReadModel을 했던 것처럼 Animation을 불러올 것이다.

	void ReadAnimation(wstring filename); 

를 추가한다.

지금까지 ModelBone을 했던 것과 유사하게 Animation이란 새로운 타입이 추가가 되어야 한다.

Engine/00. Engine/Resource/Model 필터에 클래스를 추가한다. 이름을 ModelAnimation이라 한다.

Animation은 어찌 됐던 Resource다. 나중에 Resource를 상속받아서 만들거나 하는 부분은 언젠가 정리를 할 때 하도록 하고, 일단은 ModelAnimation 여기에 배치해서 시작을 한다.

 

지난 시간에 Assimp에서 읽어 왔던 파일을 파일로 저장하는 데까지 성공했고, 그거를 다시 로드해서 사용하기 위한 준비를 하고 있는 단계라고 보는 거다. 

 

ModelAnimation클래스를 대칭적으로 만들어 주면 되는 것이다.

struct로 만든다. struct와 class를 구분하는 차이는 private, public 섞어가지고 쓰고 그럴 때는 클래스로 많이 하고, 그냥 편리하게 어디서든 public으로 열어줄 생각이면 struct로 많이 하는 편이다. C++은 그렇고 C#은 큰 차이가 있다.

 

ModelAnimation에서 들고 있어야 할 정보를 채워준다.

#pragma once

struct ModelKeyframeData
{
	float time;
	Vec3 scale;
	Quaternion rotation;
	Vec3 translation;
};

struct ModelKeyframe
{
	wstring boneName;
	vector<ModelKeyframeData> transforms;
};

struct ModelAnimation
{
	shared_ptr<ModelKeyframe> GetKeyframe(const wstring& name);

	wstring name;
	float duration = 0.f;
	float frameRate = 0.f;
	uint32 frameCount = 0;
	unordered_map<wstring, shared_ptr< ModelKeyframe>> keyframes;
};

shared_ptr<ModelKeyframe> ModelAnimation::GetKeyframe(const wstring& name)
{
	auto findIt = keyframes.find(name); 
	if (findIt == keyframes.end())
		return nullptr; 

	return findIt->second; 
}

단순하게 데이터를 들고 있는 용도로만 활용할 예정이라서 일단 이렇게 만들어 준다.

 

 

4. Model에 vector<shared_ptr<ModelAnimation>> _animations을 선언하고 이를 채워주는 ReadAnimation 함수 정의하기

Model.h로 돌아간다.

ReadAnimation을 할 때 관련된 잡동사니들을 다 로드해야 하는데

그전에 ModelAnimation 변수를 들고 있게 한다.

	vector<shared_ptr<ModelAnimation>> _animations; 
struct ModelAnimation; 

전방선언을 해준다.

 

ReadAnimation에서 하나씩 읽어오면 된다.

void Model::ReadAnimation(wstring filename)
{
	wstring fullPath = _modelPath + filename + L".clip";

	shared_ptr<FileUtils> file = make_shared<FileUtils>();
	file->Open(fullPath, FileMode::Read);

	shared_ptr<ModelAnimation> animation = make_shared<ModelAnimation>();

	animation->name = Utils::ToWString(file->Read<string>());
	animation->duration = file->Read<float>();
	animation->frameRate = file->Read<float>();
	animation->frameCount = file->Read<uint32>();

	uint32 keyframesCount = file->Read<uint32>();

	for (uint32 i = 0; i < keyframesCount; i++)
	{
		shared_ptr<ModelKeyframe> keyframe = make_shared<ModelKeyframe>();
		keyframe->boneName = Utils::ToWString(file->Read<string>());

		uint32 size = file->Read<uint32>();

		if (size > 0)
		{
			keyframe->transforms.resize(size);
			void* ptr = &keyframe->transforms[0];
			file->Read(&ptr, sizeof(ModelKeyframeData) * size);
		}

		animation->keyframes[keyframe->boneName] = keyframe;
	}

	_animations.push_back(animation);
}

로드해야 하는 부분을 적어서 기입을 하는 데까지 성공한 것이라 볼 수 있다.

 

5. vector<shared_ptr<ModelAnimation>> _animations에 채워준 내용들을 편하게 쓸 수 있게 Get 함수들을 만들기 

Model.h에 Get시리즈를 만들어 준다.

uint32 GetAnimationCount() { return _animations.size(); }
vector<shared_ptr<ModelAnimation>>& GetAnimations() { return _animations; }
shared_ptr<ModelAnimation> GetAnimationByIndex(UINT index) { return (index < 0 || index >= _animations.size()) ? nullptr : _animations[index]; }
shared_ptr<ModelAnimation> GetAnimationByName(wstring name);

필요한 것에 따라 빠르게 처리하는 부분을 넣어 놨다.

 

shared_ptr<ModelAnimation> Model::GetAnimationByName(wstring name)
{
	for (auto& animation : _animations)
	{
		if (animation->name == name)
			return animation;
	}

	return nullptr;
}

애니메이션을 이름으로 찾아준다.

 

Engine을 빌드해서 작업에서 문제가 없는지 살펴본다.

 

ReadAnimation을 하면 Road 해서 들고 있는 부분까지 만들었다.

 

6. ReadAnimation이 잘 작동하는지 디버깅하기

 

정상적으로 실행되는지 보고 싶으면 breakpoint를 잡아서 _animation에 pushback을 잘하는지 살펴보면 된다.

void AnimationDemo::CreateKachujin()
{
	shared_ptr<class Model> m1 = make_shared<Model>();
	m1->ReadModel(L"Kachujin/Kachujin");
	m1->ReadMaterial(L"Kachujin/Kachujin");
	m1->ReadAnimation(L"Kachujin/Idle");
	m1->ReadAnimation(L"Kachujin/Run");
	m1->ReadAnimation(L"Kachujin/Slash");

	_obj = make_shared<GameObject>();
	_obj->GetOrAddTransform()->SetPosition(Vec3(0, 0, 1));
	_obj->GetOrAddTransform()->SetScale(Vec3(0.01f));

	_obj->AddComponent(make_shared<ModelRenderer>(_shader));
	{
		_obj->GetModelRenderer()->SetModel(m1);
		//_obj->GetModelRenderer()->SetPass(1);
	}
}

에서 ReadAnimation을 호출하는 부분의 주석을 해제하고 실행을 해보면

 

_animations의 내용 중 keyframes에 가서 살펴보면 일단은 파싱이 잘 되고 있지 않은가 생각할 수 있다.

animation은 넣어줄 때마다 animations에 중첩이 되어 있는 상태라고 볼 수 있다.

 

지금 작업하는 방식에서 Model은 껍데기처럼 리소스를 들고 있는 역할만 하고 있다. 나중에 ResourceManager에 어떻게 옮겨야 할지는 고민을 해봐야 할 것이고, 일단 관련된 모든 애들을 집어넣고 있다.

 

7. 그리는 역할을 하는 ModelAnimator라는 컴포넌트 클래스 생성하기

이제 실제로 그리는 역할을 해줄 아이가 필요하다.

원래 그리는 역할은 ModelRenderer 컴포넌트를 만들어서 여기서 담당하게 했었다.

애니메이션이 섞이면 뒤섞일 수 있기 때문에 굳이 여기에 정리하기보다는 ModelAnimator라는 애를 따로 만들어줄 것이다. 유니티에도 Animator라는 컴포넌트가 있었다.

 

Engine/04. Component에 ModelAnimator라는 Component를 상속받은 클래스를 만들어준다.

#pragma once
#include "Component.h"

class Model;

class ModelAnimator : public Component
{
	using Super = Component;

public:
	ModelAnimator(shared_ptr<Shader> shader);
	~ModelAnimator();

	virtual void Update() override;

	void SetModel(shared_ptr<Model> model);
	void SetPass(uint8 pass) { _pass = pass; }

private:
	shared_ptr<Shader>	_shader;
	uint8			_pass = 0;
	shared_ptr<Model>	_model;
};
ModelAnimator::ModelAnimator(shared_ptr<Shader> shader)
	: Super(ComponentType::Animator), _shader(shader)
{
}

 

ComponentType::Animator가 만들어지게 되면 게임 오브젝트에다가도 항상 대칭적으로 그 아이를 빠를게 찾을 수 있게끔 만들어 놨었다. 

GameObject.cpp로 가서

#include "ModelAnimator.h"
shared_ptr<ModelAnimator> GameObject::GetModelAnimator()
{
	shared_ptr<Component> component = GetFixedComponent(ComponentType::Animator);
	return static_pointer_cast<ModelAnimator>(component);
}

이렇게 해주면 편리하게 사용할 수 있다.

 

ModelAnimator에서 이런 식으로 Animator가 새로운 부품이 되었으니까, 정상적으로 잘 들어갈 것으로 판단이 되고,

나머지 헤더들도 ModelRenderer와 비슷할 수 있으니까 복사해서 ModelAnimator.cpp에 붙여 넣어 준다.

 

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

	// TODO

	// Bones
	BoneDesc boneDesc;

	const uint32 boneCount = _model->GetBoneCount();

	for (uint32 i = 0; i < boneCount; i++)
	{
		shared_ptr<ModelBone> bone = _model->GetBoneByIndex(i);
		boneDesc.transform[i] = bone->transform;
		// 안채워 준 값은 쓰레기값이 들어가겠지만 쉐이더에서 사용 안할 예정이라서 일단은 냅둔다.
		// 깔끔하게 하려면 identity 행렬을 밀어 준다거나 하는 식으로 작업을 하면 된다.
	}
	RENDER->PushBoneData(boneDesc);

	// Transform
	auto world = GetTransform()->GetWorldMatrix();
	RENDER->PushTransformData(TransformDesc{ world });

	const auto& meshes = _model->GetMeshes();
	for (auto& mesh : meshes)
	{
		if (mesh->material)
			mesh->material->Update();

		// BoneIndex
		_shader->GetScalar("BoneIndex")->SetInt(mesh->boneIndex);

		uint32 stride = mesh->vertexBuffer->GetStride();
		uint32 offset = mesh->vertexBuffer->GetOffset();

		DC->IASetVertexBuffers(0, 1, mesh->vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset);
		DC->IASetIndexBuffer(mesh->indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);

		_shader->DrawIndexed(0, _pass, mesh->indexBuffer->GetCount(), 0, 0);
	}
}

ModelAnimator::Update는 ModelRenderer의 Update에서 잡동사니 부분들이랑 연관성이 있을 거 같다. 그렇기 때문에 얘들도 긁어서 복사해서 갖고 온 다음에 시작을 해본다.

 

 

 

엔진에 잡동사니 만드는데 오래 걸렸는데 이제부터는 추가되어야 하는 부분은 ModelAnimator가 Animation 정보를 받아서 틀어주는 역할을 하는 것이다.

 

ModelAnimation이 사실상 키프레임과 잡동사니들이 있고,

vector<shared_ptr<ModelAnimation>> _animations;

Model이 정보들을 들고 있다 보니까,

 

이걸 그대로 ModelAnimator에서 렌더링 하게끔 ModelAnimator::Update에서 해주면 된다.

일단 //TODO만 배치를 해둔다.

 

8. AnimationDemo::CreateKachujin에서 ModelRenderer를 ModelAnimator로 바꾸고, GetModelRenderer를 GetAnimator로 바꾸기

사실상 AnimationDemo 쪽에서는 AnimationDemo::CreateKachujin에서

_obj->AddComponent(make_shared<ModelRenderer>(_shader));
{
	_obj->GetModelRenderer()->SetModel(m1);
	//_obj->GetModelRenderer()->SetPass(1);
}

이제는 일반적인 ModelRenderer가 아니라 ModelAnimator로

	_obj->AddComponent(make_shared<ModelAnimator>(_shader));
	{
		_obj->GetModelAnimator()->SetModel(m1);
		_obj->GetModelAnimator()->SetPass(1);
	}

바꾸고

#include "ModelAnimator.h"

를 추가해 작업을 할 준비를 하면 된다.

 

void AnimationDemo::CreateKachujin()
{
	shared_ptr<class Model> m1 = make_shared<Model>();
	m1->ReadModel(L"Kachujin/Kachujin");
	m1->ReadMaterial(L"Kachujin/Kachujin");
	m1->ReadAnimation(L"Kachujin/Idle");
	m1->ReadAnimation(L"Kachujin/Run");
	m1->ReadAnimation(L"Kachujin/Slash");

	_obj = make_shared<GameObject>();
	_obj->GetOrAddTransform()->SetPosition(Vec3(0, 0, 1));
	_obj->GetOrAddTransform()->SetScale(Vec3(0.01f));

	_obj->AddComponent(make_shared<ModelAnimator>(_shader));
	{
		_obj->GetModelAnimator()->SetModel(m1);
		_obj->GetModelAnimator()->SetPass(1);
	}
}

아직 본격적인 애니메이션 코드를 넣지 않았지만 메모리에 다시 끌고 와서 로딩을 하고 그다음에 콘텐츠 상에서 이걸 호출하는 부분까지 준비를 해 놨다.

 

9. SetModel도 ModelRenderer 것을 가져와 만들어 주고 실행해 보기 

SetModel만 처리를 하고 정상적으로 호출이 되는지 보고, 그다음 순서로 애니메이션을 넣어 본다.

void ModelAnimator::SetModel(shared_ptr<Model> model)
{
	_model = model; 

	const auto& materials = _model->GetMaterials();
	for (auto& material : materials)
	{
		material->SetShader(_shader);
	}
}

 

Engine을 다시 빌드하고 실행을 하면

크래시 나던 것도 해결이 되고 잘 뜨는 것을 볼 수 있다.

 

 

이제 여기에 애니메이션을 넣어줄 것인데

기본적으로 T 포지션 상태라는 걸 알 수 있다.

여기다가 애니메이션이 들어간다는 건 뼈 정보를 움직여서 SRT가 바뀔 것이다. 정점마다 연관성이 있는 뼈 인덱스와 뼈에 얼마나 가중치를 받아 적용이 되는지의 정보를 정점에다가 넣어놓은 상태이다. 그 정보를 이용해서 다시 좌표를 계산을 해서 걔만 움직여 주면 된다.

 

이제 80 퍼까지 왔다.

 

남은 건 움직이게 하는 코드를 만들 것인데, 수학을 기억해야 한다. 뭔가 틀어서 애니메이션이 실행이 됐다는 걸 이해하는 건 별로 의미가 없다.

 

10. 3가지 요점 - 스키닝, 애니메이션과 키프레임, 좌표변환

 

세 가지 요점은 다음과 같다. 

 

1. 스키닝이란 무엇인지, 가중치와 뼈 인덱스를 들고 있다. 

스키닝(Skinning): 스키닝은 3D 캐릭터 모델의 메쉬(피부)가 그 캐릭터의 뼈대(보통 '본'이라고 함)의 움직임을 따라가도록 만드는 과정입니다. 이 과정에서 각 메쉬의 정점은 하나 이상의 본에 '가중치'가 할당되며, 이 가중치는 해당 본의 움직임이 그 정점에 얼마나 영향을 미칠지를 결정합니다. 본 인덱스는 메쉬의 각 정점이 어떤 본에 연결되어 있는지를 나타냅니다. 이는 본의 움직임에 따라 메쉬가 어떻게 변형될지를 결정하는 데 중요합니다.

 

2. 애니메이션이랑 키프레임이랑 어떤 식이고, 키프레임은 본에 따라 시점마다 SRT를 들고 있는 정보다. 

애니메이션과 키프레임: 애니메이션은 본들의 움직임을 시간에 따라 정의한 것이며, 키프레임은 특정 시점에서 본의 위치, 회전, 크기(Scale, Rotation, Translation, 즉 SRT)를 지정합니다. 애니메이션은 이러한 키프레임들 사이의 값을 보간하여 본이 시간에 따라 어떻게 움직일지를 결정합니다. 키프레임은 애니메이션의 핵심 구성 요소로, 본이 시간에 따라 어떻게 변화해야 하는지를 정의합니다.

 

3. 그리고 세 번째로는 relative에서 global로 갔다가 global에서 다시 relative로 갔다가 이 왔다 갔다 하는 작업을 해야 된다. 화면에 출력했을 때는 글로벌 좌표로 있는 것이다. 예를 들면 지금 무릎에 있는 어떠한 정점이 있다 했을 때 T 포즈로 지금 잘 뜨고 있다는 건 애당초 글로벌 좌표로 계산이 되었다는 얘기가 되는 거고, 그 애니메이션이 발차기하는 걸로 바뀌어야 한다면 관절끼리의 SRT가 변화하여 바뀌게 될 것이다. 발에 있는 SRT는 발목을 기준으로 한 좌표일 것이고, 발목은 무릎을 기준으로 한 좌표일 것이고 이렇게 뼈들이 다 상대적이고 상대적인 좌표로 SRT가 되어 있기 때문에 그거를 우리가 바뀐 애니메니션이 글로벌로 한번 다시 변화를 해줄 필요가 있다. 이 T포즈의 글로벌에서 시작을 해서 이거를 뼈대를 기준으로 하는 relative로 변환을 했다가 로컬로 간거를 다시 애니메이션 적용을 시킨 거에 최종 상태로 바꿔야 되는 게 우리의 미션이다.

좌표 변환 (Global과 Relative): 3D 모델링에서는 객체의 위치를 표현하기 위해 다양한 좌표 시스템을 사용합니다. '글로벌' 좌표 시스템은 전체 3D 공간에 대한 위치를 나타내며, '로컬' 또는 'relative' 좌표 시스템은 특정 객체 또는 본에 상대적인 위치를 나타냅니다. 

좌표 변환의 순서를 보다 명확하게 정리하면 다음과 같습니다:

  1. 글로벌 좌표에서의 시작: 3D 모델링에서 모든 객체의 초기 위치는 글로벌 좌표 시스템 내에서 정의됩니다. 예를 들어, 캐릭터가 T 포즈로 서 있는 상태는 글로벌 좌표로 계산되어 표현됩니다. 이는 3D 공간에서의 절대적인 위치를 나타내며, 모든 객체는 이 글로벌 좌표계를 기준으로 위치합니다.
  2. 로컬(또는 Relative) 좌표로의 변환: 캐릭터의 특정 부분(예: 무릎, 발목)에 대한 애니메이션을 적용하기 위해서는, 먼저 해당 부분의 위치를 글로벌 좌표에서 로컬 좌표로 변환해야 합니다. 이는 본이나 객체의 상대적인 위치와 변화를 쉽게 계산하기 위함입니다. 각 본의 SRT(크기, 회전, 이동)는 로컬 좌표계를 기준으로 정의되며, 본끼리의 상대적인 관계를 통해 표현됩니다.
  3. 애니메이션 적용: 애니메이션(예: 발차기 동작)이 적용되면, 각 본의 로컬 좌표계에서의 SRT가 변경됩니다. 이는 캐릭터의 특정 부분이 움직이면서 발생하는 상대적인 변화를 나타냅니다. 예를 들어, 발의 SRT는 발목을 기준으로, 발목의 SRT는 무릎을 기준으로 정의됩니다.
  4. 글로벌 좌표로의 재변환: 애니메이션 적용 후, 변화된 로컬 좌표를 다시 글로벌 좌표로 변환하여 최종적인 위치를 계산합니다. 이 과정은 애니메이션이 실제 3D 공간에서 어떻게 표현될지를 결정합니다. 로컬 좌표에서 계산된 각 본과 객체의 상대적인 변화는 글로벌 좌표계 내에서 절대적인 위치 변화로 변환되어, 최종적인 애니메이션 효과가 화면에 표현됩니다.

이러한 글로벌->로컬->글로벌 좌표 변환 과정은 3D 애니메이션에서 매우 중요하며, 캐릭터의 움직임을 자연스럽고 현실적으로 만드는 데 필수적입니다. 이 과정을 통해 복잡한 애니메이션과 캐릭터의 움직임을 효과적으로 구현할 수 있습니다.

 

 

11. 애니메이션 정보를 Shader에 넘겨주기 위해 VS의 매개 변수의 타입 struct VertexTextureNormalTangentBlend 정의하기

그 작업을 하려면 고민되는 부분이, 지금까지 만들었던 애니메이션 정보를 넘겨줄 수 있어야 한다.

이제 쉐이더 작업을 시작할 수 있는데 16. AnimationDemo.fx라고 되어 있는 부분에서

VS의 매개 변수에 Blend까지 붙여준다.

MeshOutput VS(VertexTextureNormalTangentBlend input)

Blend를 붙인 건 BlendingAnimation까지 고려해서 만들었던 부분인데 00. Global.fx에 가서

struct VertexTextureNormalTangentBlend
{
    float4 position : POSITION;
    float2 uv : TEXCOORD;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
    float4 blendIndices : BLEND_INDICES;
    float4 blendWeights : BLEND_WEIGHTS;
};

이걸 추가한다.

 

추가적으로 섞여야 하는 뼈대 정보들이 float4 두 개로 이렇게 된 것이다.

4가지의 값을 이용해서 연관성이 있는 뼈대 번호를 찾아서 뭔가 만들어 주면 될 것이다.

 

이제 16. AnimationDemo.fx의 VS에서 // TODO 에다가 뭔가를 해주면 될 것이다.

MeshOutput VS(VertexTextureNormalTangentBlend input)
{
    MeshOutput output; 
    
    // TODO
    
    output.position = mul(input.position, BoneTransforms[BoneIndex]); 
    output.position = mul(output.position, W);
    output.worldPosition = output.position.xyz;
    output.position = mul(output.position, VP);
    output.uv = input.uv; 
    output.normal = mul(input.normal, (float3x3)W);
    output.tangent = mul(input.tangent, (float3x3)W); 
    
    return output;     
}

이 뼈대의 정보를 이용해서 섞어주긴 해야 하는데

문제는 애니메이션마다 관련된 정보가 다르니까 애니메이션에 대한 정보를 어떻게든 넘겨줘야 한다.

cbuffer로 넘겨줘야 할까? 한 가지 문제가 있다. cbuffer가 많은 데이터를 밀어 넣을 수 있는 공간이 아니다. Animation은 생각보다 용량이 크기 때문에 곧이곧대로 넘길 수 없다.

다른 방식을 취해야 하는데 Texture를 만들어 넘기거나 상수버퍼를 제외한 다른 옵션을 찾아야 하는데 그 작업을 이어서 해볼 것이다.

 

 

 

반응형

댓글