Instancing

ㅋㅋ·2022년 7월 28일
0

DirectX12강의

목록 보기
33/39

버텍스, 인덱스, 메테리얼 등이 동일한 물체가 여러 개로 존재한다면,

해당 정보들을 계속해서 받는 것이 아니라 한번 받은 정보를 재사용하여

그려주도록 함


인풋 어셈블러에 정점 정보들과

해당 정점들을 사용하는 물체들의 position 값을 같이 넘겨주어

instancing 할 수 있음


instancing buffer 클래스

struct InstancingParams
{
	Matrix matWorld;
	Matrix matWV;
	Matrix matWVP;
};

class InstancingBuffer
{
public:
	...

	void Init(uint32 maxCount = 10);

	void Clear();
	void AddData(InstancingParams& params);
	void PushData();

public:
	...

private:
	uint64						_instanceId = 0;
	ComPtr<ID3D12Resource>		_buffer;
	D3D12_VERTEX_BUFFER_VIEW	_bufferView;

	uint32						_maxCount = 0;
	vector<InstancingParams>	_data;
};

인풋 어셈블러에 넣을 물체들의 position 관련 행렬들을 버텍스 버퍼로 구현


instancing manager 클래스

카메라가 하던 렌더 역할을 이전한 싱글턴 매니저 클래스

class InstancingManager
{
	DECLARE_SINGLE(InstancingManager);

public:
	void Render(vector<shared_ptr<GameObject>>& gameObjects);

	...

private:
	map<uint64, shared_ptr<InstancingBuffer>> _buffers;
};

물체를 ID 별로 매핑하여 인스턴싱 버퍼에 넣어 관리


void InstancingManager::Render(vector<shared_ptr<GameObject>>& gameObjects)
{
	map<uint64, vector<shared_ptr<GameObject>>> cache;

	for (shared_ptr<GameObject>& gameObject : gameObjects)
	{
		const uint64 instanceId = gameObject->GetMeshRenderer()->GetInstanceID();
		cache[instanceId].push_back(gameObject);
	}

	for (auto& pair : cache)
	{
		...
        
        if (vec.size() == 1)
		{
			vec[0]->GetMeshRenderer()->Render();
		}
		else
		{
			const uint64 instanceId = pair.first;

			for (const shared_ptr<GameObject>& gameObject : vec)
			{
				InstancingParams params;
				
                ...

				AddParam(instanceId, params);
			}

			shared_ptr<InstancingBuffer>& buffer = _buffers[instanceId];
			vec[0]->GetMeshRenderer()->Render(buffer);
		}
	}
}

렌더 시 동일 물체가 중복되는 경우

버퍼에 position 관련 정보를 넣고 instancing 방식으로 렌더


object 클래스 수정

class Object
{
	...

protected:
	uint32 _id = 0;
};

Object::Object(OBJECT_TYPE type) : _objectType(type)
{
	static uint32 idGenerator = 1;
	_id = idGenerator;
	idGenerator++;
}

오브젝트마다 각각 아이디를 갖도록 구현


mesh, mesh renderer 클래스 수정

메쉬와 메테리얼을 렌더하던 메쉬 렌더러가 인스턴싱 버퍼를 통하여 렌더할 수 있도록 수정


uint64 MeshRenderer::GetInstanceID()
{
	...
    
	InstanceID instanceID{ _mesh->GetID(), _material->GetID() };
	return instanceID.id;
}

void MeshRenderer::Render(shared_ptr<InstancingBuffer>& buffer)
{
	buffer->PushData();
	
    ...
	
    _mesh->Render(buffer);
}

void Mesh::Render(shared_ptr<InstancingBuffer>& buffer)
{
	D3D12_VERTEX_BUFFER_VIEW bufferViews[] = { _vertexBufferView, buffer->GetBufferView() };
	GRAPHICS_CMD_LIST->IASetVertexBuffers(0, 2, bufferViews);
	GRAPHICS_CMD_LIST->IASetIndexBuffer(&_indexBufferView);

	...

	GRAPHICS_CMD_LIST->DrawIndexedInstanced(_indexCount, buffer->GetCount(), 0, 0, 0);
}

