본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 50 - Instancing(인스턴싱)

 

필요한 개념


Instancing(인스턴싱) 구조 설명

 

 

Instancing(인스턴싱) : Mesh나 Model을 대량으로 그려낼 수 있는 기술(VertexBuffer(틀하나를 가지고)로 와장창 찍어낸다 해서 인스턴스)

InstancingDemo.h 를 실행시켜보면 프레임수가 상당히 낮다(하나씩 각각 그리기 때문에)

DpCall(Draw Primitive Call) : 렌더링 파이프라인을 한번 거치는 과정, 데이터를 세팅하고 모든 과정을 거쳐서
렌더링까지 가야 하므로 소요비용이 상당함, 게임에서는 최적화라고 하면 이것부터 줄이는 작업이 시작됨

DpCall을 1000번을 그리는데 DpCall을 1000번 씀
인스턴싱은 DpCall을 1000번을 그리는데 DpCall을 1번 씀, 이 상황에서는 (1/1000)로 줄어든다.
-> 그래서 세팅이 한번 일어나고, 그리는게 1번으로 바뀜(DpCall) -> 속도가 달라짐

현재 인스턴싱을 쓰지 않는 게임은 거의 없다.

VertexBuffer가 정점 정보를 가지고 있다.
인스턴싱은 하나가 더 들어감(VertexBuffer로)
-> 즉, VertexBuffer로 instanceBuffer가 하나더 생김

Buffer 슬롯 세팅
0 -> VertexBuffer
1 -> InstanceBuffer

instanceBuffer는 world 정보들을 가지고 있다.


-> 
VertexBuffer에는 정점정보가 들어가 있고
instanceBuffer(VertexBuffer로 만든)에는 SRT(world) 정보들이 들어가게 된다.

만약 1000개를 그리고 싶다면
InstanceBuffer에 1000개의 SRT(world)정보가 들어간다.
-> 즉 instance 배열 크기만큼 그려내는 것이다.

결국 Dp에서는 이것을 한번만 그리는 것처럼 보이지만 내부적으로는 갈라서 그리는 것이다.

기존 보다 속도가 월등히 올라감!

내 기준 기존 500프레임 -> 4000프레임으로 올라감

작업관리자 켜보면 -> GPU 엔진에 0번 1번이 매겨져 있다.
노트북에서는
0번 : 내장 그래픽
1번 : 외장 그래픽
교수님 PC에서는
0번 : 외장 그래픽
1번 : 내장 그래픽

내장 그래픽을 사용하면 프레임이 더 떨어져서 나온다.
-> 그래서 외장 그래픽으로 사용하도록 하는 코드로 바꿔준다.(D3D.h 관련)
또는 그래픽 카드 설정 이용해서 강제로 외장 그래픽카드로 작동하게 할 수 있다.

 

 

 

 

 

 

InstancingDemo.h


더보기
#pragma once
#include "Systems/IExecute.h"

class InstancingDemo : 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 CreateMesh();

private:
	Shader* shader;
	Material* material;

	vector<Mesh::MeshVertex> vertices;
	vector<UINT> indices;

	VertexBuffer* vertexBuffer; // 0번 슬롯
	VertexBuffer* instanceBuffer; // 1번 슬롯
	
	IndexBuffer* indexBuffer;

	PerFrame* perFrame;

	Transform* transforms[1000];
	Matrix worlds[1000];
};

 

 

 

 

 

 

 

InstancingDemo.Cpp


더보기
#include "stdafx.h"
#include "InstancingDemo.h"

void InstancingDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(0, 0, 0);
	Context::Get()->GetCamera()->Position(0, 0, -17);

	shader = new Shader(L"50_Instancing.fx");

	// 프레임 별로 밀어줄려고
	perFrame = new PerFrame(shader);

	// DiffuseMap 사용할려고
	material = new Material(shader);
	material->DiffuseMap(L"Box.png");

	for (UINT i = 0; i < 1000; i++)
	{
		//transforms[i] = new Transform(shader);

		transforms[i] = new Transform();

		transforms[i]->Position(Math::RandomVec3(-30, 30));
		transforms[i]->Scale(Math::RandomVec3(1.0f, 2.5f));
		transforms[i]->RotationDegree(Math::RandomVec3(-180, 180));

		// worlds 세팅
		worlds[i] = transforms[i]->World();
	}

	CreateMesh();

	// instance는 1번 슬롯을 사용한다.
	instanceBuffer = new VertexBuffer(worlds, 1000, sizeof(Matrix), 1);
}

void InstancingDemo::Update()
{
	perFrame->Update();

	// shader로 넘어갈게 아니여서 필요없다.(instanceBuffer가 넘어감)
	/*for (UINT i = 0; i < 1000; i++)
		transforms[i]->Update();*/
}

