[DirectX 11] Hierarchical Z-Buffer Occlusion Culling

2022. 4. 4. 16:42·Graphics

Hardware Occlusion Culling 

1) 구현 원리

 - Occluder가 Rendering 된 상태에서 (Z-Buffer 유지 상태) Occludee를 Rendering 하여Pixel에 그려지는지 테스트 하는 방식.

 - Low-Poly Data나 Bounding Mesh를 그려서 Z-Buffer를 구성한다.

 - Occludee를Pixel Write를 하지 않고 Z-Test만 한다.

 - 그려지는 Pixel 수를 GPU측 메모리에 기록해둔다.

 - 해당 Occludee의Pixel 수를 GPU로부터 얻어온다.

2) 단점

 - DrawCall 회수를 줄이지 못한다.

 - 두번 그려야 하므로 왠만해선 빨라지기 힘들다.

- Occludee의크기가 커질수록 Z-Test 회수도 많아진다.

 - Pixel 수를 받아올때PCI버스를 타고GPU Memory -> System Memory로 가져오는 과정에서 비용이 많이든다.

 

Hierarchical Z-Buffer Occlusion Culling

1) 구현 원리

 - Occluder와 Occludee가 꼭 정밀할 필요는 없기에, 간단한 Bounding Sphere로 대체하여 비교한다.

 - Z-Buffer의 해상도 또한 정밀할 필요가 없다.

 - Bounding Sphere를 통해 Z-Test를 하므로 그리지 않고 Sphere의 한 점만 Projection하여 Depth 값을 비교한다.

 - 직접 버퍼에 그릴 필요가 없으므로 Pixel Shader가 아닌 Compute Shader를 활용하여 Depth 테스트를 한다.

 - Screen Space에서 Occludee를 중심점과 Occludee가 그려질 2x2 Texel의 Depth값을 비교한다.

 - Occludee의 크기가 크면 Z-Buffer의 해상도는 낮아도 되며, Occludee의 크기가 작으면 Z-Buffer의 해상도가 높아야한다.

 - Occludee가 Screen Space에서 2x2 Texel을 보장할 수 있다면 해당 해상도의 Z-Buffer 기준으로 비교한다.

 - 2x2 Texel을 기준으로 잡는 이유는 Screen Space 상에서 항상 Occludee가 그려지는 영역 전체를 커버할수 있기 때문이다.

 - 1x1 Texel을 기준으로 잡을 경우 Texel의 경계에 걸치게되면 잘못된 Depth를 비교하기 때문에 2x2 Texel을 기준으로 잡는다.

 - Occludee의 경계구가 Screen Space 상에서 반드시 2x2 Texel이 되도록 ceil(log2(texelSize)) 공식을 활용하여 MipMap을 선택하여 비교한다.

 - 적절한 MipMap 설정후 해당 Z-Buffer MipMap의 2x2 Texel을 Sampling하여 가장 큰 Depth 기준으로 비교를하여 Culling을 실행한다.

 

2) 장점

 - 낮은 해상도의 Z-Buffer를 사용하므로 Z-Buffer의 Read/Write 시간을 줄일 수 있다.

 - Compute Shader를 활용하여 Bounding Sphere당 스레드를 할당하여 Depth 테스트 병렬화를 할 수 있다.

 - 실질적으로 해당 Occludee를 비교하기 위해서 float4(CenterPosition, Radius)만 필요로 하므로 GPU 메모리를 크게 필요로 하지 않는다.

 

Deferred Context & CommandList

- DirectX 11 에서 지원하는 Deferred Context와 Command List를 활용하면 MipMap Sampling Rendering을 예약해둘 수 있다.

 1) 해당 CommandList를 예약하기 위한 Deferred Context를 생성한다.

 2) 생성한 Deferred Context를 통해 실질적인 MipMap Sampling 과정을 설정한다.

 3) 해당 CommandList를 보관해둔 후 사용한 Deferred Context는 해제해준다.

 4) Occluder Z-Buffer를 그린 후 미리 설정해둔 CommandList를 통해 MipMap Sampling을 실행한다.

 


ID3D11DeviceContext* context;
g_Device->CreateDeferredContext(0, &context);

context->RSSetViewports(1, m_Hiz_VP);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
context->IASetVertexBuffers(0, 1, m_Screen_DB->VertexBuf->GetAddress(), &m_Screen_DB->Stride, &m_Screen_DB->Offset);
context->IASetIndexBuffer(m_Screen_DB->IndexBuf->Get(), DXGI_FORMAT_R32_UINT, 0);

m_Screen_VS->Update(context);

// Draw Screen MipMap..
// 최대 크기의 Mip Map을 제외한 모든 Mip Map Sampling 실행..
for (UINT miplevel = 1; miplevel < hizDesc->MipLevels; miplevel++)
{
	context->OMSetRenderTargets(1, &m_MipMap_RTV[miplevel], nullptr);

	// 한단계 위의 Mip Level을 Resource로 설정..
	m_HizMipMap_PS->SetShaderResourceView<gLastMipMap>(m_MipMap_SRV[miplevel - 1]);

	m_HizMipMap_PS->Update(context);

	context->DrawIndexed(m_Screen_DB->IndexCount, 0, 0);
}

