본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 53 - Instancing(Animation)

 

필요한 개념


- 모델 같은 경우는 본이 수정될 수 있다는 전제이고,
애니메이션은 따로 수정될 수 없다는 전제 하에 진행(따로 수정하도록 할려면 4차원 배열로 사용해야한다. Texture3DArray 사용해서)

그래서

tweenDesc[MAX_MODEL_INSTANCE];
blendDesc[MAX_MODEL_INSTANCE];
를 인스턴싱 개수 만큼 배열로 잡아줘서 ConstantBuffer를 통해 shader로 넘긴다.

 

 

 

 

 

ModelAnimator.h


더보기
#pragma once

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

	void Update();

private:
	void UpdateTweenMode(UINT index);
	void UpdateBlendMode(UINT index);

public:
	void Render();

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

	Model* GetModel() { return model; }

	void Pass(UINT pass);

	Transform* AddTransform();
	Transform* GetTransform(UINT index) { return transforms[index]; }
	void UpdateTransforms();

	// 동작을 바꿔가는 함수
	// takeTime : 한 동작과 다음 동작의 전환 될때 까지 소요시간(동작 전환에 대한 소요시간)
	// Shader에 넘어갈 frameBuffer도 1개 더 있어야함, 현재 동작과 다음 동작을 동시에 실행시키기 위해(Clip(동작) 전환 간의 뚝 뚝 끊김 방지)
	void PlayTweenMode(UINT index, UINT clip, float speed = 1.0f, float takeTime = 1.0f);
					  
	void PlayBlendMode(UINT index, UINT clip, UINT clip1, UINT clip2);
	void SetBlendAlpha(UINT index, float alpha);


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;

	struct TweenDesc
	{
		// 애니메이션이 변화되는 시간
		float TakeTime = 1.0f;
		// 변해가는 시간 기록
		float TweenTime = 0.0f;
		float ChangeTime = 0.0f;

		// Padding이 없으면 넣을 구조체가 땡겨져 버려서 넣음 
		float Padding;


		// lerp 하기 위한
		// 
		// 현재 동작
		KeyframeDesc Curr;
		// 다음 동작
		KeyframeDesc Next;

		TweenDesc()
		{
			Curr.Clip = 0;
			// -1은 clip이 없다는 뜻
			Next.Clip = -1;
		}
	} tweenDesc[MAX_MODEL_INSTANCE];
	// 얘들도 인스턴스 최대 개수 만큼 바꿔줘야 한다.

private:
	struct BlendDesc
	{
		// 0보다 크면 Blend모드로 수행
		// 0이라면 TweenMode로 수행
		UINT Mode = 0;
		// 언리얼의 speed로 움직인 값과 동일한 역할
		// 얼마만큼 섞일지
		float Alpha = 0;
		Vector2 Padding;

		// 동작은 3개로 고정시킨다고 가정해서 사용(ex) idle, walk, run)
		// 필요하다면 더 많이 세팅해도 된다.
		KeyframeDesc Clip[3];
	}blendDesc[MAX_MODEL_INSTANCE];
	// 얘들도 인스턴스 최대 개수 만큼 바꿔줘야 한다.

	ConstantBuffer* blendBuffer;
	ID3DX11EffectConstantBuffer* sBlendBuffer;

private:
	Shader* shader;
	Model* model;

	vector<Transform*> transforms;
	Matrix worlds[MAX_MODEL_INSTANCE];

	VertexBuffer* instanceBuffer;
};

 

 

 

 

 

 

 

ModelAnimator.Cpp


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

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

	frameBuffer = new ConstantBuffer(&tweenDesc, sizeof(TweenDesc) * MAX_MODEL_INSTANCE);
	sFrameBuffer = shader->AsConstantBuffer("CB_TweenFrame");

	blendBuffer = new ConstantBuffer(&blendDesc, sizeof(BlendDesc) * MAX_MODEL_INSTANCE);
	sBlendBuffer = shader->AsConstantBuffer("CB_BlendFrame");

	instanceBuffer = new VertexBuffer(worlds, MAX_MODEL_INSTANCE, sizeof(Matrix), 1, true);
}

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

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

	SafeDelete(frameBuffer);
	SafeDelete(blendBuffer);

	SafeDelete(instanceBuffer);
}

