본문 바로가기

DirectX11 3D/기본 문법

<Directx11 3D> 46 - Animation Rendering

 

필요한 개념


- Global, Relative-Local 개념

더보기
Global, Relative-Local 개념

 

- 50, 20 이동 후에는 Global이 된다. 여기서 C로 30이동은 Relative이다. 다시 C로 30을 이동하면(G = R * G) Global이 된다.

 

- 이와 마찬가지로 bone(Releative)에다가 Parent(Global)을 곱해주면 bone에서 부모 공간으로 이동한 bone(Global)이 나온다. 이 값을 다시 bone(Global)에 저장한다.

 

 

 

- Model Animator Class를 상속으로 만들어도 된다. 그러나 여기선 따로 만들었다.

 

*CBuffer로 넘기는 건 불가
250(MAX_MODEL_TRANSFORMS, 본 관절의 최대 개수) * 500(MAX_MODEL_KEYFRAMES, 키 프레임 최대 개수)
하면 125,000개가 된다.(행렬의 개수)
CBuffer는 4996개 까지 넘길 수 있다.

-> 그래서 텍스쳐를 보낸다.
Clip하나당
byte수로 하면 125,000 * 64 = 8,000,000(약 8메가)

ex) 애니메이션 5개 쓰면 40메가 정도 나옴

애니메이션까지 계산된 행렬을 넘긴다.
행 - KeyFrame(행마다 0번 프레임 1번 프레임 ..)
열 - Bone이 들어감
면 - Clip

최대값 125,000으로 잡고 안쓰는 나머지 부분은 안쓴다.

한면의 크기는 125,000
구조체 배열로 동적할당하는 방식으로(Clip이 몇개가 될지 모르니까)

- Texture
X - Transform(열)
Y - KeyFrame(행)
Z - Clip(면)

Texture의 포멧은 일반적으로 R8G8B8A8 -> 총 4byte
dx10부터는 R32B32G32A32_FLOAT(총 16바이트)  UNORM(0 ~ 1), NORM(-1 ~ 1)
R32B32G32A32_FLOAT 이 포맷이 픽셀하나를 저장하는데 최대로 쓸수 있는 크기
픽셀 하나당 16byte를 쓸 수 있다.(But 문제 생김)
Bone하나는 64바이트(Matrix) 근데 픽셀하나는 최대 16바이트이다.

그래서 행렬 하나를 4개로 쪼개서 쓴다.(픽셀 4개로)


쪼개서 가져오는 것은 Shader에서 할것이다.

 

- Texture 개념(리소스)
Texture포맷들은 그에 상응하는 Array가 있다.
Texture1D - Array
Texture2D - Array
Texture3D - Array
(*TextureCube도 Array가 있다.)

ex)
desc.ArraySize에 값을 주면(2 이상이면)
Texture2DArray가 된다.
* Array가 붙는 것은 D3D11_USAGE_DYNAMIC으로 사용할 수 없다.(x) -> 즉 Array에서는 Dynamic이 걸리지 않음
(그러나 예외적으로 BIND에 D3D11_BIND_SHADER_RESOURCE를 주게 되면 DYNAMIC이 허용될 수 있도록 할 수는 있다.)
만약 애니메이션을 편집하고 싶다면 한단계 올라가서 Texture2D.. 등에서 DYNAMIC을 주면된다.(얘는 걸린다.)

Texture2D나 Texture3D나 차이는 거의없다. MipMap방식이 조금 다를뿐...(약간만 고민하면 됨)

*중요)

스택 메모리의 크기는 윈도우에서 일반적으로 2MB 정도로 할당된다. memcpy의 크기도 스택 메모리에 의존해서 2MB 이상 사용할 수 없으므로 VirtualAlloc()을 사용함

*Tip)

어셈블리어를 보면 현재 실행중인 것들을 볼 수 있는데, 변수 명이나 함수나 다 노출된다. 그래서 현업에서는 사용자에게
배포 되기전에 코드 난독화를 거침(변수명들을 알 수 없는 문자로 바꿈)

