Environment:
DirectX 12
Windows 10
Visual Studio 2019
前言
在「INTRODUCTION TO 3D GAME PROGRAMMING WITH DIRECTX 12」一書中,示例代碼使用MSAA
的方式已經過時,所以導致出現錯誤。這是因為在新版DirectX 12中,不能在運行時修改交換鏈(SwapChain)。為了使用MSAA
,需要作出修改,本文使用的示例是Chapter 6中的Box。
INTRODUCTION TO 3D GAME PROGRAMMING WITH DIRECTX 12
修改
主要步驟:
創建MSAA用的RenderTarget
創建MSAA用的DepthStencil
創建MSAA用的Pipeline
Notice: SwapChain不需要修改
創建RTV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ID3D12Resource* msaaRenderTarget; D3D12_DESCRIPTOR_HEAP_DESC msaaRTVHeapDesc; msaaRTVHeapDesc.NumDescriptors = 1 ; msaaRTVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; msaaRTVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; msaaRTVHeapDesc.NodeMask = 0 ; device->CreateDescriptorHeap (&msaaRTVHeapDesc, IID_PPV_ARGS (&msaaRTVHeap)); D3D12_RESOURCE_DESC msaaResourceDesc = CD3DX12_RESOURCE_DESC::Tex2D ( DXGI_FORMAT_R8G8B8A8_UNORM, window_width, window_height, 1 , 1 , 4 ); msaaResourceDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; device->CreateCommittedResource ( &CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &msaaResourceDesc, D3D12_RESOURCE_STATE_RESOLVE_SOURCE, &msaaOptimizedClearValue, IID_PPV_ARGS (&msaaRenderTarget) ); D3D12_RENDER_TARGET_VIEW_DESC msaaRTVDesc = {}; msaaRTVDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; msaaRTVDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS; device->CreateRenderTargetView (msaaRenderTarget, &msaaRTVDesc, msaaRTVHeap->GetCPUDescriptorHandleForHeapStart ());
注意在CreateCommittedResource ()
中的State是D3D12_RESOURCE_STATE_RESOLVE_SOURCE
,而不是D3D12_RESOURCE_STATE_RENDER_TARGET
。
創建DSV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ID3D12Resource* msaaDepthStencil; D3D12_DESCRIPTOR_HEAP_DESC msaaDSVHeapDesc; msaaDSVHeapDesc.NumDescriptors = 1 ; msaaDSVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; msaaDSVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; msaaDSVHeapDesc.NodeMask = 0 ; device->CreateDescriptorHeap (&msaaDSVHeapDesc, IID_PPV_ARGS (&msaaDSVHeap)); D3D12_RESOURCE_DESC msaaDepthStencilDesc = CD3DX12_RESOURCE_DESC::Tex2D ( DXGI_FORMAT_D24_UNORM_S8_UINT, window_width, window_height, 1 , 1 , 4 ); msaaDepthStencilDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; D3D12_CLEAR_VALUE depthOptimizedClearValue = {}; depthOptimizedClearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthOptimizedClearValue.DepthStencil.Depth = 1.0f ; depthOptimizedClearValue.DepthStencil.Stencil = 0.0f ; device->CreateCommittedResource ( &CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &msaaDepthStencilDesc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &depthOptimizedClearValue, IID_PPV_ARGS (&msaaDepthStencil) ); D3D12_DEPTH_STENCIL_VIEW_DESC msaaDSVDesc = {}; msaaDSVDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; msaaDSVDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS; device->CreateDepthStencilView (msaaDepthStencil, &msaaDSVDesc, msaaDSVHeap->GetCPUDescriptorHandleForHeapStart ());
基本與RTV相同。
創建Pipeline
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 D3D12_GRAPHICS_PIPELINE_STATE_DESC msaaPSODesc; ZeroMemory (&msaaPSODesc, sizeof (D3D12_GRAPHICS_PIPELINE_STATE_DESC));msaaPSODesc.InputLayout = {inputLayout.data (), (UINT)inputLayout.size ()}; msaaPSODesc.pRootSignature = rootSignature; msaaPSODesc.VS = { vsByteCode->GetBufferPointer (), vsByteCode->GetBufferSize () }; msaaPSODesc.PS = { psByteCode->GetBufferPointer (), psByteCode->GetBufferSize () }; msaaPSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC (D3D12_DEFAULT); msaaPSODesc.BlendState = CD3DX12_BLEND_DESC (D3D12_DEFAULT); msaaPSODesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC (D3D12_DEFAULT); msaaPSODesc.SampleMask = D3D12_DEFAULT_SAMPLE_MASK; msaaPSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; msaaPSODesc.NumRenderTargets = 1 ; msaaPSODesc.RTVFormats[0 ] = DXGI_FORMAT_R8G8B8A8_UNORM; msaaPSODesc.SampleDesc.Count = 4 ; msaaPSODesc.SampleDesc.Quality = 0 ; msaaPSODesc.DSVFormat = DXGI_FORMAT_D24_UNORM_S8_UINT; ID3D12PipelineState* msaaPipelineState; device->CreateGraphicsPipelineState (&msaaPSODesc, IID_PPV_ARGS (&msaaPipelineState));
Pipiline中的SampleDesc的設置需要與Buffer一致。所以Count = 4
以及Quality = 0
。
Draw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 D3D12_RESOURCE_BARRIER barrierDesc = {}; barrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrierDesc.Transition.pResource = msaaRenderTarget; barrierDesc.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_SOURCE; barrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; cmdList->ResourceBarrier (1 , &barrierDesc); auto rtvH = msaaRTVHeap->GetCPUDescriptorHandleForHeapStart ();auto dsvH = msaaDSVHeap->GetCPUDescriptorHandleForHeapStart ();cmdList->ClearRenderTargetView (rtvH, Colors::LightSteelBlue, 0 , nullptr ); cmdList->ClearDepthStencilView (dsvH, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f , 0 , 0 , nullptr ); cmdList->OMSetRenderTargets (1 , &rtvH, true , &dsvH); ID3D12DescriptorHeap* descriptorHeaps[] = {cbvHeap}; cmdList->SetGraphicsRootSignature (rootSignature); cmdList->SetDescriptorHeaps (_countof (descriptorHeaps), descriptorHeaps); cmdList->SetGraphicsRootDescriptorTable (0 , cbvHeap->GetGPUDescriptorHandleForHeapStart ()); cmdList->IASetVertexBuffers (0 , 1 , &vbView); cmdList->IASetIndexBuffer (&ibView); cmdList->IASetPrimitiveTopology (D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); cmdList->DrawIndexedInstanced (indices.size (), 1 , 0 , 0 , 0 ); barrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_SOURCE; cmdList->ResourceBarrier (1 , &barrierDesc); barrierDesc = CD3DX12_RESOURCE_BARRIER::Transition (backBuffers[currentBackBuffer], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RESOLVE_DEST); cmdList->ResourceBarrier (1 , &barrierDesc); cmdList->ResolveSubresource (backBuffers[currentBackBuffer], 0 , msaaRenderTarget, 0 , DXGI_FORMAT_R8G8B8A8_UNORM); barrierDesc = CD3DX12_RESOURCE_BARRIER::Transition ( backBuffers[currentBackBuffer], D3D12_RESOURCE_STATE_RESOLVE_DEST, D3D12_RESOURCE_STATE_PRESENT ); cmdList->ResourceBarrier (1 , &barrierDesc); cmdList->Close (); ID3D12CommandList* cmdLists[] = {cmdList}; cmdQueue->ExecuteCommandLists (_countof (cmdLists), cmdLists); swapChain->Present (0 , 0 ); currentBackBuffer = (currentBackBuffer + 1 ) % 2 ; cmdQueue->Signal (fence, ++fenceValue); if (fence->GetCompletedValue () != fenceValue) { auto event = CreateEvent (nullptr , false , false , nullptr ); fence->SetEventOnCompletion (fenceValue, event); WaitForSingleObject (event, INFINITE); CloseHandle (event); }
需要先渲染到MSAA的Buffer中,並解析到BackBuffer。ResolveSubresource()
將MSAA資源複制到非MSAA資源中。
結果對比
點擊圖片放大查看
Origin
MSAA
總結
因為MSAA算法已經包含在GPU中,所以只需要直接使用便可(實現原理也不難,可以自行上網搜索)。渲染的方式與關閉MSAA時基本一致。但是需要一個MSAA用的RTV、DSV以及Pipeline。
在渲染時先渲染到MSAA的RenderTarget上,然後通過ResolveSubresource ()
將其複制到後台緩衝中。
that’s all!
Reference
DirectX 12小技巧-启用MSAA
Introduction of Multisampling