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을 선택하게 되고 그만큼더 빠르게 처리된다.
프디제작자님!
좋아요좋아요
네…
좋아요좋아요