void ModelAnimator::Update()
{		
	// 0보다 크면 Blend모드로 수행
	// 0이라면 TweenMode로 수행
	
	// 실제로 그릴 개수 만큼 반복(for문이어서 느려서 computeShader 쪽으로
	// 나중에 옮김)
	for (UINT i = 0; i < transforms.size(); i++)
	{
		if (blendDesc[i].Mode == 0)
			UpdateTweenMode(i);
		else
			UpdateBlendMode(i);
	}


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

		CreateTexture();
	}

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

void ModelAnimator::UpdateTweenMode(UINT index)
{
	// 시간에 따라 애니메이션 플레이
	// 이럴때는 비율을 만드는게 중요(시간비율)
	TweenDesc& desc = tweenDesc[index];

	// 현재 애니메이션
	{
		ModelClip* clip = model->ClipByIndex(desc.Curr.Clip);

		// 이전 프레임과 현재 프레임의 차인 델타 타임을 누적시킨다.
		// Running Time이란 ? 애니메이션이 현재 플레이 되고 있는 시간
		desc.Curr.RunningTime += Time::Delta();

		// 시간 비율 생성
		// FrameRate가 30일 것이다. 그러면 30분의 1
		// * desc.Speed가 맞기는 한데 조정편하게 하라고 / 함
		float time = 1.0f / clip->FrameRate() / desc.Curr.Speed;

		// 애니메이션의 다음 프레임으로 넘긴다.(시간을 0 ~ 1까지 만든 것을)
		if (desc.Curr.Time >= 1.0f)
		{
			desc.Curr.RunningTime = 0;
			// 순환 시킴(루프 처리)
			// 한번만 플레이하고 싶다면 마지막 프레임에서 멈추면 됨
			desc.Curr.CurrFrame = (desc.Curr.CurrFrame + 1) % clip->FrameCount();

			//문제 발생 1)
			//동작이 뚝뚝 끊긴다.
			//프레임 간의 보간이 필요->Lerp가 필요
			//Lerp(0 ~1까지니까)
			desc.Curr.NextFrame = (desc.Curr.CurrFrame + 1) % clip->FrameCount();
		}
		// 비율을 RunningTime에다가 나눠준다.
		desc.Curr.Time = desc.Curr.RunningTime / time;

	}

	// 다음 동작 플레이(-1보다 큰 거면 다음 동작이 있다는 뜻)
	if (desc.Next.Clip > -1)
	{
		// TweenTime 계산
		desc.ChangeTime += Time::Delta();
		// 0 ~ 1까지 정규화된 시간을 갖는다.
		desc.TweenTime = desc.ChangeTime / desc.TakeTime;


		// TweenTime이 1보다 크다면 Animation 전환이 완료 되었다는 얘기다.
		if (desc.TweenTime >= 1.0f)
		{
			// 초기화 해줌
			desc.Curr = desc.Next;

			desc.Next.Clip = -1;
			desc.Next.CurrFrame = 0;
			desc.Next.NextFrame = 0;
			desc.Next.Time = 0;
			desc.Next.RunningTime = 0.0f;

			desc.ChangeTime = 0.0f;
			desc.TweenTime = 0.0f;
		}

		// 완료되지 않은 상황(변화되어가는 상황)
		else
		{
			ModelClip* clip = model->ClipByIndex(desc.Next.Clip);

			desc.Next.RunningTime += Time::Delta();

			float time = 1.0f / clip->FrameRate() / desc.Next.Speed;

			if (desc.Next.Time >= 1.0f)
			{
				desc.Next.RunningTime = 0;
				desc.Next.CurrFrame = (desc.Next.CurrFrame + 1) % clip->FrameCount();
				desc.Next.NextFrame = (desc.Next.CurrFrame + 1) % clip->FrameCount();
			}
			desc.Next.Time = desc.Next.RunningTime / time;
		}
	}

}

