필요한 개념
- 모델 같은 경우는 본이 수정될 수 있다는 전제이고,
애니메이션은 따로 수정될 수 없다는 전제 하에 진행(따로 수정하도록 할려면 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)
}
결과
'DirectX11 3D > 기본 문법' 카테고리의 다른 글
| <DirectX11 3D> 58 - ComputeShader(CS) 이론 (0) | 2022.02.24 |
|---|---|
| <DirectX11 3D> 56 - Thread(스레드) (0) | 2022.02.23 |
| <DirectX11 3D> 52 - Instancing(Model) (0) | 2022.02.22 |
| <DirectX11 3D> 51 - Instancing(Mesh) (0) | 2022.02.22 |
| <DirectX11 3D> 50 - Instancing(인스턴싱) (0) | 2022.02.21 |