요약
스택 프레임의 크기는 약 2MB
Memcpy 불가, VirtualAlloc 사용
램에 먼저 메모리를 할당 후 텍스처에 데이터를 복사해 준다.
면 - Clip
행 - KeyFrame(행마다 0번 프레임 1번 프레임 ..)
열 - Bone이 들어감

- Create SRV, Shader
ShaderResourceView(srv) : GPU 메모리에 복사된 리소스 데이터를 셰이더에서 어떤 형식으로 다룰지를 알려줌

초반에 CurrFrame = 0, NextFrame = 0을 줘서 T-Pose가 아닌, 0번 프레임 동작이 나옴

 

 

관련 함수

- VirtualAlloc() 참고

https://cpplab.tistory.com/45?category=957393 

 

VirtualAlloc() 함수

VirtualAlloc() -> 가상메모리를 사용하기 위해 가장 많이 사용, malloc() 보다 발전된 형태 Reserve(예약), Commit(확정)이 가능 * 예약의 경우 물리적 메모리는 전혀 소요되지 않는다. 확정에만 가상메모리

cpplab.tistory.com

 

 

ModelAnimator.h


더보기
#pragma once

class ModelAnimator
{
public:
	ModelAnimator(Shader* shader);
	~ModelAnimator();

	void Update();
	void Render();

public:
	void ReadMesh(wstring file);
	void ReadMaterial(wstring file);
	void ReadClip(wstring file);

	Transform* GetTransform() { return transform; }
	Model* GetModel() { return model; }

	void Pass(UINT pass);

private:
	void CreateTexture();
	// index는 클립의 번호
	void CreateClipTransform(UINT index);

private:
	// ClipTransform는 최종 애니메이션이 결합된 행렬
	struct ClipTransform
	{
		// 2차원 행렬
		Matrix** Transform;

		ClipTransform()
		{
			// 행은 키프레임 수 만큼
			Transform = new Matrix*[MAX_MODEL_KEYFRAMES];

			// 그 안(행)에서 열들로 동적할당
			for (UINT i = 0; i < MAX_MODEL_KEYFRAMES; i++)
				Transform[i] = new Matrix[MAX_MODEL_TRANSFORMS];
		}

		~ClipTransform()
		{
			for (UINT i = 0; i < MAX_MODEL_KEYFRAMES; i++)
				SafeDeleteArray(Transform[i]);

			SafeDeleteArray(Transform);
		}
	};
	// 클립 마다 얘가 동적할당 받아야 한다.
	// 3차원 배열은 복잡하니까 2차원 배열로 처리하고 얘를 동적할당 받아서 처리
	ClipTransform* clipTransforms = nullptr;

	// 텍스쳐로 Shader로 넘겨주기위해
	// 얘도 리소스(ID3D11Resource)를 상속 받고 있다.
	ID3D11Texture2D* texture = nullptr;
	// 리소스(ID3D11Resource)들은 전부 얘로 넘어가게 할 수 있다.
	// 특이점은 버퍼도 리소스를 상속받고 있다. 즉, 버퍼도 렌더링 파이프라인을 타지 않더라도 ID3D11ShaderResourceView로 넘길 수 있다.
	ID3D11ShaderResourceView* srv;


private:
	// 프레임 정보에 대한 구조체
	struct KeyframeDesc
	{
		int Clip = 0; // 현재 플레이하려는 애니메이션의 클립 번호

		UINT CurrFrame = 0; // 현재 프레임 번호
		UINT NextFrame = 0; // 다음 프레임 번호

		float Time = 0.0f; // 현재 플레이되는 시간(0 ~ 1)로 정규화될 것임
		float RunningTime = 0.0f; // 애니메이션 시작했을때 부터 시간

		float Speed = 1.0f; // 애니메이션 속도

		Vector2 Padding;
	} keyframeDesc;

	ConstantBuffer* frameBuffer;
	ID3DX11EffectConstantBuffer* sFrameBuffer;

private:
	Shader* shader;
	Model* model;
	Transform* transform;
};

 

 

 

 

 

 

 

ModelAnimator.Cpp


더보기
#include "Framework.h"
#include "ModelAnimator.h"