void ModelAnimator::UpdateBlendMode(UINT index)
{
	// 이것도 비율로 만드는 것, 별로 복잡하지 x
	BlendDesc& desc = blendDesc[index];

	// 동작 총 3개(0, 1, 2)
	for (UINT i = 0; i < 3; i++)
	{
		ModelClip* clip = model->ClipByIndex(desc.Clip[i].Clip);

		desc.Clip[i].RunningTime += Time::Delta();

		float time = 1.0f / clip->FrameRate() / desc.Clip[i].Speed;

		if (desc.Clip[i].Time >= 1.0f)
		{
			desc.Clip[i].RunningTime = 0;
			desc.Clip[i].CurrFrame = (desc.Clip[i].CurrFrame + 1) % clip->FrameCount();
			desc.Clip[i].NextFrame = (desc.Clip[i].CurrFrame + 1) % clip->FrameCount();
		}
		desc.Clip[i].Time = desc.Clip[i].RunningTime / time;
	}
}

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

	blendBuffer->Render();
	sBlendBuffer->SetConstantBuffer(blendBuffer->Buffer());

	instanceBuffer->Render();

	for (ModelMesh* mesh : model->Meshes())
		mesh->Render(transforms.size());
}

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::PlayTweenMode(UINT index, UINT clip, float speed, float takeTime)
{
	blendDesc[index].Mode = 0;
	
	// 다음 동작 관련 세팅해줌
	
	tweenDesc[index].TakeTime = takeTime;

	tweenDesc[index].Next.Clip = clip;
	tweenDesc[index].Next.Speed = speed;
}

void ModelAnimator::PlayBlendMode(UINT index, UINT clip, UINT clip1, UINT clip2)
{
	blendDesc[index].Mode = 1;

	blendDesc[index].Clip[0].Clip = clip;
	blendDesc[index].Clip[1].Clip = clip1;
	blendDesc[index].Clip[2].Clip = clip2;
}

void ModelAnimator::SetBlendAlpha(UINT index, float alpha)
{
	// Clmap : 값 제한
	alpha = Math::Clamp(alpha, 0.0f, 2.0f);

	blendDesc[index].Alpha = alpha;
}

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)
	}
}


Transform* ModelAnimator::AddTransform()
{
	Transform* transform = new Transform();
	transforms.push_back(transform);

	return transform;
}