context->FinishCommandList(FALSE, &m_MipMap_CommandList);

// 예약 후 해당 DeviceContext는 해제..
context->Release();

- Deferred Context를 활용한 CommandList 설정 방식이다.

 

// Occluder Depth 그리기..
g_Context->RSSetViewports(1, m_Hiz_VP);
g_Context->RSSetState(m_NoCull_RS);
g_Context->OMSetRenderTargets(1, &m_MipMap_RTV[0], m_HizDepth_DSV);
g_Context->ClearRenderTargetView(m_MipMap_RTV[0], reinterpret_cast<const float*>(&DXColors::White));
g_Context->ClearDepthStencilView(m_HizDepth_DSV, D3D11_CLEAR_DEPTH, 1.0, 0);

g_Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

VertexBuffer* originVertex = nullptr;
IndexBuffer* originIndex = nullptr;

ID3D11Buffer* convertVertex = nullptr;
ID3D11Buffer* convertIndex = nullptr;

// Occluder List Z-Buffer 설정..
for (MeshBuffer* occluder : g_GlobalData->OccluderList)
{
	// 해당 Occluder Vertex & Index Buffer..
	originVertex = occluder->VertexBuf;
	originIndex = occluder->IndexBuf;

	convertVertex = (ID3D11Buffer*)originVertex->pVertexBuf;
	convertIndex = (ID3D11Buffer*)originIndex->pIndexBuf;

	CB_DepthStaticMesh objectBuf;
	objectBuf.gWorldViewProj = viewproj;

	m_Depth_VS->ConstantBufferUpdate(&objectBuf);

	m_Depth_VS->Update();
	m_Depth_PS->Update();

	g_Context->IASetVertexBuffers(0, 1, &convertVertex, &originVertex->Stride, &originVertex->Offset);
	g_Context->IASetIndexBuffer(convertIndex, DXGI_FORMAT_R32_UINT, 0);

	g_Context->DrawIndexed(originIndex->Count, 0, 0);
}

// 예약해둔 Z MipMap Sampling 실행..
g_Context->ExecuteCommandList(m_MipMap_CommandList, FALSE);

- 설정해둔 CommandList를 통해 MipMap Sampling을 하는 과정이다.

 

 


Z-Buffer MipMap을 통한 Occlusion Culling 과정

Occludee 크기에 맞는 2x2 Texel을 보장하는 Z-Buffer MipMap 설정

 

 

Occludee Bounding Sphere NDC 공간으로 투영한 후 해당 2x2 Texel Sampling

 