ModelAnimator::ModelAnimator(Shader* shader) 
	: shader(shader)
{
	model = new Model();
	transform = new Transform(shader);

	frameBuffer = new ConstantBuffer(&keyframeDesc, sizeof(KeyframeDesc));
	sFrameBuffer = shader->AsConstantBuffer("CB_AnimationFrame");
}

ModelAnimator::~ModelAnimator()
{
	SafeDelete(model);
	SafeDelete(transform);

	SafeDeleteArray(clipTransforms);
	SafeRelease(texture);
	SafeRelease(srv);

	SafeDelete(frameBuffer);
}

void ModelAnimator::Update()
{
	ImGui::InputInt("Clip", &keyframeDesc.Clip);
	keyframeDesc.Clip %= model->ClipCount();

	ImGui::InputInt("CurrFrame", (int*)&keyframeDesc.CurrFrame);
	keyframeDesc.CurrFrame %= model->ClipByIndex(keyframeDesc.Clip)->FrameCount();


	// Texture가 null이었다면 Texture를 세팅해서
	// 넘길 수 있도록 세팅
	if (texture == NULL)
	{
		for (ModelMesh* mesh : model->Meshes())
			mesh->SetShader(shader);

		CreateTexture();
	}

	for (ModelMesh* mesh : model->Meshes())
		mesh->Update();
}

void ModelAnimator::Render()
{
	frameBuffer->Render();
	sFrameBuffer->SetConstantBuffer(frameBuffer->Buffer());

	for (ModelMesh* mesh : model->Meshes())
	{
		mesh->SetTransform(transform);
		mesh->Render();
	}
}

void ModelAnimator::ReadMesh(wstring file)
{
	model->ReadMesh(file);
}

void ModelAnimator::ReadMaterial(wstring file)
{
	model->ReadMaterial(file);
}

void ModelAnimator::ReadClip(wstring file)
{
	model->ReadClip(file);
}

void ModelAnimator::Pass(UINT pass)
{
	for (ModelMesh* mesh : model->Meshes())
		mesh->Pass(pass);
}

