프로그래밍 공부
작성일
2023. 11. 28. 14:48
작성자
WDmil
728x90

Instancing

인스턴싱은, 최적화 기법중 하나로 동일 객체나 요소를 여러번 그리는 작업을 최적화 하기 위한 기술이다.

 

동일한 메시를 그릴 때 여러번 DrawCall 을 할 필요 없이 한번만 DrawCall 을 한 뒤, 같은 모델을 여러번 배치하는 형태로 이루어진다.

 

한번 생성된 데이터는 다시 날릴 필요 없이. 그 횟수동안 받아온 위치정보만 대입하여 그려주면 되기 때문에

 

여러번 데이터버퍼를 받아올 필요가 없기 때문이다.

 

이는 DrawCall 이 데이터를 Device에서 DeviceContext에서 넘기는 과정에서 생기는 Bus Latency나 Moemory Latency가 쌓이면 엄청난 시간적 손해가 발생하기 때문이다.

 

그래서 DrawCall 은 리소스를 많이 쓰는 작업이고 이러한 DrawCall을 줄이면 줄일수록 더 효율적인 프로그램이 된다.

 

인스턴싱은 이러한 DrawCall을 줄일 수 있는 원론적인 방법중 한가지 이다.

 

Instancing의 조건

인스턴싱이 아무리 효율적이라고 해도, 정의하고 생성하는데 아무조건이 없는것 이 아니다.

 

인스턴싱을 할 객체간 공유되는 데이터가 존재해야 Instancing이 가능하며, 이는 다음과 같다.

 

MeshData를 설정하고 정의하는 VertexData

MeshData를 설정하고 정의하는 indexData

 

DrawCall을 할 때 생성되는 srv데이터

 

위 3가지가 동일할 때 에만 인스턴싱을 할 수 있다.


기본원리

 

예를들어 VertexBuffer데이터에 다음과 같은 정보가 포함된다고 해보자.

Pos

Uv

Tan

Normal

 

우리는 이러한 데이터가 묶여있다고 가정할 때, 여기의 데이터중 Pos데이터만 다른것으로 바꾸면

 

같은 객체가 다른위치에 존재할 수 있다는걸 알 수 있다.

 

 

위와같은 형태의 데이터구조가 된다.

 

여기서 중간의 데이터는 Pos의 Matrix데이터가 된다.

 

정의된 데이터의 위치값을 Matrix데이터가 바꾸고 그 바꾼 위치값대로 다시 Light등 데이터를 재처리하게 된다.

 

즉, DataBuffer를 호출하거나 보내는 일 없이 DeviceContext에서 모든 데이터를 처리할 수 있다는 의미이다.


인스턴싱을 안썼을때의 경우

인스턴싱을 안쓰고 간단한 Quad를 1000번 호출하여 이미지를 생성해보자.

	quads.resize(COUNT);
	for (Quad*& quad : quads)
	{
		quad = new Quad();
		quad->GetMaterial()->SetDiffuseMap(L"Textures/Landscape/Box.png");

		Vector3 pos(MATH->Random(-10, 10), MATH->Random(-10, 10));
		quad->SetLocalPosition(pos);
		quad->UpdateWorld();
	}

프레임이 197정도 나온다.


인스턴싱을 썼을의 경우

위 1000개의 사각형에 인스턴싱을 적용해서 처리해보자.

 

//TextureInstancing
#include "../VertexHeader.hlsli"
#include "../PixelHeader.hlsli"

struct VertexInput
{
	float4 pos : POSITION;
	float2 uv : UV;
	
	matrix transform : INSTANCE_TRANSFORM;
};

struct PixelInput
{
	float4 pos : SV_POSITION;
	float2 uv : UV;
};

PixelInput VS(VertexInput input)
{
	PixelInput output;
	output.pos = mul(input.pos, input.transform);
	output.pos = mul(output.pos, view);
	output.pos = mul(output.pos, projection);
	
	output.uv = input.uv;
	
	return output;
}


float4 PS(PixelInput input) : SV_TARGET
{
	return diffuseMap.Sample(samp, input.uv) * mDiffuse;
}

우선 hlsl을 새로 만들어준다.

 

우리는 같은 Buffer를 사용해서 다른 Matrix를 적용해야 할것이다.

 

