-
Notifications
You must be signed in to change notification settings - Fork 411
Using HDR rendering
This lesson covers the basics of HDR rendering, tone-mapping, and adding HDR10 wide color gamut rendering with DirectX Tool Kit.
First create a new project. For this lesson, use the DeviceResources variant of the The basic game loop, then use the instructions in Adding the DirectX Tool Kit.
Save the files RenderTexture.h and RenderTexture.cpp to your new project's folder. Using to the top menu and select Project / Add Existing Item.... Select "RenderTexture.h" and hit "OK". Repeat for "RenderTexture.cpp".
Add to the Game.h file to the #include
section:
#include "RenderTexture.h"
In the Game.h file, add the following variable to the bottom of the Game class's private declarations:
std::unique_ptr<DX::RenderTexture> m_hdrScene;
std::unique_ptr<DirectX::ToneMapPostProcess> m_toneMap;
std::unique_ptr<DirectX::DescriptorHeap> m_resourceDescriptors;
std::unique_ptr<DirectX::DescriptorHeap> m_renderDescriptors;
enum Descriptors
{
SceneTex,
Count
};
enum RTDescriptors
{
HDRScene,
RTCount
};
In the Game.cpp file, modify the Game class constructor:
m_deviceResources = std::make_unique<DX::DeviceResources>(DXGI_FORMAT_R10G10B10A2_UNORM);
m_deviceResources->RegisterDeviceNotify(this);
m_hdrScene = std::make_unique<DX::RenderTexture>(DXGI_FORMAT_R16G16B16A16_FLOAT);
XMVECTORF32 color;
color.v = XMColorSRGBToRGB(Colors::CornflowerBlue);
m_hdrScene->SetClearColor(color);
We are using
DXGI_FORMAT_R16G16B16A16_FLOAT
for the HDR render target. Other reasonable options (depending on your Direct3D hardware feature level) includingDXGI_FORMAT_R32G32B32A32_FLOAT
, orDXGI_FORMAT_R11G1B10_FLOAT
.DXGI_FORMAT_R32G32B32_FLOAT
(a 96 bits-per-pixel format) support is often optional for various rendering operations. TheDXGI_FORMAT_R9G9B9E5_SHAREDEXP
is not typically supported for render targets.
In Game.cpp, add to the TODO of CreateDeviceDependentResources:
m_resourceDescriptors = std::make_unique<DescriptorHeap>(device,
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
Descriptors::Count);
m_renderDescriptors = std::make_unique<DescriptorHeap>(device,
D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
RTDescriptors::RTCount);
m_hdrScene->SetDevice(device,
m_resourceDescriptors->GetCpuHandle(Descriptors::SceneTex),
m_renderDescriptors->GetCpuHandle(RTDescriptors::HDRScene));
RenderTargetState rtState(m_deviceResources->GetBackBufferFormat(),
DXGI_FORMAT_UNKNOWN);
// Set tone-mapper as 'pass-through' for now...
m_toneMap = std::make_unique<ToneMapPostProcess>(device,
rtState,
ToneMapPostProcess::None, ToneMapPostProcess::SRGB);
In Game.cpp, add to the TODO of CreateWindowSizeDependentResources:
auto size = m_deviceResources->GetOutputSize();
m_hdrScene->SetWindow(size);
m_toneMap->SetHDRSourceTexture(m_resourceDescriptors->GetGpuHandle(Descriptors::SceneTex));
In Game.cpp, add to the TODO of OnDeviceLost:
m_hdrScene->ReleaseDevice();
m_toneMap.reset();
m_resourceDescriptors.reset();
m_renderDescriptors.reset();
In Game.cpp, modify Clear as follows:
// Clear the views.
auto rtvDescriptor = m_renderDescriptors->GetCpuHandle(RTDescriptors::HDRScene);
auto dsvDescriptor = m_deviceResources->GetDepthStencilView();
commandList->OMSetRenderTargets(1, &rtvDescriptor, FALSE, &dsvDescriptor);
XMVECTORF32 color;
color.v = XMColorSRGBToRGB(Colors::CornflowerBlue);
commandList->ClearRenderTargetView(rtvDescriptor, color, 0, nullptr);
...
In Game.cpp, modify Render as follows:
// Don't try to render anything before the first Update.
if (m_timer.GetFrameCount() == 0)
{
return;
}
// Prepare the command list to render a new frame.
m_deviceResources->Prepare();
auto commandList = m_deviceResources->GetCommandList();
m_hdrScene->BeginScene(commandList);
Clear();
// TODO: Add your rendering code here.
m_hdrScene->EndScene(commandList);
auto rtvDescriptor = m_deviceResources->GetRenderTargetView();
commandList->OMSetRenderTargets(1, &rtvDescriptor, FALSE, nullptr);
ID3D12DescriptorHeap* heaps[] = { m_resourceDescriptors->Heap() };
commandList->SetDescriptorHeaps(_countof(heaps), heaps);
m_toneMap->Process(commandList);
// Show the new frame.
m_deviceResources->Present();
m_graphicsMemory->Commit(m_deviceResources->GetCommandQueue());
Build and run, and the result will be the original 'cornflower blue' screen.
In the Game.h file, add the following variables to the bottom of the Game class's private declarations:
DirectX::SimpleMath::Matrix m_world;
DirectX::SimpleMath::Matrix m_view;
DirectX::SimpleMath::Matrix m_proj;
std::unique_ptr<DirectX::GeometricPrimitive> m_shape;
std::unique_ptr<DirectX::BasicEffect> m_effect;
In Game.cpp, add to the TODO of CreateDeviceDependentResources after where you have created m_graphicsMemory
:
RenderTargetState hdrState(m_hdrScene->GetFormat(),
m_deviceResources->GetDepthBufferFormat());
EffectPipelineStateDescription pd(
&GeometricPrimitive::VertexType::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
hdrState);
m_effect = std::make_unique<BasicEffect>(device, EffectFlags::Lighting, pd);
m_effect->EnableDefaultLighting();
m_shape = GeometricPrimitive::CreateTeapot();
m_world = Matrix::Identity;
In Game.cpp, add to the TODO of CreateWindowSizeDependentResources:
m_view = Matrix::CreateLookAt(Vector3(2.f, 2.f, 2.f),
Vector3::Zero, Vector3::UnitY);
m_proj = Matrix::CreatePerspectiveFieldOfView(XM_PI / 4.f,
float(size.right) / float(size.bottom), 0.1f, 10.f);
In Game.cpp, add to the TODO of OnDeviceLost (before the reset of m_graphicsMemory
):
m_shape.reset();
m_effect.reset();
In Game.cpp, add to the TODO of Render (before calling m_hdrScene->EndScene(commandList)
):
m_effect->SetMatrices(m_world, m_view, m_proj);
m_effect->Apply(commandList);
m_shape->Draw(commandList);
In Game.cpp, add to the TODO of Update:
float time = float(timer.GetTotalSeconds());
m_world = Matrix::CreateRotationZ(cosf(time) * 2.f);
float colorScale = 1.f + cosf(time);
m_effect->SetDiffuseColor(XMVectorSetW(Colors::White * colorScale, 1.f));
Build and run to see the scene with a teapot. The color scales between black and white which for now is full saturated.
In the previous render, the color values range from 0 to 2, so the teapot over saturates for half the time. To resolve this, we use tone-mapping. In the first case, this uses a Reinhard local operator as follows. In Game.cpp, modify the CreateDeviceDependentResources:
// Set the tone-mapper to use Reinhard
m_toneMap = std::make_unique<ToneMapPostProcess>(device,
rtState,
ToneMapPostProcess::Reinhard, ToneMapPostProcess::SRGB);
Build and run to see the colors less intense white.
For a better overall color treatment, you may want to use an ACES Filmic operator instead. In Game.cpp, modify the CreateDeviceDependentResources:
// Set the tone-mapper to ACES Filmic
m_toneMap = std::make_unique<ToneMapPostProcess>(device,
rtState,
ToneMapPostProcess::ACESFilmic, ToneMapPostProcess::SRGB);
Build and run to see a slightly different handling.
UNDER DEVELOPMENT
Next lessons: Game controller input, Using the SimpleMath library, Adding the DirectX Tool Kit for Audio
DirectX Tool Kit docs PostProcess and ToneMapPostProcess
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