void ModelAnimator::CreateTexture()
{
	// 즉, 클립의 면의 개수(클립의 갯수)
	clipTransforms = new ClipTransform[model->ClipCount()];

	// 각 클립별로 행렬(구조체 ClipTransform)을 만들어 줌
	for (UINT i = 0; i < model->ClipCount(); i++)
		CreateClipTransform(i);

	//Create Texture
	{
		//	X - Transform(열)
		//	Y - KeyFrame(행)
		//	Z - Clip(면)
		D3D11_TEXTURE2D_DESC desc;
		ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));

		/*
		Texture의 포멧은 일반적으로 R8G8B8A8 -> 총 4byte
		dx10부터는 R32B32G32A32_FLOAT(총 16바이트)  UNORM(0 ~ 1), NORM(-1 ~ 1)
		R32B32G32A32_FLOAT 이 포맷이 픽셀하나를 저장하는데 최대로 쓸수 있는 크기
		픽셀 하나당 16byte를 쓸 수 있다.(But 문제 생김)
		Bone하나는 64바이트(Matrix) 근데 픽셀하나는 최대 16바이트이다.

		그래서 행렬 하나를 4개로 쪼개서 쓴다.(픽셀 4개로)
		-> Width = Transform * 4

		픽셀하나당 16바이트이므로 행렬을 저장하려면 * 4를 해서 64로 사용
		*/
		desc.Width = MAX_MODEL_TRANSFORMS * 4; // 가로
		desc.Height = MAX_MODEL_KEYFRAMES;
		// 면의 배열의 개수(면의 개수는 clip)
		desc.ArraySize = model->ClipCount();
		desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; // 16Byte * 4 = 64Byte
		// 들어가서 변할일이 없다.
		desc.Usage = D3D11_USAGE_IMMUTABLE;
		// ShaderResourceView로 해서 Shader에 연결시킬 것임
		desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
		desc.MipLevels = 1;
		// MSAA와 관련 있다.
		desc.SampleDesc.Count = 1;

		// 한 면의 바이트 수
		UINT pageSize = MAX_MODEL_TRANSFORMS * 4 * 16 * MAX_MODEL_KEYFRAMES; // 행렬이니까 곱하기 16


		// Malloc은 생성자 호출x new 키워드는 생성자 호출 o
		// Malloc은 길이만 잡는다.(스텍 프레임에 한계가 있다. 스텍프레임은 크기는 2MB이다, 그래서 Malloc은 최대 2MB까지 가능하다.)
		// 우리는 하나에 8메가이다.
		// 스텍이 깨져버림(깨지면 StackOverFlow가 나옴)

		// 텍스쳐 메모리 전체 크기를 잡아놓고 메모리들을 텍스쳐를 만들어서
		// 초기값으로 해줌

		// 스택 메모리의 크기는 윈도우에서 일반적으로 2MB 정도로 할당된다. memcpy의 크기도 스택 메모리에 의존해서 2MB 이상
		// 사용할 수 없으므로 VirtualAlloc을 사용함
		// malloc이라면 void* p = malloc(pageSize * model->ClipCount());로 하면 되지만, 2MB를 넘어서 불가

		// 가상메모리에 할당, 예약을 건다는 개념, 실제 메모리에 할당해서 쓸 수 있도록 함
		// 게임업계에서 많이 씀
		// 애니메이션 전체의 텍스쳐 크기 : pageSize * model->ClipCount()
		// MEM_RESERVE : 내가 저정도를 쓰겠다 예약
		// PAGE_READWRITE : 예약한 곳을 읽고 쓸 수 있게 열어 두겠다.
		void* p = VirtualAlloc(NULL, pageSize * model->ClipCount(), MEM_RESERVE, PAGE_READWRITE);
		// 예약한 메모리 사이즈를 알아 낼 수 있다.
		// VirtualQuery()라는 명령어가 있다. MEMORY_BASIC_INFORMATION를 리턴함

		for (UINT c = 0; c < model->ClipCount(); c++)
		{
			// 각 클립의 시작 번호, 주소
			// ex) 0번의 시작.. 1번의 시작.. 이됨
			UINT start = c * pageSize;

			for (UINT k = 0; k < MAX_MODEL_KEYFRAMES; k++)
			{
				// 줄단위로 써감
				// 시작주소
				// 시작 부터 한줄 씩 점프 뛸 수 있다.
				// 포인터는 자신의 자료형의 크기만큼 점프하므로 (BYTE*)로 캐스팅한다.
				// 얘는 바이트 크기만큼 잡는 거라
				// 주소는 바이트 만큼 점프뜀(int는 4바이트)
				// why? p는 void라 몇 바이트 점프 뛸지 모름
				// 그래서 BYTE*를 줘서 1 BYTE만큼 점프뜀
				void* temp = (BYTE*)p + MAX_MODEL_TRANSFORMS * k * sizeof(Matrix) + start;

				// 실제 메모리에 씀
				// 위에서는 예약을 했다면 여기서는 실제로 씀
				// 1) 할당할 주소 2) 할당할 사이즈
				// 3) MEM_COMMIT : 메모리 확정시키겠다.(사용하겠다는 의미)
				VirtualAlloc(temp, MAX_MODEL_TRANSFORMS * sizeof(Matrix), MEM_COMMIT, PAGE_READWRITE);

				// 이제 메모리 사용가능
				// 한줄의 사이즈를 씀
				memcpy(temp, clipTransforms[c].Transform[k], MAX_MODEL_TRANSFORMS * sizeof(Matrix));
			}
		}//for(c)

		// array를 한장썼을때랑 여러장 썼을 때랑 달라짐

		// SubResource할때 맴버로 아래 2개가 더 있다.
		// SysMemPitch : 넓이(한줄의 크기)
		// SysMemSlicePitch : 한면의 사이즈(한장의 크기)
		// 여러장을 사용할 것이어서 SubResource가 배열로 되어야 한다.

		D3D11_SUBRESOURCE_DATA* subResources = new D3D11_SUBRESOURCE_DATA[model->ClipCount()];
		for (UINT c = 0; c < model->ClipCount(); c++)
		{
			// 페이지의 시작 위치
			void* temp = (BYTE*)p + c * pageSize;

			subResources[c].pSysMem = temp;
			// 가로줄의 크기
			subResources[c].SysMemPitch = MAX_MODEL_TRANSFORMS * sizeof(Matrix);
			// 한면의 크기
			subResources[c].SysMemSlicePitch = pageSize;
		}
		// 텍스쳐를 생성
		Check(D3D::GetDevice()->CreateTexture2D(&desc, subResources, &texture));

		SafeDeleteArray(subResources);
		// 가상 메모리를 자유롭게 풀어줘야함
		// MEM_RELEASE : 지우겠다.
		VirtualFree(p, 0, MEM_RELEASE);
	}


	//Create SRV
	{
		D3D11_SHADER_RESOURCE_VIEW_DESC desc;
		ZeroMemory(&desc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
		// 텍스쳐를 셰이더에서 어떠한 포맷으로 다룰것인지(텍스쳐의 포맷과 동일)
		desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
		// 리소스의 형식을 알려주는 것
		// 우리는 TEXTURE2DArray를 사용해서
		desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
		// 여러개의 맴버변수가 보이는데, 우리가 ViewDimension에서 세팅한
		// 리소스타입의 변수에만 넣어줘야 한다.
		desc.Texture2DArray.MipLevels = 1;
		desc.Texture2DArray.ArraySize = model->ClipCount();
		
		// 1번인자 : 연결한 리소스, 2번 : desc, 3번 : 생성할 객체
		Check(D3D::GetDevice()->CreateShaderResourceView(texture, &desc, &srv));
	}

	// srv를 mesh에 전달함
	for (ModelMesh* mesh : model->Meshes())
		mesh->TransformsSRV(srv);
}