void ModelAnimator::UpdateTransforms()
{
	for (UINT i = 0; i < transforms.size(); i++)
		memcpy(worlds[i], transforms[i]->World(), sizeof(Matrix));

	D3D11_MAPPED_SUBRESOURCE subResource;
	D3D::GetDC()->Map(instanceBuffer->Buffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	{
		memcpy(subResource.pData, worlds, sizeof(Matrix) * MAX_MESH_INSTANCE);
	}
	D3D::GetDC()->Unmap(instanceBuffer->Buffer(), 0);
}

 

 

53_InstancingAnimation.fx


더보기
#include "00_Global.fx"
#include "00_Light.fx"
#include "00_Render.fx"


/*
00_Render.fx 파일에 추가된 부분

#define MAX_MODEL_KEYFRAMES 500
#define MAX_MODEL_INSTANCE 500

struct AnimationFrame
{
    int Clip;

    uint CurrFrame;
    uint NextFrame;

    float Time;
    float Running;

    float3 Padding;
};

struct TweenFrame
{
    float TakeTime;
    float TweenTime;
    float RunningTime;
    float Padding;
    
    AnimationFrame Curr;
    AnimationFrame Next;
    
};

// 묶어서 전역변수로 쓰기 위해
cbuffer CB_TweenFrame
{
    TweenFrame TweenFrames[MAX_MODEL_INSTANCE];
};

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

// inout : 입력, 출력용으로 사용하겠다.
// in : 입력용으로 사용하겠다. out : 출력용으로 사용하겠다.
// Animation의 world를 계산함
// world는 입력도 하고 출력도 할거라서 inout
void SetTweenWorld(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 };

    
    // 0번이 현재 동작 1번이 다음동작(Tweening 때문에)
    // 이후 작업 편하게 하려고 변수 선언
    int clip[2];
    int currFrame[2];
    int nextFrame[2];
    float time[2];
    
    clip[0] = TweenFrames[input.InstanceID].Curr.Clip;
    currFrame[0] = TweenFrames[input.InstanceID].Curr.CurrFrame;
    nextFrame[0] = TweenFrames[input.InstanceID].Curr.NextFrame;
    time[0] = TweenFrames[input.InstanceID].Curr.Time;
    
    clip[1] = TweenFrames[input.InstanceID].Next.Clip;
    currFrame[1] = TweenFrames[input.InstanceID].Next.CurrFrame;
    nextFrame[1] = TweenFrames[input.InstanceID].Next.NextFrame;
    time[1] = TweenFrames[input.InstanceID].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++)
    {
        // 가중치가 4개였다.(스키닝을 하기위한)
        // Sample은 확대/축소가 일어나므로, 데이터의 변형이 일어나서
        // 사용할 수 없다. 그래서 Load을 사용 -> 행열로 원본데이터를 불러옴
        // int4 ? 왜 int인가? -> 마지막 w는 밉맵의 번호이다, 우리는 맵맵을 한장으로 사용하므로 0(우리는 민맵 1로 하나만 사용하니까 0을 줌)
        // 4개 픽셀 했었어서 *4 곱함(x는 그렇다.)
        // + 0은 각 픽셀의 0행을 위해
        // 본 번호(열), 키 프레임(행), clip(면)
        c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[0], clip[0], 0)); // 1행
        c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[0], clip[0], 0)); // 2행
        c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[0], clip[0], 0)); // 3행
        c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[0], clip[0], 0)); // 4행
        curr = matrix(c0, c1, c2, c3);
        
        n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[0], clip[0], 0)); // 1행
        n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[0], clip[0], 0)); // 2행
        n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[0], clip[0], 0)); // 3행
        n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[0], clip[0], 0)); // 4행
        next = matrix(n0, n1, n2, n3);
        
        // 이전 프레임에서 현재 프레임까지의 갑시, desc.time은 0 ~ 1 범위이므로 lerp의 보간 값으로 그대로 대입한다.
        // lerp도 0 ~ 1값으로 처리하기 때문이다.
        // (현재 프레임 Matrix, 다음 프레임 Matrix, 시간값(0 ~ 1))
        currAnim = lerp(curr, next, time[0]);
        
        
        
        // 다음 동작 구해줌
        // flatten : 제어문법(현재는 신경쓰지 말자)
        // 다음이 -1 보다 크다면(clip이 있다는 뜻) 다음 동작을 플레이 시킨다.
        [flatten]
        if (clip[1] > -1)
        {
            c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, currFrame[1], clip[1], 0)); // 1행
            c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, currFrame[1], clip[1], 0)); // 2행
            c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, currFrame[1], clip[1], 0)); // 3행
            c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, currFrame[1], clip[1], 0)); // 4행
            curr = matrix(c0, c1, c2, c3);
        
            n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, nextFrame[1], clip[1], 0)); // 1행
            n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, nextFrame[1], clip[1], 0)); // 2행
            n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, nextFrame[1], clip[1], 0)); // 3행
            n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, nextFrame[1], clip[1], 0)); // 4행
            next = matrix(n0, n1, n2, n3);
            
            nextAnim = lerp(curr, next, time[1]);
            
            // TweenTime : 0 ~ 1까지 정규화되어 있다.
            currAnim = lerp(currAnim, nextAnim, TweenFrames[input.InstanceID].TweenTime);
        }
        
        
        // 최종행렬에 가중치만 누적시키면 된다.
        // 곱한걸 계속 누적시키는 것(계속 영향을 받을 수 있도록)
        // 초반에 설명한 weights[i] 0.5라면 0.5만큼 curr에 곱해줌(움직여줌)
        // transform : 가중치, 프레임까지 결합된 행렬
        transform += mul(weights[i], currAnim);
    }
    
    // 최종행렬을 world로 변환
    // transform : 애니메이션이 이동할 행렬, input.Transform : 인스턴싱의 world
    world = mul(transform, input.Transform);
}


struct BlendFrame
{
    uint Mode;
    float Alpha;
    float2 Padding;

    AnimationFrame Clip[3];
};

cbuffer CB_BlendFrame
{
    // s는 인스턴싱 때문에 미리 붙여놓음
    BlendFrame BlendFrames[MAX_MODEL_INSTANCE];
};

void SetBlendWorld(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 };


    // 행단위로 써놨었다. 읽어들임
    float4 c0, c1, c2, c3;
    float4 n0, n1, n2, n3;
    
    
    matrix curr = 0, next = 0;
    matrix currAnim[3];
    matrix anim = 0;
    
    // 최종행렬
    matrix transform = 0;
    
    
    BlendFrame frame = BlendFrames[input.InstanceID];
    
    
    // 현재 동작 구해줌
    [unroll(4)]
    for (int i = 0; i < 4; i++)
    {
        [unroll(3)]
        for (int k = 0; k < 3; k++)
        {
            c0 = TransformsMap.Load(int4(indices[i] * 4 + 0, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 1행
            c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 2행
            c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 3행
            c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, frame.Clip[k].CurrFrame, frame.Clip[k].Clip, 0)); // 4행
            curr = matrix(c0, c1, c2, c3);
        
            n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 1행
            n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 2행
            n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 3행
            n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, frame.Clip[k].NextFrame, frame.Clip[k].Clip, 0)); // 4행
            next = matrix(n0, n1, n2, n3);
     
            currAnim[k] = lerp(curr, next, frame.Clip[k].Time);
        }
        
        // 0, 1, 2 구간이 떨어짐
        int clipA = (int) frame.Alpha;
        int clipB = clipA + 1;
        
        float alpha = frame.Alpha;
        
        if (alpha >= 1.0f)
        {
            // 1 ~ 2구간을 가야하니까 -1을 시킴
            // lerp를 하기 위해 0 ~ 1구간을 만들어야 해서 -1을 함
            alpha = frame.Alpha - 1.0f;
            
            // 2를 넘었다면 플레이 구간이 1 ~ 2이다.
            if (frame.Alpha >= 2.0f)
            {
                clipA = 1;
                clipB = 2;
            }
        }
        
        anim = lerp(currAnim[clipA], currAnim[clipB], alpha);
       
        transform += mul(weights[i], anim);
    }
    world = mul(transform, input.Transform);
}

MeshOutput VS_Animation(VertexModel input)
{
    MeshOutput output;
    
    if (BlendFrames[input.InstanceID].Mode == 0)
        SetTweenWorld(World, input);
    else
        SetBlendWorld(World, input);
    
    VS_GENERATE
    
    return output;
}

*/

float4 PS(MeshOutput input) : SV_Target
{
    float3 normal = normalize(input.Normal);
    float3 light = -GlobalLight.Direction;
    
    return DiffuseMap.Sample(LinearSampler, input.Uv) * dot(light, normal);
}

technique11 T0
{
   P_VP(P0, VS_Mesh, PS)
   P_VP(P1, VS_Model, PS)
   P_VP(P2, VS_Animation, PS)
}

 

 

 

 

 

 

 

 

 

 

 

결과