헛둘이 2023. 2. 14. 14:26

붓 그리기로 치면,

전체적인 그림을 알고 있으면 빨간 붓을 들었을 때 이 참에 빨간 색이 들어간 모든 부분을 칠하는 것

 

이전에 DrawIndexedInstanced 함수에서 인스턴싱하는 방법과 별개로

IASetVertexBuffers에 여러 개의 버텍스 버퍼를 넣어줄 수 있다.

 

인스턴싱이란 하나의 정점 정보로 여러개의 물체를 그린다는 것.

어떤 물체가 인덱스 버퍼, 버텍스 버퍼가 같고, 머테리얼조차 같아서 동일한 쉐이더를 사용한다면

그 물체는 위치값만 바꿔서 여러 개로 찍어낼 수 있다.

 

InstancingBuffer 클래스

- 헤더에 구조체 InstancingParams를 선언하고, 이 구조체의 멤버는 월드행렬, 월드뷰행렬, 월드뷰프로젝션행렬이 있다.

- Init 함수에서 구조체의 개수를 받아서 InstancingParams * 개수만큼 버퍼 할당

- 내부적으로 InstanceParams의 벡터를 들고 있으며, AddData 멤버함수로 InstancingParams를 받아서 저장한다.

- PushData 함수로 이 정보들을 한번에 뭉쳐서 GPU 메모리에 매핑한다.

 

	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},
	};

- 쉐이더에 넘기는 구조를 의미하는 INPUT_ELEMENT에서 이 행렬값도 같이 넘기도록 세팅한다.

- 6번째 멤버의 경우 위의 4개와 달리 INSTANCE 어쩌고 되어 있는데,

- 이렇게 세팅해주면 위에서 위치정보 배열 통째로 넘긴 그 덩어리에서 자신의 인스턴스의 인덱스로 그 덩어리를 참조해서

- 자신의 위치에 그려줄 수 있게 된다.

 

struct VS_IN
{
    float3 pos : POSITION;
    float2 uv : TEXCOORD;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;

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

- 디퍼트 쉐이더에서 이 값을 이렇게 받는다.

 

 

InstancingManager 클래스

- 실질적으로 렌더링하는 주체

- 카메라의 내부에서 forward, deferred로 정렬된 오브젝트들의 인스턴스ID를 검사해서

- Material, Mesh의 ID가 동일한 물체들을 하나의 Map에 모아서 벡터로 관리하고,

- 벡터의 사이즈를 확인해서 1 이상이라면 그 위치정보를 자신의 InstancingBuffer에 넘겨서 업로드한 후 그려주는 방식

void Camera::Render_Deferred()
{
	S_MatView = _matView;
	S_MatProjection = _matProjection;

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

void Camera::Render_Forward()
{
	S_MatView = _matView;
	S_MatProjection = _matProjection;

	GET_SINGLE(InstancingManager)->Render(_vecForward);

	for (auto& gameObject : _vecParticle)
	{
		gameObject->GetParticleSystem()->Render();
	}
}

 

 

쉐이더에서도 인스턴싱 유무를 알아야 할텐데?

VS_OUT VS_Main(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;

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

        output.viewPos = mul(float4(input.pos, 1.f), input.matWV).xyz;
        output.viewNormal = normalize(mul(float4(input.normal, 0.f), input.matWV).xyz);
        output.viewTangent = normalize(mul(float4(input.tangent, 0.f), input.matWV).xyz);
        output.viewBinormal = normalize(cross(output.viewTangent, output.viewNormal));
    }
    else
    {
        output.pos = mul(float4(input.pos, 1.f), g_matWVP);
        output.uv = input.uv;

        output.viewPos = mul(float4(input.pos, 1.f), g_matWV).xyz;
        output.viewNormal = normalize(mul(float4(input.normal, 0.f), g_matWV).xyz);
        output.viewTangent = normalize(mul(float4(input.tangent, 0.f), g_matWV).xyz);
        output.viewBinormal = normalize(cross(output.viewTangent, output.viewNormal));
    }   

    return output;
}

- deferred의 VS에서 g_int_0의 값을 확인해서 인스턴싱할 물체라는 것을 인지한다.

- 이 값은 머티리얼을 로드할 때 세팅한다.

#pragma region Object
	for (int32 i = 0; i < 50; i++)
	{
		shared_ptr<GameObject> obj = make_shared<GameObject>();
		obj->AddComponent(make_shared<Transform>());
		obj->GetTransform()->SetLocalScale(Vec3(25.f, 25.f, 25.f));
		obj->GetTransform()->SetLocalPosition(Vec3(-300.f + i * 10.f, 0.f, 500.f));
		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);
			//material->SetInt(0, 0);
			//meshRenderer->SetMaterial(material->Clone());
		}
		obj->AddComponent(meshRenderer);
		scene->AddGameObject(obj);
	}
#pragma endregion

- 이렇게 해서 물체를 그리면,

- 인스턴싱하기 전(270fps) 인스턴싱한 후(410fps)와 같이 유의미한 차이를 보여준다.