void ModelAnimator::CreateClipTransform(UINT index)
{
	Matrix* bones = new Matrix[MAX_MODEL_TRANSFORMS];

	ModelClip* clip = model->ClipByIndex(index);
	for (UINT f = 0; f < clip->FrameCount(); f++)
	{
		for (UINT b = 0; b < model->BoneCount(); b++)
		{
			ModelBone* bone = model->BoneByIndex(b);

			Matrix parent;
			Matrix invGlobal = bone->Transform();
			D3DXMatrixInverse(&invGlobal, NULL, &invGlobal);

			int parentIndex = bone->ParentIndex();
			if (parentIndex < 0)
				D3DXMatrixIdentity(&parent);
			else
				parent = bones[parentIndex];

			Matrix animation;
			ModelKeyframe* frame = clip->Keyframe(bone->Name());

			// 이전에 이름이 매칭이 안되는 경우 자기 boneTrnasform을 넣어놨었다.
			// 없는 경우
			if (frame != nullptr)
			{
				// 데이터를 가져옴
				ModelKeyframeData& data = frame->Transforms[f];

				Matrix S, R, T;
				D3DXMatrixScaling(&S, data.Scale.x, data.Scale.y, data.Scale.z);
				// 쿼터니언으로 가져와야 한다.
				D3DXMatrixRotationQuaternion(&R, &data.Rotation);
				D3DXMatrixTranslation(&T, data.Translation.x, data.Translation.y, data.Translation.z);
			
				animation = S * R * T;
			}

			// 애니메이션의 키 데이터가 없을 경우
			// 자신의 본 노드 정보를 저장해놓은 것에 identity된 행렬을 곱한다.
			// 이후 이 행렬을 부모랑 곱해서 자신의 움직임은 그냥 부모를 따르도록 한다.
			else
			{
				D3DXMatrixIdentity(&animation);
			}

			// 부모껄 가져와서 animation과 결합후 bones에 다시 넣어놓는다.
			// 부모 자식관계를 맺는것과 동일
			// animation 행렬은 해당 프레임에 해당 본이 얼마만큼 이동할지를 결정
			// *왜 다시 Global로 넣어놨냐면 이 다음에 이걸 parent로 사용하려고
			bones[b] = animation * parent;

			// 우리가 원하는 건 Global을 뺸값을 넣는 것
			// invGlobal도 G이고 bones[b]도 G이다.
			// 그래서 역행렬로 없애주고 넣어줌
			// Inverse를 만든 이유가 Relative를 만들려고 그랬던 것(G->L)
			// 최종 행렬
			clipTransforms[index].Transform[f][b] = invGlobal * bones[b];
		}//for(f)
	}
}

 

 

