필요한 개념
언리얼은 메터리얼이 전부 shader로 이루어져서 모델 import시에 shader 컴파일이 일어난다.
Mixamo모델을 로드 하면 반투명 캐릭터로 보이는데 디자이너가 그렇게 설정했다.
그래서 불투명(opaque)로 바꿔주면 된다.
material 편집기 들어가서 blendMode->Opaque로 세팅하면 된다.
unreal은 .fbx파일에 texture를 압축을 푼다.
우리는 .fbx 내부에 압축되어 있는 texture를 압출 풀어서 복사해서 따로 파일로 저장했다.
unreal Animation 임포트 해서 보면 프레임마다 본의 값이 변하는 것을 볼 수 있다.
모델 언리얼 임포트 할때 설명 :
fbx File Information에 보면
File Creator(뭘로 부터 만들어졌냐), File Axis Direction(ex) Z-UP(RH) -> z축을 up으로 사용하겠다.)
max도 언리얼도 좌표가 DX랑 다름
언리얼은 좌표가 다르다. x : 전방, y : 우측, z : 수직
Animaton Start Frame, End Frame(프레임 시작과 끝)

BlendSpace1D : 기본 값에 의해서 움직일 수 있도록 함
(우리가 사용하는 것은 플레이어의 속도에 따라 동작을 결정)
if문으로 하면 뚝뚝끊기니, 부드럽게 처리하는 blend기능을 사용한다.
우리는 0 ~ 2까지 해서 idle, walk, run 구현할 것이다.
- Blending : 애니메이션을 동작을 섞어서(ex) 커피 블랜딩)

