D3D11버전에서 구현했던 Compute Shader를 이용한 Hi-Z Occlusion Culling을 D3D12엔진에서 구현했다.
기본 개념과 구현에 대해서는 자세히 적지 않겠다.
예전에 KASA에서 밝표했던 내용이 있으므로 발표자료를 첨부한다.
D3D12에서 Compute Shader를 사용하려면 어떤 API들을 사용해야하는지 찾느라 좀 애먹었다.
Compute Shader를 위한 ID3DPipelineState를 만들려면D3D12_GRAPHICS_PIPELINE_STATE_DESC구조체 대신 D3D12_COMPUTE_PIPELINE_STATE_DESC를 사용해야한다. 또한 CreateGraphicsPipelineState()대신 ID3D12Device::CreateComputePipelineState()를 사용한다. 이거 몰라서 한참 헤맸다.
다음과 같이 Compute Shader에서 사용할 ID3DPipelineState를 만든다.
D3D12_COMPUTE_PIPELINE_STATE_DESC computePsoDesc = {};
computePsoDesc.pRootSignature = m_pRootSignature_Cull;
computePsoDesc.CS = CD3DX12_SHADER_BYTECODE(m_pCS_Cull->pCodeBuffer,m_pCS_Cull->dwCodeSize);
if (FAILED(pResourceManager->CreateComputePipelineState(&computePsoDesc,&m_pPipelineState_Cull)))
__debugbreak();
그리고 Compute Shader를 호출하는 코드는 다음과 같다.
대충 이런 스타일이다. 참고삼아 함수 하나를 다 적었다.
void CHIZQueryManager::Cull(ID3D12GraphicsCommandList* pCommandList,PLANE* pPlaneList,DWORD dwPlaneNum,HIZ_OCC_TEST* pOccludeeList,UINT uiOccludeeNum)
{
ID3D12Device* pDevice = m_pRenderer->INL_GetD3DDevice();
CAMERA_DESC_COMMON* pCamDesc = m_pRenderer->INL_GetCameraDesc();
#ifdef _DEBUG
if (dwPlaneNum != 6)
__debugbreak();
#endif
for (DWORD i=0; i<dwPlaneNum; i++) { *(PLANE*)&m_pConstBufferZCull->FrustumPlanes[i].x = *(PLANE*)(pPlaneList+i);
}
MATRIX4* pMatView = m_pRenderer->INL_GetViewMatrix();
MATRIX4* pMatProj = m_pRenderer->INL_GetProjMatrix();
MATRIX4 matViewProj;
MatrixMultiply2(&matViewProj,pMatView,pMatProj);
*(VECTOR3*)&m_pConstBufferZCull->EyePos = pCamDesc->v3From;
*(VECTOR3*)&m_pConstBufferZCull->Up = pCamDesc->v3Up;
m_pConstBufferZCull->EyePos.w = 1.0F;
m_pConstBufferZCull->Up.w = 1.0f;
m_pConstBufferZCull->matViewProj = matViewProj;
m_pConstBufferZCull->ViewportSize.x = (float)m_Width;
m_pConstBufferZCull->ViewportSize.y = (float)m_Height;
float Far = m_pRenderer->INL_GetFar();
m_pConstBufferZCull->FarRcp = 1.0f / Far;
m_pConstBufferZCull->OccludeeNum = uiOccludeeNum;
TransposeMatrix(&m_pConstBufferZCull->matViewProj);
if (uiOccludeeNum > m_uiMaxOccludeeNum)
{
CleanupBuffersForCull();
CreateBuffersForCull(uiOccludeeNum);
}
VECTOR4* pDest = (VECTOR4*)m_pBoundsInputBuffer;
for (DWORD i=0; i<uiOccludeeNum; i++) { *(VECTOR3*)&pDest->x = pOccludeeList[i].bs.v3Point;
pDest->w = pOccludeeList[i].bs.fRs;
pDest++;
}
pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_pBoundsBuffer, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COPY_DEST));
pCommandList->CopyResource(m_pBoundsBuffer,m_pBoundsBufferWrite);
pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_pBoundsBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE));
(m_pCullResultList+i,&Bounds,pSysBufferZCull->EyePos,pSysBufferZCull->Up,matViewProj,pSysBufferZCull->FrustumPlanes,pSysBufferZCull->ViewportSize,pSysBufferZCull->FarRcp);
//}
//m_uiCullResultNum = uiOccludeeNum;
pCommandList->SetPipelineState(m_pPipelineState_Cull);
pCommandList->SetComputeRootSignature(m_pRootSignature_Cull);
ID3D12DescriptorHeap* ppHeaps[] = { m_pCommonHeap_Cull,m_pSamplerHeap_Cull };
pCommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
pCommandList->SetComputeRootDescriptorTable(0,m_pCommonHeap_Cull->GetGPUDescriptorHandleForHeapStart());
pCommandList->SetComputeRootDescriptorTable(1,m_pSamplerHeap_Cull->GetGPUDescriptorHandleForHeapStart());
UINT GroupNum = (uiOccludeeNum / MAX_CULL_THREAD_NUM) + ((uiOccludeeNum % MAX_CULL_THREAD_NUM) != 0);
pCommandList->Dispatch(GroupNum,1,1);
pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_pCulledResultBuffer, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE));
pCommandList->CopyResource(m_pCulledResultBufferSystem,m_pCulledResultBuffer);
pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_pCulledResultBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE,D3D12_RESOURCE_STATE_UNORDERED_ACCESS));
pCommandList->Close();
}
일단 잘 작동하는데 최적화가 필요하다.
CommandList를 미리 만들어두고 bundle로 사용할 수 있으므로 그렇게 하도록 수정해야한다. 일단 불필요하게 wait하는 부분도 많다.
그리고 예전에 무시했던 D3DQuery를 이용한 Occlusion Culling이 제법 효과가 있었다는걸 깨달았다. D3D12버전에선 D3DQuery를 뺄 생각이었는데 넣어야겠다.
스샷을 보면 윗쪽에 Down-sampleing된 depth맵들이 보인다.
오브젝트들 위로 표시된 숫자들은 Compute Shader에서 어떤 mipmpa을 선택할지를 보여준다. 오브젝트가 크면 클수록(화면에서 차지하는 면적이 클 수록) 해상도가 낮은 mipmap을 선택하게 되고 그만큼더 빠르게 처리된다.

프디제작자님!
좋아요좋아요
네…
좋아요좋아요