ModelClip.h


더보기
#pragma once

// 애니메이션 데이터를 저장할 클래스

struct ModelKeyframeData
{
	float Time;

	Vector3 Scale;
	Quaternion Rotation;
	Vector3 Translation;
};

struct ModelKeyframe
{
	wstring BoneName;
	vector<ModelKeyframeData> Transforms;
};

class ModelClip
{
public:
	friend class Model;

private:
	ModelClip();
	~ModelClip();

public:
	float Duration() { return duration; }
	float FrameRate() { return frameRate; }
	UINT FrameCount() { return frameCount; }

	ModelKeyframe* Keyframe(wstring name);

private:
	wstring name;

	float duration;
	float frameRate;
	UINT frameCount;

	// 맵의 경우 균형 트리로 키를 검색하며 unorderedmap은 해싱으로
	// 검색해서 대량의 키가 있는 경우 해싱이 훨씬 빠르다.
	unordered_map<wstring, ModelKeyframe*> keyframeMap;
};

 

 

ModelClip.cpp


더보기
#include "Framework.h"
#include "ModelClip.h"

ModelClip::ModelClip()
{

}

ModelClip::~ModelClip()
{

}

ModelKeyframe* ModelClip::Keyframe(wstring name)
{
	if (keyframeMap.count(name) < 1)
		return NULL;

	return keyframeMap[name];
}

 

 

45_Animation.fx


더보기
#include "00_Global.fx"

float3 Direction = float3(-1, -1, 1);

struct VertexModel
{
    float4 Position : Position;
    float2 Uv : Uv;
    float3 Normal : Normal;
    float3 Tangent : Tangent;
    float4 BlendIndices : BlendIndices;
    float4 BlendWeights : BlendWeights;
};

#define MAX_MODEL_TRANSFORMS 250
#define MAX_MODEL_KEYFRAMES 500

cbuffer CB_Bone
{
    matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
    
    uint BoneIndex;
    
    // CBuffer 전체가 배열이 되지 않는한, padding은 안 넣어줘도 된다.
};

struct AnimationFrame
{
    int Clip;

    uint CurrFrame;
    uint NextFrame;

    float Time;
    float Running;

    float3 Padding;
};

// 묶어서 전역변수로 쓰기 위해
cbuffer CB_AnimationFrame
{
    AnimationFrame Keyframes;
};

Texture2DArray TransformsMap;

struct VertexOutput
{
    // SV_Position -> position0번으로 취급됨
    float4 Position : SV_Position;
    float3 Normal : Normal;
    float2 Uv : Uv;
};

