DirectX

70_인스턴싱_인스턴싱 통합

devRiripong 2024. 3. 20.
반응형

통합해서 관리하는 걸 만들고

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

댓글