// Occluder Z-Buffer를 기준으로 Occludee Occlusion Culling..
void CullingPass::OcclusionCullingQuery()
{
	if (CullingRenderMeshList.empty() || g_GlobalData->OccluderList.empty()) return;

	CameraData* cam = g_GlobalData->MainCamera_Data;

	Matrix& view = cam->CamView;
	Matrix& proj = cam->CamProj;
	Matrix& viewproj = cam->CamViewProj;

	// View Frustum 설정..
	m_Frustum.FrustumTransform(viewproj);

	// 실질적인 Render Count 초기화..
	m_RenderCount = 0;

	for (int i = 0; i < CullingRenderMeshList.size(); i++)
	{
		m_RenderData = CullingRenderMeshList[i];

		// 모든 오브젝트 Draw 초기화..
		m_RenderData->m_Draw = false;

		// 활성화 되있는 Object만 Culling..
		if (m_RenderData->m_ObjectData->IsActive == false) continue;

		// 해당 Collider Transform Update..
		m_Sphere = m_RenderData->m_Mesh->m_MeshProperty->BoundSphere;
		m_Sphere.Transform(m_Sphere, m_RenderData->m_ObjectData->World);

		// Collider XYZ (Center) W (Radius) 형태로 Buffer 삽입..
		m_ColliderData.x = m_Sphere.Center.x;
		m_ColliderData.y = m_Sphere.Center.y;
		m_ColliderData.z = m_Sphere.Center.z;
		m_ColliderData.w = m_Sphere.Radius;

		m_ColliderList[m_RenderCount++] = m_ColliderData;
	}

	// Mapping SubResource Data..
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	ZeroMemory(&mappedResource, sizeof(D3D11_MAPPED_SUBRESOURCE));

	// GPU Access Lock Buffer Data..
	g_Context->Map(m_Collider_Buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	
	// Copy Resource Data..
	memcpy(mappedResource.pData, &m_ColliderList[0], sizeof(Vector4) * m_RenderCount);
	
	// GPU Access UnLock Buffer Data..
	g_Context->Unmap(m_Collider_Buffer, 0);

	// Culling Constant Buffer Update..
	CB_HizCull cullBuf;
	cullBuf.gView = view;
	cullBuf.gProj = proj;
	cullBuf.gViewProj = viewproj;
	cullBuf.gFrustumPlanes[0] = m_Frustum.Planes[0].point;
	cullBuf.gFrustumPlanes[1] = m_Frustum.Planes[1].point;
	cullBuf.gFrustumPlanes[2] = m_Frustum.Planes[2].point;
	cullBuf.gFrustumPlanes[3] = m_Frustum.Planes[3].point;
	cullBuf.gFrustumPlanes[4] = m_Frustum.Planes[4].point;
	cullBuf.gFrustumPlanes[5] = m_Frustum.Planes[5].point;
	cullBuf.gEyePos.x = cam->CamPos.x;
	cullBuf.gEyePos.y = cam->CamPos.y;
	cullBuf.gEyePos.z = cam->CamPos.z;
	cullBuf.gViewportSize = Vector2(m_Width, m_Height);

	m_HizCull_CS->ConstantBufferUpdate(&cullBuf);

	m_HizCull_CS->SetShaderResourceView<gHizMap>(m_Hiz_SRV);
	m_HizCull_CS->SetShaderResourceView<gColliderBuffer>(m_Collider_SRV);
	m_HizCull_CS->SetUnorderedAccessView<gCullingBuffer>(m_Culling_UAV);

	m_HizCull_CS->Update();

	g_Context->Dispatch(m_RenderCount, 1, 1);
}

// GPGPU을 통해 나온 결과값을 기반으로 Draw 상태 설정..
void CullingPass::DrawStateUpdate()
{
	if (CullingRenderMeshList.empty() || g_GlobalData->OccluderList.empty()) return;

	// GPU -> CPU Culling Result Data Copy
	g_Context->CopyResource(m_ResultCopy_Buffer, m_Culling_Buffer);

	// Mapping SubResource Data..
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	ZeroMemory(&mappedResource, sizeof(D3D11_MAPPED_SUBRESOURCE));
	
	// GPU Access Lock Buffer Data..
	g_Context->Map(m_ResultCopy_Buffer, 0, D3D11_MAP_READ, 0, &mappedResource);

	// Copy Resource Data..
	memcpy(&m_ResultList[0], (float*)mappedResource.pData, sizeof(float) * m_RenderCount);

	// GPU Access UnLock Buffer Data..
	g_Context->Unmap(m_ResultCopy_Buffer, 0);

	// 실질적인 Render Count 초기화..
	m_RenderCount = 0;

	for (int i = 0; i < CullingRenderMeshList.size(); i++)
	{
		m_RenderData = CullingRenderMeshList[i];

		// 활성화 되있는 Object만 Culling..
		if (m_RenderData->m_ObjectData->IsActive == false) continue;

		// Culling 결과에 대한 Draw 상태 업데이트..
		m_RenderData->m_Draw = (bool)m_ResultList[m_RenderCount++];
	}
}

- Occluder Z-Buffer와 Occludee Bounding Sphere의 비교를 통해 GPGPU(Compute Shader) 측에서 결과 값을 내보낸 후 해당 결과값을 기반으로 Occludee들의 Draw 여부를 설정하여 Rendering을 실행한다.

 

적용 영상

Hierarchical Z-Buffer Occlusion Culling 적용 영상

 

저작자표시 (새창열림)

'Graphics' 카테고리의 다른 글

[DirectX 11] Rim Lighting  (0) 2022.04.25
[DirectX 11] Vertex Texture Fetch Skinning Animation Instancing  (0) 2022.04.21
[DirectX 11] Fast Picking (Object ID Picking)  (6) 2022.03.29
[DirectX 11] Game Engine Render Resource 동기화 방식 및 Resource 관리 방식  (0) 2022.03.16
[DirectX 11] Game Engine & Graphic Engine 분리 및 Render Data 전달 방식  (0) 2022.03.07
'Graphics' 카테고리의 다른 글
  • [DirectX 11] Rim Lighting
  • [DirectX 11] Vertex Texture Fetch Skinning Animation Instancing
  • [DirectX 11] Fast Picking (Object ID Picking)
  • [DirectX 11] Game Engine Render Resource 동기화 방식 및 Resource 관리 방식
KyuHwang
KyuHwang
  • KyuHwang
    DirectX Engine
    KyuHwang
  • 전체
    오늘
    어제
    • 분류 전체보기 (50)
      • C++ (4)
      • CS (0)
      • Graphics (32)
      • DLL (2)
      • Shader (7)
      • Project (4)
      • ETC (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • YouTube
  • 공지사항

  • 인기 글

  • 태그

    mobile format
    Shader Macro
    nvtt
    C ++
    Alpha Blending
    shader
    Hash Code
    std::enable_if
    Explicit Linking
    std::is_base_of
    Shader Resource 관리
    DLL Interface
    Shader Reflection
    dll
    Implicit Linking
    rigging chain
    hlsl
    Define Function
    Order Independent Transparency
    Directx11
    texture block compression
    Define Macro
    RunTime Compile Shader
    bc format
    Project
    Return Type Operator
    Win API
    animation constraint
    DirectX 2D
    animation retargeting
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
KyuHwang
[DirectX 11] Hierarchical Z-Buffer Occlusion Culling
상단으로

티스토리툴바