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 과정
// 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을 실행한다.
적용 영상
'DirectX 11' 카테고리의 다른 글
[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 |