통합해서 관리하는 걸 만들고
SceneManager랑 나머지 Component 안 만든 거 정리를 하면서 진행해 본다.
1. RenderDemo 클래스를 만들고 세팅하기
AnimInstancingDemo클래스를 복붙 해서 이름을 RenderDemo로 한다.
Client/Game 필터에 넣는다. 코드를 알맞게 수정한다.
_shader = make_shared<Shader>(L"23. RenderDemo.fx");
목적은 결국 애니메이션이건 일반 모델이건 메쉬건 상관없이 통합해서 관리할 수 있게끔 만들어 줘야 된다가 미션이다.
Main에도 RenderDemo를 세팅한다.
#include "RenderDemo.h"
desc.app = make_shared<RenderDemo>(); // 실행 단위
Engine 쪽 코드는 거의 건드릴 게 없고, Shader 코드를 통합해서 만드는 게 목적이다.
2. 23. RenderDemo.fx 쉐이더 만들기
셰이더 22. AnimInstancingDemoi.fx를 복붙하고 이름을 23. RenderDemo.fx로 한 뒤 Client/Shaders/Week3 필터에 넣는다.
이 쉐이더는 20, 21, 22를 통합할 예정이다.
3. 00. Render.fx 쉐이더 만들고 20. MeshInstancingDemo.fx, 21. ModelInstancingDemo.fx, 22. AnimInstancingDemo.fx에서 필요한 코드를 가져오기
3번째 global인 00. Render.fx를 만든다. 00. Light.fx를 복붙한 뒤 이름을 00. Render.fx로 한다.
Client/Shaders/Common 필터에 넣는다.
인스턴싱 관련된 잡동사니들을 다 몰빵해 놓을 것이다.
#ifndef _RENDER_FX_
#define _RENDER_FX_
#include "00. Global.fx"
#endif
이렇게 두고 시작한다.
23. RenderDemo.fx에서
#include "00. Render.fx"
하면 내용이 다 포함이 되어야 한다.
23. RenderDemo.fx 코드는
#include "00. Global.fx"
#include "00. Light.fx"
#include "00. Render.fx"
float4 PS(MeshOutput input) : SV_TARGET
{
//float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
float4 color = DiffuseMap.Sample(LinearSampler, input.uv);
return color;
}
technique11 T0
{
PASS_VP(P0, VS_Mesh, PS)
PASS_VP(P1, VS_Model, PS)
PASS_VP(P2, VS_Animation, PS)
};
이 상태에서 실행이 되어야 된다.
나머지 잡동사니들을 00. Render.fx에 몰빵 다.
00. Render.fx에 20. MeshInstancingDemo.fx, 21. ModelInstancingDemo.fx, 22. AnimInstancingDemo.fx의 코드를 가져온다.
#ifndef _RENDER_FX_
#define _RENDER_FX_
#include "00. Global.fx"
#define MAX_MODEL_TRANSFORMS 250
#define MAX_MODEL_KEYFRAMES 500
#define MAX_MODEL_INSTANCE 500
// ************* MeshRender *************
struct VertexMesh
{
float4 position : POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
// INSTANCING
uint instanceID : SV_InstanceID;
matrix world : INST;
};
MeshOutput VS_Mesh(VertexMesh input)
{
MeshOutput output;
output.position = mul(input.position, input.world); // W
output.worldPosition = output.position;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = input.normal;
return output;
}
// ************* ModelRender *************
struct VertexModel
{
float4 position : POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
float4 blendIndices : BLEND_INDICES; // 애니메이션 블렌딩 할 거
float4 blendWeights : BLEND_WEIGHTS;
// INSTANCING
uint instanceID : SV_InstanceID;
matrix world : INST;
};
cbuffer BoneBuffer
{
matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
};
uint BoneIndex;
MeshOutput VS_Model(VertexModel input)
{
MeshOutput output;
output.position = mul(input.position, BoneTransforms[BoneIndex]); // Model Global (Root를 기준)로 일단 가기
output.position = mul(output.position, input.world); // W
output.worldPosition = output.position;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = input.normal;
return output;
}
// ************* AnimRender *************
struct KeyframeDesc
{
int animIndex;
uint currFrame;
uint nextFrame;
float ratio;
float sumTime;
float speed;
float2 padding;
};
struct TweenFrameDesc
{
float tweenDuration; // 얼마만큼 할지
float tweenRatio;
float tweenSumTime; // 경과시간
float padding;
KeyframeDesc curr; // 현재 애니메이션
KeyframeDesc next; // 바뀌어질 애니메이션(없으면 현재걸 트러주면 된다.)
};
cbuffer TweenBuffer
{
TweenFrameDesc TweenFrames[MAX_MODEL_INSTANCE];
};
Texture2DArray TransformMap;
matrix GetAnimationMatrix(VertexModel input)
{
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 animIndex[2];
int currFrame[2];
int nextFrame[2];
float ratio[2];
animIndex[0] = TweenFrames[input.instanceID].curr.animIndex;
currFrame[0] = TweenFrames[input.instanceID].curr.currFrame;
nextFrame[0] = TweenFrames[input.instanceID].curr.nextFrame;
ratio[0] = TweenFrames[input.instanceID].curr.ratio;
animIndex[1] = TweenFrames[input.instanceID].next.animIndex;
currFrame[1] = TweenFrames[input.instanceID].next.currFrame;
nextFrame[1] = TweenFrames[input.instanceID].next.nextFrame;
ratio[1] = TweenFrames[input.instanceID].next.ratio;
float4 c0, c1, c2, c3; // current
float4 n0, n1, n2, n3; // next
matrix curr = 0;
matrix next = 0;
matrix transform = 0;
for (int i = 0; i < 4; i++)
{
c0 = TransformMap.Load(int4(indices[i] * 4 + 0, currFrame[0], animIndex[0], 0));
c1 = TransformMap.Load(int4(indices[i] * 4 + 1, currFrame[0], animIndex[0], 0));
c2 = TransformMap.Load(int4(indices[i] * 4 + 2, currFrame[0], animIndex[0], 0));
c3 = TransformMap.Load(int4(indices[i] * 4 + 3, currFrame[0], animIndex[0], 0));
curr = matrix(c0, c1, c2, c3);
n0 = TransformMap.Load(int4(indices[i] * 4 + 0, nextFrame[0], animIndex[0], 0));
n1 = TransformMap.Load(int4(indices[i] * 4 + 1, nextFrame[0], animIndex[0], 0));
n2 = TransformMap.Load(int4(indices[i] * 4 + 2, nextFrame[0], animIndex[0], 0));
n3 = TransformMap.Load(int4(indices[i] * 4 + 3, nextFrame[0], animIndex[0], 0));
next = matrix(n0, n1, n2, n3);
matrix result = lerp(curr, next, ratio[0]);
// 다음 애니메이션이 있는지 체크
if (animIndex[1] >= 0)
{
c0 = TransformMap.Load(int4(indices[i] * 4 + 0, currFrame[1], animIndex[1], 0));
c1 = TransformMap.Load(int4(indices[i] * 4 + 1, currFrame[1], animIndex[1], 0));
c2 = TransformMap.Load(int4(indices[i] * 4 + 2, currFrame[1], animIndex[1], 0));
c3 = TransformMap.Load(int4(indices[i] * 4 + 3, currFrame[1], animIndex[1], 0));
curr = matrix(c0, c1, c2, c3);
n0 = TransformMap.Load(int4(indices[i] * 4 + 0, nextFrame[1], animIndex[1], 0));
n1 = TransformMap.Load(int4(indices[i] * 4 + 1, nextFrame[1], animIndex[1], 0));
n2 = TransformMap.Load(int4(indices[i] * 4 + 2, nextFrame[1], animIndex[1], 0));
n3 = TransformMap.Load(int4(indices[i] * 4 + 3, nextFrame[1], animIndex[1], 0));
next = matrix(n0, n1, n2, n3);
matrix nextResult = lerp(curr, next, ratio[1]);
result = lerp(result, nextResult, TweenFrames[input.instanceID].tweenRatio);
}
transform += mul(weights[i], result);
}
return transform;
}
MeshOutput VS_Animation(VertexModel input)
{
MeshOutput output;
//output.position = mul(input.position, BoneTransforms[BoneIndex]); // Model Global (Root를 기준)로 일단 가기
matrix m = GetAnimationMatrix(input);
output.position = mul(input.position, m);
output.position = mul(output.position, input.world); // W
output.worldPosition = output.position;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)input.world);
output.tangent = mul(input.tangent, (float3x3)input.world);
return output;
}
#endif
이렇게 00. Render.fx에 필요한 모든 애들을 넣어 놨으니까 필요한 애들은 23. RenderDemo.fx 처럼 꺼내 써가지고 작업을 할 수 있게 만들어 주면 된다.
Client 프로젝를 빌드한다.
4. RenderDemo.cpp 에서 Animation, Model, Mesh의 obj와 Render pass를 세팅하기
통합이 된 상태이다. 23. RenderDemo.fx는 필요한 애들만 골라서
technique11 T0
{
PASS_VP(P0, VS_Mesh, PS)
PASS_VP(P1, VS_Model, PS)
PASS_VP(P2, VS_Animation, PS)
};
이렇게 몇번 째 Pass인지만 설정해 주면 되고,
최종적으로 작업하고 있는 RenderDemo.cpp 에서는 이거에 따라가지고 Render pass를 맞춰주기만 하면 모든 애들이 정상적으로 잘 돌아가야 한다고 결론을 낼 수 있다.
RenderDemo::Init에서
obj->GetModelAnimator()->SetPass(2); // 애니메이션
이런식으로 해주고
model과 관련된 부분은 원래 작업하던 것을 긁어 오면 된다. Pass를 1로 둔다.
// Model
shared_ptr<class Model> m2 = make_shared<Model>();
m2->ReadModel(L"Tower/Tower");
m2->ReadMaterial(L"Tower/Tower");
for (int32 i = 0; i < 100; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
obj->GetOrAddTransform()->SetScale(Vec3(0.01f));
obj->AddComponent(make_shared<ModelRenderer>(_shader));
{
obj->GetModelRenderer()->SetModel(m2);
obj->GetModelRenderer()->SetPass(1); // Model
}
_objs.push_back(obj);
}
그리고 Mesh의 경우는 Pass를 0으로 두면 된다.
// Mesh
// Material
{
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(_shader);
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\\\Resources\\\\Textures\\\\veigar.jpg");
material->SetDiffuseMap(texture);
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
RESOURCES->Add(L"Veigar", material);
}
for (int32 i = 0; i < 100; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetLocalPosition(Vec3(rand() % 100, 0, rand() % 100));
obj->AddComponent(make_shared<MeshRenderer>());
{
obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
}
{
auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
obj->GetMeshRenderer()->SetMesh(mesh);
obj->GetMeshRenderer()->SetPass(0); // Mesh
}
_objs.push_back(obj);
}
작업했던 Veigar 입힌 구를 그대로 사용한다.
이렇게 원하는 개수를 각각 입력을 한 다음에
쉐이더가 렌더 된 거가 정상적으로 동작을 한다면 별다른 문제없이 모든 물체들이 동일한 화면에 다 뜨게 된다.
#include "pch.h"
#include "RenderDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
void RenderDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"23. RenderDemo.fx");
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
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");
for (int32 i = 0; i < 500; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
obj->GetOrAddTransform()->SetScale(Vec3(0.01f)); // Tower는 너무 크기 때문에 크기를 줄여 준다.
obj->AddComponent(make_shared<ModelAnimator>(_shader));
{
obj->GetModelAnimator()->SetModel(m1); // model이 mesh랑 material 관련 부분들을 다 들고 있는 식으로 만들어 놨다.
obj->GetModelAnimator()->SetPass(2); // 애니메이션
}
_objs.push_back(obj);
}
// Model
shared_ptr<class Model> m2 = make_shared<Model>();
m2->ReadModel(L"Tower/Tower");
m2->ReadMaterial(L"Tower/Tower");
for (int32 i = 0; i < 100; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
obj->GetOrAddTransform()->SetScale(Vec3(0.01f));
obj->AddComponent(make_shared<ModelRenderer>(_shader));
{
obj->GetModelRenderer()->SetModel(m2);
obj->GetModelRenderer()->SetPass(1); // Model
}
_objs.push_back(obj);
}
// Mesh
// Material
{
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(_shader);
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\\\Resources\\\\Textures\\\\veigar.jpg");
material->SetDiffuseMap(texture);
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
RESOURCES->Add(L"Veigar", material);
}
for (int32 i = 0; i < 100; i++)
{
auto obj = make_shared<GameObject>();
obj->GetOrAddTransform()->SetLocalPosition(Vec3(rand() % 100, 0, rand() % 100));
obj->AddComponent(make_shared<MeshRenderer>());
{
obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
}
{
auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
obj->GetMeshRenderer()->SetMesh(mesh);
obj->GetMeshRenderer()->SetPass(0); // Mesh
}
_objs.push_back(obj);
}
RENDER->Init(_shader);
}
void RenderDemo::Update()
{
_camera->Update();
RENDER->Update();
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.4f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(0.1f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f);
RENDER->PushLightData(lightDesc);
}
// INSTANCING
INSTANCING->Render(_objs);
}
void RenderDemo::Render()
{
}
_objs에다가 몰빵 해서 넣어 놨지만 인스턴싱이 적용이 되어가지고 분류가 잘 된다고 하면
실행하면
캐릭터, 건물, vegar 구슬도 있는 걸 볼 수 있다.
여기까지 만들었다면
terrain을 깔아주고, skyBox를 입히기만 해도 꽤 그럴싸하게 보인다.
포폴을 만들 때 갭이 커 보이지만 돌아가는 무엇인가를 만들었으면 여기에 이펙트와 충돌만 붙이고 마지막으로 길 찾기만 할 수 있으면 이상적일 것이다.
그러면 상용 엔진으로 작업하는 것과 다를 게 없다.
물론 이걸 더 예쁘게 만드는 건 다른 문제다.
조명, 그림자 등이 필요하다.
서버까지 붙여서 연동을 하면 아름다울 것이다.
결국 Instancing이라는 기법을 알았고, Rendering을 할 때 항상 신경 썼던 DrawCall 이 어떤 의미인지 알게 되었다.
이렇게 최종적인 합본을 완성했다.
'DirectX' 카테고리의 다른 글
72_Quaternion (0) | 2024.03.20 |
---|---|
71_인스턴싱_Scene 구조 정리 (0) | 2024.03.20 |
69_인스턴싱_ModelAnimation(인스턴싱) (0) | 2024.03.18 |
68_인스턴싱_ModelRenderer(인스턴싱) (0) | 2024.03.16 |
67_인스턴싱_MeshRenderer(인스턴싱) (0) | 2024.03.14 |
댓글