-
Notifications
You must be signed in to change notification settings - Fork 411
Line drawing and anti aliasing
Here we learn how to render a grid in 3D, and enable multi-sample anti-aliasing (MSAA).
First create a new project using the instructions from the first two lessons: The basic game loop and Adding the DirectX Tool Kit which we will use for this lesson.
In the Game.h file, add the following variables to the bottom of the Game class's private declarations:
std::unique_ptr<DirectX::BasicEffect> m_effect;
std::unique_ptr<DirectX::PrimitiveBatch<DirectX::VertexPositionColor>> m_batch;
DirectX::SimpleMath::Matrix m_world;
DirectX::SimpleMath::Matrix m_view;
DirectX::SimpleMath::Matrix m_proj;
In Game.cpp, add to the TODO of CreateDevice:
m_batch = std::make_unique<PrimitiveBatch<VertexPositionColor>>(m_d3dDevice.Get());
RenderTargetState rtState(DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_D32_FLOAT);
EffectPipelineStateDescription pd(
&VertexPositionColor::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
rtState,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE);
m_effect = std::make_unique<BasicEffect>(m_d3dDevice.Get(), EffectFlags::VertexColor, pd);
m_world = Matrix::Identity;
Note that with DirectX 12, the topology type is part of the Pipeline State Object (PSO) so you have to draw only lines with this effect instance. If you want to draw triangles and points as well, you need to create distinct effect instances. You can mix lists vs. strips as long as the type (point, line, triangle) is the same as the PSO.
In Game.cpp, add to the TODO of CreateResources:
m_view = Matrix::CreateLookAt(Vector3(2.f, 2.f, 2.f),
Vector3::Zero, Vector3::UnitY);
m_proj = Matrix::CreatePerspectiveFieldOfView(XM_PI / 4.f,
float(backBufferWidth) / float(backBufferHeight), 0.1f, 10.f);
m_effect->SetView(m_view);
m_effect->SetProjection(m_proj);
In Game.cpp, add to the TODO of OnDeviceLost:
m_effect.reset();
m_batch.reset();
In Game.cpp, add to the TODO of Render:
m_effect->SetWorld(m_world);
m_effect->Apply(m_commandList.Get());
m_batch->Begin(m_commandList.Get());
Vector3 xaxis(2.f, 0.f, 0.f);
Vector3 yaxis(0.f, 0.f, 2.f);
Vector3 origin = Vector3::Zero;
size_t divisions = 20;
for( size_t i = 0; i <= divisions; ++i )
{
float fPercent = float(i) / float(divisions);
fPercent = ( fPercent * 2.0f ) - 1.0f;
Vector3 scale = xaxis * fPercent + origin;
VertexPositionColor v1( scale - yaxis, Colors::White );
VertexPositionColor v2( scale + yaxis, Colors::White );
m_batch->DrawLine( v1, v2 );
}
for( size_t i = 0; i <= divisions; i++ )
{
float fPercent = float(i) / float(divisions);
fPercent = ( fPercent * 2.0f ) - 1.0f;
Vector3 scale = yaxis * fPercent + origin;
VertexPositionColor v1( scale - xaxis, Colors::White );
VertexPositionColor v2( scale + xaxis, Colors::White );
m_batch->DrawLine( v1, v2 );
}
m_batch->End();
Build and run to see a 3D grid.
Technical notes: Because of the design of the Direct3D 12 pipeline state object (PSO), you'll note that you can't mix the drawing of points, lines, and triangles/quads with the same effect. Each effect is created for one of those basic primitives, meaning you need more than one pipeline state object to draw them all. Be sure to
End
the batch before applying a new PSO to ensure all pending draws are submitted with the proper active PSO.
Taking a closer look at the grid in the previous screenshot, you can see the lines are a little thin and jagged in places. To make this more visible, in Game.cpp, add to the TODO of Update:
m_world = Matrix::CreateRotationY( cosf( static_cast<float>(timer.GetTotalSeconds())));
Build and run to see the grid spinning, and notice the slight shimmering of the lines--it will be more obvious if you maximize the window size.
There are two approaches to addressing this problem, known as aliasing.
The first is to enable a special anti-aliasing mode specific to line drawing in Direct3D.
In Game.cpp, modify CreateDevice:
CD3DX12_RASTERIZER_DESC rastDesc(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE, FALSE,
D3D12_DEFAULT_DEPTH_BIAS, D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS, TRUE, FALSE, TRUE,
0, D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF);
EffectPipelineStateDescription pd(
&VertexPositionColor::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
rastDesc,
rtState,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE);
This creates a raster state that is the same as our standard
CullNone
but withAntialiasedLineEnable
set to TRUE andMultisampleEnable
set to FALSE. Note also that in Direct3D 12, there's noScissorEnable
setting as there is in Direct3D 11.
Build and run to see the shimmering of the lines lessen, although they will appear to be a bit thicker than a single pixel.
A second more general solution is to use Multisample anti-aliasing (MSAA) which uses more video memory and pixel-fill performance to achieve higher quality rendering results. In this case, we will make use of 4x MSAA where the render target and the depth buffer will be 4 times larger. MSAA can be used with all primitives, not just lines.
In the Game.h file, add the following variables to the bottom of the Game class's private declarations:
Microsoft::WRL::ComPtr<ID3D12Resource> m_offscreenRenderTarget;
In Game.cpp, modify before the TODO section of CreateDevice:
// Create descriptor heaps for render target views and depth stencil views.
D3D12_DESCRIPTOR_HEAP_DESC rtvDescriptorHeapDesc = {};
rtvDescriptorHeapDesc.NumDescriptors = c_swapBufferCount + 1; //<--- Add one here!
rtvDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
In Game.cpp, modify in the TODO section of CreateDevice:
RenderTargetState rtState(DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_D32_FLOAT);
rtState.sampleDesc.Count = 4; // <--- 4x MSAA
CD3DX12_RASTERIZER_DESC rastDesc(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE, FALSE,
D3D12_DEFAULT_DEPTH_BIAS, D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS, TRUE, TRUE, FALSE,
0, D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF);
EffectPipelineStateDescription pd(
&VertexPositionColor::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
rastDesc,
rtState,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE);
This creates a raster state that is the same as our standard
CullNone
which hasMultisampleEnable
set to TRUE.
In Game.cpp, modify before the TODO section of CreateResources:
D3D12_RESOURCE_DESC depthStencilDesc = CD3DX12_RESOURCE_DESC::Tex2D(
depthBufferFormat,
backBufferWidth,
backBufferHeight,
1, // This depth stencil view has only one texture.
1, // Use a single mipmap level.
4 // <--- Use 4x MSAA
);
depthStencilDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
...
D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
dsvDesc.Format = depthBufferFormat;
dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS; // <--- Use MSAA version
In Game.cpp, add to the TODO section of CreateResources:
D3D12_RESOURCE_DESC msaaRTDesc = CD3DX12_RESOURCE_DESC::Tex2D(
backBufferFormat,
backBufferWidth,
backBufferHeight,
1, // This render target view has only one texture.
1, // Use a single mipmap level
4 // <--- Use 4x MSAA
);
msaaRTDesc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
D3D12_CLEAR_VALUE msaaOptimizedClearValue = {};
msaaOptimizedClearValue.Format = backBufferFormat;
memcpy(msaaOptimizedClearValue.Color, Colors::CornflowerBlue, sizeof(float) * 4);
DX::ThrowIfFailed(m_d3dDevice->CreateCommittedResource(
&depthHeapProperties,
D3D12_HEAP_FLAG_NONE,
&msaaRTDesc,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE,
&msaaOptimizedClearValue,
IID_PPV_ARGS(m_offscreenRenderTarget.ReleaseAndGetAddressOf())
));
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor(
m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
c_swapBufferCount, m_rtvDescriptorSize);
m_d3dDevice->CreateRenderTargetView(m_offscreenRenderTarget.Get(), nullptr, rtvDescriptor);
In Game.cpp, add to the TODO of OnDeviceLost:
m_offscreenRenderTarget.Reset();
In Game.cpp, modify Clear:
// Reset command list and allocator.
DX::ThrowIfFailed(m_commandAllocators[m_backBufferIndex]->Reset());
DX::ThrowIfFailed(m_commandList->Reset(
m_commandAllocators[m_backBufferIndex].Get(), nullptr));
// Transition the render target into the correct state to allow for drawing into it.
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
m_offscreenRenderTarget.Get(),
D3D12_RESOURCE_STATE_RESOLVE_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET);
m_commandList->ResourceBarrier(1, &barrier);
// Clear the views.
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor(
m_rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
c_swapBufferCount, m_rtvDescriptorSize);
...
In Game.cpp, modify Present:
D3D12_RESOURCE_BARRIER barriers[2] =
{
CD3DX12_RESOURCE_BARRIER::Transition(m_offscreenRenderTarget.Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_RESOLVE_SOURCE),
CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_backBufferIndex].Get(),
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RESOLVE_DEST)
};
m_commandList->ResourceBarrier(2, barriers);
m_commandList->ResolveSubresource(m_renderTargets[m_backBufferIndex].Get(), 0,
m_offscreenRenderTarget.Get(), 0, DXGI_FORMAT_B8G8R8A8_UNORM);
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
m_renderTargets[m_backBufferIndex].Get(),
D3D12_RESOURCE_STATE_RESOLVE_DEST, D3D12_RESOURCE_STATE_PRESENT);
m_commandList->ResourceBarrier(1, &barrier);
// Send the command list off to the GPU for processing.
DX::ThrowIfFailed(m_commandList->Close());
m_commandQueue->ExecuteCommandLists(1, CommandListCast(m_commandList.GetAddressOf()));
...
The ability to create an MSAA DXGI swap chain is only supported for the older "bit-blt" style presentation modes, specifically DXGI_SWAP_EFFECT_DISCARD
or DXGI_SWAP_EFFECT_SEQUENTIAL
. The newer "flip" style presentation modes DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
or DXGI_SWAP_EFFECT_FLIP_DISCARD
required for Universal Windows Platform (UWP) apps and Direct3D 12 doesn't support creating MSAA swap chains--attempts to create a swap chain with SampleDesc.Count
> 1 will fail. Instead, you create your own MSAA render target and explicitly resolve to the DXGI back-buffer for presentation as shown here. See the SimpleMSAA12 sample.
Next lesson: 3D shapes
DirectX Tool Kit docs CommonStates, Effects, EffectPipelineStateDescription, PrimitiveBatch, RenderTargetState, VertexTypes
FXAA
SMAA
Spatial anti-alising
All content and source code for this package are subject to the terms of the MIT License.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
- Universal Windows Platform apps
- Windows desktop apps
- Windows 11
- Windows 10
- Xbox One
- Xbox Series X|S
- x86
- x64
- ARM64
- Visual Studio 2022
- Visual Studio 2019 (16.11)
- clang/LLVM v12 - v18
- MinGW 12.2, 13.2
- CMake 3.20