DirectX11

GPU Instancing

RuNaPi 2023. 6. 22. 16:33

하나의 매쉬를 그리기 위해서는 매쉬 구성요소와 위치등을 알아야한다. 그렇기에 만약 여러개의 매쉬를 그리게 된다면 매쉬의 위치 값를 매번 업데이트 해주어야 하며, 매번 드로우콜이 발생이 된다.

인스턴싱을 사용하게 되면 똑같은 머티리얼을 사용하는 똑같은 매쉬의 경우 한번의 드로우 콜로 줄이는게 가능해진다.

Hardware Instancing

하드웨어 인스턴싱은 인스턴스 인풋 값을 사용하여 인스턴싱을 한다.

일단 정점 셰이더를 만들때 셰이더 리플렉션을 사용해서 인풋 레이 아웃을 만들때 Semantic을 읽어 Instance일 경우에는 인스턴스 인풋으로 만들도록 하였다.

void DX11Shader::ReflectInputLayout(ID3D11Device* device, ID3D11ShaderReflection* reflection, D3D11_SHADER_DESC& desc)
{
	// Create InputLayout
	...
		D3D11_SIGNATURE_PARAMETER_DESC paramDesc;
		reflection->GetInputParameterDesc(idx, &paramDesc);

		D3D11_INPUT_ELEMENT_DESC inputDesc;
		inputDesc.SemanticName = paramDesc.SemanticName;
		inputDesc.SemanticIndex = paramDesc.SemanticIndex;

		if (idx == 0)
		{
			inputDesc.AlignedByteOffset = 0;
		}
		else
		{
			inputDesc.AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
		}

		std::string semanticName = paramDesc.SemanticName;

		if (semanticName.find("INSTANCE_") == 0)
		{
			inputDesc.InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
			inputDesc.InputSlot = 1;
			inputDesc.InstanceDataStepRate = 1;
		}
		else
		{
			inputDesc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
			inputDesc.InputSlot = 0;
			inputDesc.InstanceDataStepRate = 0;
		}
	...
}

 

인스턴스에 사용할 버퍼를 하나 더 만들어준다. 스태틱 매쉬를 인스턴싱 하기위해 World Matrix와 World Inverse Matirx, 그리고 PBR에서 사용될 머티리얼 정보를 넣어 주었다.

if (m_InstanceBuffer != nullptr) m_RenderSystem->Release(*m_InstanceBuffer);

BufferDesc _desc;

_desc._size = size;
_desc._stride = sizeof(Matrix) * 2 + sizeof(Vector4) * 3;
_desc._format = Format::UNKNOWN;
_desc._bindFlags = BindFlags::VertexBuffer;
_desc._miscFlags = MiscFlags::DynamicUsage;

m_RenderSystem->CreateBuffer(GetUUID(), _desc);

m_BufferSize = size;

인스턴스 버퍼를 버텍스 버퍼에 같이 바인드 하기위하여

버텍스 버퍼를 바인드 할때 여러개의 버퍼를 바인드 할 수 있도록 추가 해주고

auto* _buffer = reinterpret_cast<DX11Buffer**>(buffers);

ID3D11Buffer* _buffers[] = { _buffer[0]->GetBuffer(), _buffer[1]->GetBuffer() };
uint32 _strides[] = { _buffer[0]->GetStride(), _buffer[1]->GetStride() };
uint32 _offsets[] = { 0, 0 };

m_Context->IASetVertexBuffers(0, 2, _buffers, _strides, _offsets);

 

그리고 매쉬의 버텍스 버퍼와 함께 바인드 해주면된다.

for (auto& _iter : m_InstanceQueue)
{
	for (auto& _meshInstance : _iter.second._meshInstanceDatas)
	{
		uint32 _meshCount = 0;

		do 
		{
			uint32 _drawNum = UpdateInstanceBuffer(_meshInstance, _meshCount);

			RenderPass* _renderPass = GetRenderPass(_iter.second._materialBuffer, _meshInstance._bIsSkin);

			BindRenderPass(_renderPass);

			_iter.second._materialBuffer->BindResource(m_CommandBuffer);

			auto* _vertexBuffer = _meshInstance._meshBuffer->GetBuffer();

			auto& _subMeshBuffer = _meshInstance._meshBuffer->GetSubMesh(_meshInstance._subMeshIdx);

			Buffer* _instance[2] = { _vertexBuffer , m_InstanceBuffer->GetInstanceBuffer() };

			m_CommandBuffer->SetVertexBuffer(_instance);

			m_CommandBuffer->SetIndexBuffer(*_subMeshBuffer.GetBuffer());

			m_CommandBuffer->DrawIndexedInstanced(_subMeshBuffer.GetIndexCount(), _drawNum, 0);

			_meshCount += _drawNum;
		} 
		while (_meshInstance._renderObjects.size() > _meshCount);
	}
}

 

한번에 모든 인스턴스를 할 경우 인스턴스 버퍼의 크기가 모자르기 때문에 만약 인스턴스 버퍼의 크기를 넘을 경우에는 반복해서 그리도록 하였다. 

#if defined(HARDWARE)
struct VSInstanceInput
{
	float4x4	_world		: INSTANCE_WORLD;
	float4x4	_worldInv	: INSTANCE_WORLDINV;

	// material property
	float4		_albedo		: INSTANCE_ALBEDO;
	float4		_emissive	: INSTANCE_EMISSIVE;
	float4		_pbr		: INSTANCE_PBR;
};

VSOutput main(VSInput input, VSInstanceInput instanceData, uint instanceID : SV_InstanceID)
{
    VSOutput _output;
    
    ....
    
    return _output;
}
#endif

 

그리고 쉐이더에서 하드웨어 인스턴싱을 사용하도록 정점 쉐이더를 변경해주면 된다.

 

 GPU Instancing

하드웨어에서 지원하는 인스턴스가 아닌 

'DirectX11' 카테고리의 다른 글

Tiled Resources (1)  (0) 2023.07.03
Texture Compression(Block Compression)  (0) 2023.06.27
Voxel GI - Cone Tracing  (0) 2023.05.25
Voxel GI - Voxelize  (0) 2023.05.25
Cascade Shadow  (0) 2023.02.16