ModelAnimator.h 추가된 부분
class ModelAnimator
{
...
private:
void UpdateBlendMode();
public:
void PlayBlendMode(UINT clip, UINT clip1, UINT clip2);
void SetBlendAlpha(float alpha);
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;
ConstantBuffer* blendBuffer;
ID3DX11EffectConstantBuffer* sBlendBuffer;
}
ModelAnimator.Cpp 추가된 부분
ModelAnimator::ModelAnimator(Shader* shader)
: shader(shader)
{
...
blendBuffer = new ConstantBuffer(&blendDesc, sizeof(BlendDesc));
sBlendBuffer = shader->AsConstantBuffer("CB_BlendFrame");
}
void ModelAnimator::Update()
{
// 0보다 크면 Blend모드로 수행
// 0이라면 TweenMode로 수행
if (blendDesc.Mode == 0)
UpdateTweenMode();
else
UpdateBlendMode();
...
}
void ModelAnimator::UpdateBlendMode()
{
// 이것도 비율로 만드는 것, 별로 복잡하지 x
BlendDesc& desc = blendDesc;
// 동작 총 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::PlayBlendMode(UINT clip, UINT clip1, UINT clip2)
{
blendDesc.Mode = 1;
blendDesc.Clip[0].Clip = clip;
blendDesc.Clip[1].Clip = clip1;
blendDesc.Clip[2].Clip = clip2;
}
void ModelAnimator::SetBlendAlpha(float alpha)
{
// Clmap : 값 제한
alpha = Math::Clamp(alpha, 0.0f, 2.0f);
blendDesc.Alpha = alpha;
}
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;
};
AnimationDemo.cpp
#include "stdafx.h"
#include "AnimationDemo.h"
#include "Converter.h"
void AnimationDemo::Initialize()
{
Context::Get()->GetCamera()->RotationDegree(17, 0, 0);
Context::Get()->GetCamera()->Position(0, 6, -41);
shader = new Shader(L"49_AnimationBlending.fx");
Kachujin();
}
void AnimationDemo::Update()
{
if (kachujin != nullptr) kachujin->Update();
}
void AnimationDemo::Render()
{
//static float speed = 1.0f;
//static float takeTime = 1.0f;
//static int clip = 0;
//static bool bBlendMode = false;
//static float blendAlpha = 0.0f;
//
//ImGui::Checkbox("BlendMode", &bBlendMode);
//if (bBlendMode == false)
//{
// ImGui::InputInt("Clip", &clip);
// clip %= 5;
// ImGui::SliderFloat("Speed", &speed, 0.1f, 5.0f);
// ImGui::SliderFloat("TakeTime", &takeTime, 0.1f, 5.0f);
// if (ImGui::Button("Apply"))
// kachujin->PlayTweenMode(clip, speed, takeTime);
//}
//else
//{
// ImGui::SliderFloat("Alpha", &blendAlpha, 0.0f, 2.0f);
// kachujin->SetBlendAlpha(blendAlpha);
// if (ImGui::Button("Apply"))
// kachujin->PlayBlendMode(0, 1, 2);
//}
//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->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 Walk");
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);
}
AnimationBlending.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;
};
struct TweenFrame
{
float TakeTime;
float TweenTime;
float RunningTime;
float Padding;
AnimationFrame Curr;
AnimationFrame Next;
};
// 묶어서 전역변수로 쓰기 위해
cbuffer CB_TweenFrame
{
TweenFrame TweenFrames;
};
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 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.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++)
{
// 가중치가 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.TweenTime);
}
// 최종행렬에 가중치만 누적시키면 된다.
// 곱한걸 계속 누적시키는 것(계속 영향을 받을 수 있도록)
// 초반에 설명한 weights[i] 0.5라면 0.5만큼 curr에 곱해줌(움직여줌)
// transform : 가중치, 프레임까지 결합된 행렬
transform += mul(weights[i], currAnim);
}
// 최종행렬을 world로 변환
// transform : 애니메이션이 이동할 행렬, world : 모델이 출력할 world
world = mul(transform, world);
}
struct BlendFrame
{
uint Mode;
float Alpha;
float2 Padding;
AnimationFrame Clip[3];
};
cbuffer CB_BlendFrame
{
// s는 인스턴싱 때문에 미리 붙여놓음
BlendFrame BlendFrames;
};
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;
// 현재 동작 구해줌
[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, BlendFrames.Clip[k].CurrFrame, BlendFrames.Clip[k].Clip, 0)); // 1행
c1 = TransformsMap.Load(int4(indices[i] * 4 + 1, BlendFrames.Clip[k].CurrFrame, BlendFrames.Clip[k].Clip, 0)); // 2행
c2 = TransformsMap.Load(int4(indices[i] * 4 + 2, BlendFrames.Clip[k].CurrFrame, BlendFrames.Clip[k].Clip, 0)); // 3행
c3 = TransformsMap.Load(int4(indices[i] * 4 + 3, BlendFrames.Clip[k].CurrFrame, BlendFrames.Clip[k].Clip, 0)); // 4행
curr = matrix(c0, c1, c2, c3);
n0 = TransformsMap.Load(int4(indices[i] * 4 + 0, BlendFrames.Clip[k].NextFrame, BlendFrames.Clip[k].Clip, 0)); // 1행
n1 = TransformsMap.Load(int4(indices[i] * 4 + 1, BlendFrames.Clip[k].NextFrame, BlendFrames.Clip[k].Clip, 0)); // 2행
n2 = TransformsMap.Load(int4(indices[i] * 4 + 2, BlendFrames.Clip[k].NextFrame, BlendFrames.Clip[k].Clip, 0)); // 3행
n3 = TransformsMap.Load(int4(indices[i] * 4 + 3, BlendFrames.Clip[k].NextFrame, BlendFrames.Clip[k].Clip, 0)); // 4행
next = matrix(n0, n1, n2, n3);
currAnim[k] = lerp(curr, next, BlendFrames.Clip[k].Time);
}
// 0, 1, 2 구간이 떨어짐
int clipA = (int) BlendFrames.Alpha;
int clipB = clipA + 1;
float alpha = BlendFrames.Alpha;
if(alpha >= 1.0f)
{
// 1 ~ 2구간을 가야하니까 -1을 시킴
// lerp를 하기 위해 0 ~ 1구간을 만들어야 해서 -1을 함
alpha = BlendFrames.Alpha - 1.0f;
// 2를 넘었다면 플레이 구간이 1 ~ 2이다.
if(BlendFrames.Alpha >= 2.0f)
{
clipA = 1;
clipB = 2;
}
}
anim = lerp(currAnim[clipA], currAnim[clipB], alpha);
transform += mul(weights[i], anim);
}
world = mul(transform, world);
}
VertexOutput VS(VertexModel input)
{
VertexOutput output;
//World = mul(BoneTransforms[BoneIndex], World);
//SetTweenWorld(World, input);
if(BlendFrames.Mode == 0)
SetTweenWorld(World, input);
else
SetBlendWorld(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)
}
결과
- 언리얼 BlendSpace1D에서 Speed 변수로 Blending 해줬다면, DX에서는 Alpha값(0 ~ 2) 값으로 Blending 해줬다.
'DirectX11 3D > 기본 문법' 카테고리의 다른 글
| <DirectX11 3D> 51 - Instancing(Mesh) (0) | 2022.02.22 |
|---|---|
| <DirectX11 3D> 50 - Instancing(인스턴싱) (0) | 2022.02.21 |
| <DirectX11 3D> 48 - Animation(FrameLerp, Tween) (0) | 2022.02.21 |
| <Directx11 3D> 46 - Animation Rendering (0) | 2022.02.17 |
| <Directx11 3D> 42 - Animation Clip Read & Write (0) | 2022.02.16 |