메쉬 렌더러는 메쉬와 메테리얼 가지고 인스턴스 아이디를 생성

메쉬와 메테리얼이 모두 같은 객체는 인스턴싱 버퍼 뷰를 사용하게 됨

정점 정보가 들어가는 버텍스 버퍼 뷰와

위치 관련 행렬이 들어가는 인스턴싱 버퍼 뷰를

D3D12_VERTEX_BUFFER_VIEW에 같이 넣어 draw 요청


shader 클래스 수정

void Shader::CreateGraphicsShader(const wstring& path, ShaderInfo info, const string& vs, const string& ps, const string& gs)
{
	...
    
    D3D12_INPUT_ELEMENT_DESC desc[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },

		{ "W", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0,  D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "W", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "W", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "W", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WV", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 64, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WV", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 80, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WV", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 96, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WV", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 112, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WVP", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 128, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WVP", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 144, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WVP", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 160, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
		{ "WVP", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 176, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1},
	};
    
    ...
}

1번 슬롯에는 행렬들의 행들을 각각 넣어주도록 수정

D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA 플래그를 통하여

정보가 인스턴스마다라는 것을 알려줌


deferred shader 파일 수정

struct VS_IN
{
    ...

    row_major matrix matWorld : W;
    row_major matrix matWV : WV;
    row_major matrix matWVP : WVP;
    uint instanceID : SV_InstanceID;
};

추가된 정보들을 받을 수 있도록 VS_IN 데이터 추가

SV_InstanceID는 systemp value로 자동으로 쉐이더에서 붙게 됨


VS_OUT VS_Main(VS_IN input)
{
    ...

    if (g_int_0 == 1)
    {
        output.pos = mul(float4(input.pos, 1.f), input.matWVP);
        output.uv = input.uv;

        ...
    }
    
    ...

    return output;
}

전역 변수 g_int_0을 통해서 인스턴싱 판별

인스턴싱이 적용된 경우 input에서 데이터를 사용

또한 정점마다 자신의 순서에 맞는 인스턴스 데이터를 사용


camera 클래스 수정

void Camera::Render_Deferred()
{
	...

	GET_SINGLE(InstancingManager)->Render(_vecDeferred);
}

void Camera::Render_Forward()
{
	...

	GET_SINGLE(InstancingManager)->Render(_vecForward);

	...
}

카메라가 렌더 시에 인스턴싱 매니져를 통하여 렌더


engine 클래스 수정

void Engine::Update()
{
	...
    
	GET_SINGLE(InstancingManager)->ClearBuffer();

	Render();

	...
}

엔진 클래스에서 프레임마다 인스턴싱된 버퍼들을 청소


for (int32 i = 0; i < 50; i++)
{
	...
    
	shared_ptr<MeshRenderer> meshRenderer = make_shared<MeshRenderer>();
    
	shared_ptr<Mesh> sphereMesh = GET_SINGLE(Resources)->LoadSphereMesh();
	meshRenderer->SetMesh(sphereMesh);
    
	shared_ptr<Material> material = GET_SINGLE(Resources)->Get<Material>(L"GameObject");
	material->SetInt(0, 1);
    
	meshRenderer->SetMaterial(material);
    
	obj->AddComponent(meshRenderer);
	scene->AddGameObject(obj);
}

메테리얼을 미리 만들어두고 해당 메테리얼을 불러와서 사용


PIX on Windows

쉐이더를 디버깅하기 위한 프로그램

GPU 캡쳐 후 파이프라인 탭에 가면 해당 프레임이 만들어지기까지의 히스토리를 볼 수 있음

아웃풋 머져에서 픽셀을 누른 후 debug pixel을 누르면

해당 픽셀을 만든 픽셀 쉐이더를 디버깅해볼로 수 있음

0개의 댓글