// inout : 입력, 출력용으로 사용하겠다.
// in : 입력용으로 사용하겠다. out : 출력용으로 사용하겠다.
// Animation의 world를 계산함
// world는 입력도 하고 출력도 할거라서 inout
void SetAnimationWorld(inout matrix world, VertexModel input)
{
    // 각 항목을 for문으로 다루기 위해 배열로 저장
    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 clip;
    int currFrame;
    int nextFrame;
    float time;
    
    clip = Keyframes.Clip;
    currFrame = Keyframes.CurrFrame;
    nextFrame = Keyframes.NextFrame;
    time = Keyframes.Time;

    // 행단위로 써놨었다. 읽어들임
    float4 c0, c1, c2, c3;
    
    matrix curr = 0;
    // 최종행렬
    matrix transform = 0;
    
    [unroll(4)]
    for (int i = 0; i < 4; i++)
    {
        // 가중치가 4개였다.(스키닝을 하기위한)
        // Sample은 확대/축소가 일어나므로, 데이터의 변형이 일어나서
        // 사용할 수 없다. 그래서 Load을 사용 -> 행열로 원본데이터를 불러옴
        // int4 ? 왜 int인가? -> 마지막 w는 밉맵의 번호이다, 우리는 맵맵을 한장으로 사용하므로 0(우리는 민맵 1로 하나만 사용하니까 0을 줌)
        // 4개 픽셀 했었어서 *4 곱함(x는 그렇다.)
        // + 0은 각 픽셀의 0행을 위해
        // 본 번호(열), 키 프레임(행), clip(면)
        c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame, clip, 0)); // 1행
        c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame, clip, 0)); // 2행
        c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame, clip, 0)); // 3행
        c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame, clip, 0)); // 4행
        curr = matrix(c0, c1, c2, c3);
        
        // 최종행렬에 가중치만 누적시키면 된다.
        // 곱한걸 계속 누적시키는 것(계속 영향을 받을 수 있도록)
        // 초반에 설명한 weights[i] 0.5라면 0.5만큼 curr에 곱해줌(움직여줌)
        // transform : 가중치, 프레임까지 결합된 행렬
        transform += mul(weights[i], curr);
    }
    
    // 최종행렬을 world로 변환
    // transform : 애니메이션이 이동할 행렬, world : 모델이 출력할 world
    world = mul(transform, world);
}

VertexOutput VS(VertexModel input)
{
    VertexOutput output;
    
    //World = mul(BoneTransforms[BoneIndex], World);
    SetAnimationWorld(World, input);
    
    // ModelMesh의 Transform이 넘어오나, Bone의 위치로 옮긴 다음, World로 옮겨야함
    output.Position = WorldPosition(input.Position);
    output.Position = ViewProjection(output.Position);
    
    output.Normal = WorldNormal(input.Normal);
    output.Uv = input.Uv;
    
    return output;
}

float4 PS(VertexOutput input) : SV_Target
{    
    // 음영 넣기 위해
    float NdotL = dot(normalize(input.Normal), -Direction);
    
    return DiffuseMap.Sample(LinearSampler, input.Uv) * NdotL;
}

technique11 T0
{
    P_VP(P0, VS, PS)
    P_RS_VP(P1, FillMode_WireFrame, VS, PS)
}

 

 

 

 

 

AnimationDemo.h


더보기
#pragma once
#include "Systems/IExecute.h"

class AnimationDemo : public IExecute
{
public:
	virtual void Initialize() override;
	virtual void Ready() override {};
	virtual void Destroy() override {};
	virtual void Update() override;
	virtual void PreRender() override {};
	virtual void Render() override;
	virtual void PostRender() override {};
	virtual void ResizeScreen() override {};

private:
	void Kachujin();

private:
	Shader* shader;

	ModelAnimator* kachujin = nullptr;
	
	Vector3 direction = Vector3(-1, -1, +1);
};

 

 

AnimationDemo.cpp


더보기
#include "stdafx.h"
#include "AnimationDemo.h"
#include "Converter.h"

void AnimationDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
	Context::Get()->GetCamera()->Position(1, 36, -85);

	shader = new Shader(L"45_Animation.fx");

	Kachujin();
}

void AnimationDemo::Update()
{
	if (kachujin != nullptr) kachujin->Update();
}


void AnimationDemo::Render()
{
	ImGui::SliderFloat3("Direction", direction, -1, +1);
	shader->AsVector("Direction")->SetFloatVector(direction);
	
	static int pass = 0;
	ImGui::InputInt("Pass2", &pass);
	pass %= 2;

	if (kachujin != nullptr)
	{
		kachujin->Pass(pass);
		kachujin->Render();
	}
}

void AnimationDemo::Kachujin()
{
	kachujin = new ModelAnimator(shader);
	kachujin->ReadMesh(L"Kachujin/Mesh");
	kachujin->ReadMaterial(L"Kachujin/Mesh");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Idle");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Run");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Slash");
	kachujin->ReadClip(L"Kachujin/Salsa Dancing");
	kachujin->GetTransform()->Position(0, 0, -30);
	kachujin->GetTransform()->Scale(0.025f, 0.025f, 0.025f);
}

 

 

 

결과