void InstancingDemo::Render()
{
	perFrame->Render();

	material->Render();

	vertexBuffer->Render();
	instanceBuffer->Render();

	indexBuffer->Render();

	D3D::GetDC()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	//for (UINT i = 0; i < 1000; i++)
	//{
	//	transforms[i]->Render();

	//	shader->DrawIndexed(0, 0, indices.size());
	//}

	//DpCall은 한번만 들어가면 된다.
	// 3번인자 : 인스턴스 하나당 인덱스의 개수
	// 4번인자 : 그릴려는 인스턴싱의 개수
	shader->DrawIndexedInstanced(0, 0, indices.size(), 1000);
}

void InstancingDemo::CreateMesh()
{
	float w, h, d;
	w = h = d = 0.5f;

	//Front
	vertices.push_back(Mesh::MeshVertex(-w, -h, -d, 0, 1, 0, 0, -1));
	vertices.push_back(Mesh::MeshVertex(-w, +h, -d, 0, 0, 0, 0, -1));
	vertices.push_back(Mesh::MeshVertex(+w, +h, -d, 1, 0, 0, 0, -1));
	vertices.push_back(Mesh::MeshVertex(+w, -h, -d, 1, 1, 0, 0, -1));

	//Back
	vertices.push_back(Mesh::MeshVertex(-w, -h, +d, 1, 1, 0, 0, 1));
	vertices.push_back(Mesh::MeshVertex(+w, -h, +d, 0, 1, 0, 0, 1));
	vertices.push_back(Mesh::MeshVertex(+w, +h, +d, 0, 0, 0, 0, 1));
	vertices.push_back(Mesh::MeshVertex(-w, +h, +d, 1, 0, 0, 0, 1));

	//Top
	vertices.push_back(Mesh::MeshVertex(-w, +h, -d, 0, 1, 0, 1, 0));
	vertices.push_back(Mesh::MeshVertex(-w, +h, +d, 0, 0, 0, 1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, +d, 1, 0, 0, 1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, -d, 1, 1, 0, 1, 0));

	//Bottom
	vertices.push_back(Mesh::MeshVertex(-w, -h, -d, 1, 1, 0, -1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, -h, -d, 0, 1, 0, -1, 0));
	vertices.push_back(Mesh::MeshVertex(+w, -h, +d, 0, 0, 0, -1, 0));
	vertices.push_back(Mesh::MeshVertex(-w, -h, +d, 1, 0, 0, -1, 0));

	//Left
	vertices.push_back(Mesh::MeshVertex(-w, -h, +d, 0, 1, -1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(-w, +h, +d, 0, 0, -1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(-w, +h, -d, 1, 0, -1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(-w, -h, -d, 1, 1, -1, 0, 0));

	//Right
	vertices.push_back(Mesh::MeshVertex(+w, -h, -d, 0, 1, 1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, -d, 0, 0, 1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(+w, +h, +d, 1, 0, 1, 0, 0));
	vertices.push_back(Mesh::MeshVertex(+w, -h, +d, 1, 1, 1, 0, 0));


	indices =
	{
		0, 1, 2, 0, 2, 3,
		4, 5, 6, 4, 6, 7,
		8, 9, 10, 8, 10, 11,
		12, 13, 14, 12, 14, 15,
		16, 17, 18, 16, 18, 19,
		20, 21, 22, 20, 22, 23
	};

	vertexBuffer = new VertexBuffer(&vertices[0], vertices.size(), sizeof(Mesh::MeshVertex));
	indexBuffer = new IndexBuffer(&indices[0], indices.size());
}

 

 

 

 

 

 

 

 

Instancing.fx


더보기
#include "00_Global.fx"

float3 Direction = float3(-1, -1, +1);

struct VertexInput
{
    // 0번 슬롯
    float4 Position : Position;
    float2 Uv : Uv;
    float3 Normal : Normal;
    
    // 1번 슬롯
    // Inst가 붙은 애들을 만든 Shader 클래스에서 1번으로 취급(이후에 설명)
    // world 정보가 들어가 있다.
    matrix Transform : Inst1;
};

struct VertexOutput
{
    float4 Position : SV_Position;
    float2 Uv : Uv;
    float3 Normal : Normal;
};

VertexOutput VS(VertexInput input)
{
    VertexOutput output;
    
    // 한번에 Transform에 1000개의 matrix가 들어온다.
    // 1번 버퍼에 의해 계속 콜될때 마다 0, 1, 2, 3.... 바뀜
    World = input.Transform;
    
    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
{
    float3 normal = normalize(input.Normal);
    float3 light = -Direction;
    
    return DiffuseMap.Sample(LinearSampler, input.Uv) * dot(light, normal);
}

technique11 T0
{
   P_VP(P0, VS, PS)
   P_RS_VP(P1, FillMode_WireFrame, VS, PS)
}

 

 

 

 

 

 

 

 

 

 

 

결과


Instancing을 사용하니 프레임이 4000가까이 나오는 것을 볼 수 있다.

(이전에는 각각 그려서(1000개) 기준 500 프레임이었다.)