이때, Buffer로 받아올 VertexInput은 위치값인 transform을 위와같이 가져와서,

 

Outputpos를 transform으로 정의해서 사용해야 한다.

 

받아온 pos에 transform을 사용해서. 위치 Matrix로 위치를 바꾸어주는것 이다.

 

그리고 VertexBuffer에서 데이터를 넘겨줄 때 위치slot을 정의해서 1000개의 Matrix를 DeviceContext에 넘겨준다.

void VertexBuffer::Set(UINT slot, D3D11_PRIMITIVE_TOPOLOGY type)
{
    DC->IASetVertexBuffers(slot, 1, &buffer, &stride, &offset);
    DC->IASetPrimitiveTopology(type);
}

1번 버퍼에 넘겨줄것이다.

        temp = temp.substr(0, n);
        if (temp == "INSTANCE")
        {
            elementDesc.InputSlot = 1;
            elementDesc.InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
            elementDesc.InstanceDataStepRate = 1;
        }

vertexShader에서 데이터를 넘겨줄 때 이름이 INSTANCE일경우 슬롯을 1로 변환하고 INSTANCE_DATA라는것을 정의해준다.

 

그리고 RenderCall시, DrawInstanced라고 Instanced전용 데이터를 넘겨주고 RenderCall을 해주어야 한다.

inline void Mesh<T>::DrawInstanced(UINT instanceCount, D3D11_PRIMITIVE_TOPOLOGY type)
{
	vertexBuffer->Set(type);

	if (indexBuffer)
	{
		indexBuffer->Set();
		DC->DrawIndexedInstanced(indices.size(), instanceCount, 0, 0, 0);
	}
	else
	{
		DC->DrawInstanced(vertices.size(), instanceCount, 0, 0);
	}
}

Buffer에 Indstaced를 정의해주어, 해당 Buffer가 몃번 사용될것 인지 알려주어야 한다.

 

Draw될 객체가 정해진값 대로 다 사용되었으면 삭제해야 데이터가 절약된다.

InstancingScene::InstancingScene()
{
	//quads.resize(COUNT);
	//for (Quad*& quad : quads)
	//{
	//	quad = new Quad();
	//	quad->GetMaterial()->SetDiffuseMap(L"Textures/Landscape/Box.png");

	//	Vector3 pos(MATH->Random(-10, 10), MATH->Random(-10, 10));
	//	quad->SetLocalPosition(pos);
	//	quad->UpdateWorld();
	//}

	quad = new Quad();
	quad->GetMaterial()->SetDiffuseMap(L"Textures/Landscape/Box.png");
	quad->GetMaterial()->SetShader(L"Instancing/TextureInstancing.hlsl");

	instanceData.resize(COUNT);

	for (Matrix& transform : instanceData)
	{
		Vector3 pos(MATH->Random(-10, 10), MATH->Random(-10, 10));

		transform = XMMatrixTranslation(pos.x, pos.y, pos.z);

		transform = XMMatrixTranspose(transform); // 전치행렬화
	}

	instanceBuffer = new VertexBuffer(instanceData.data(), sizeof(Matrix), COUNT);

}

InstancingScene::~InstancingScene()
{
	delete quad;
	delete instanceBuffer;
}

void InstancingScene::Update()
{
}

void InstancingScene::PreRender()
{
}

void InstancingScene::Render()
{
	//for (Quad* quad : quads)
	//	quad->Render();

	instanceBuffer->Set(1);
	quad->RenderInstanced(COUNT);

}

void InstancingScene::PostRender()
{
}

void InstancingScene::GUIRender()
{
}

 

이제 이 인스턴싱된 객체를 출력해보자.

 

여기서 RenderInstaced가 행될때, COUNT값을 넘겨 1000번 사용될것 임을 명시해주고.

 

그 Matrix의 개수는 Registaer 1번에 준다.

FPS가 엄청 크게 뛴것을 볼 수 있다.

 

728x90

'컴퓨터 용어 정리' 카테고리의 다른 글

행동 객체화 방법 [ FSM 응용 ]  (0) 2023.12.01
유한 상태 기계방법[ FSM(Finite State Machine) ]  (0) 2023.12.01
20231109 27일차 Font, Frustum  (0) 2023.11.09
Frustum 기법  (0) 2023.11.09
DIrectx11 Dll Setting  (0) 2023.11.02