DownScale2x2 BasicPostProcess 如何在 DirectX Tool Kit 中工作?

How does the DownScale2x2 BasicPostProcess work in DirectX Tool Kit?

我在 Windows 11 上有一个 DirectX 12 桌面项目,它使用 DXTK post 处理效果的组合实现 post 处理。

post-proc 序列的目的是最终得到在 'big triangle' 像素中采样的单个光晕和模糊纹理(连同在深度通道中渲染的深度纹理)着色器实现最终后台缓冲屏幕图像的景深效果。

DXTK Post进程在全尺寸 (1920x1080) 屏幕纹理上运行。目前这不会影响性能(以 60fps 为基准),但我想当我最终想要在未来支持 4K 分辨率时,这可能是一个问题,全尺寸图像 post 处理可能很昂贵。

由于推荐的最佳做法是对源图像的缩小副本进行操作, 我希望通过使用半尺寸(即四分之一分辨率)工作纹理和 DownScale_2x2 BasicPostProcess[=55= 来实现这一点] 选项。但是在多次尝试这种效果之后,只有原始源图像的左上四分之一被渲染为缩小的纹理……而不是文档中预期的完整图像:

DownScale_2x2: 将每个 2x2 像素块缩小到平均值。这是为了写入一个渲染目标,该目标在每个维度上都是源纹理大小的一半。

其他注意事项:

  • 场景几何首先渲染到 _R16G16B16A16_FLOAT MSAA 渲染目标并解析为单样本 16fp 目标
  • postprocessing 在已解析的单样本 16fp 目标上运行(其中只有中间 'Pass1' & 'Pass2' 工作渲染目标设置为后台缓冲区长度和宽度的一半)
  • 最终处理的图像被色调映射到 _R10G10B10A2_UNORM 交换链后备缓冲区以供呈现。

以下代码片段展示了我如何将 DownScale_2x2 着色器实现到我的 post 进程中。希望这足以解决问题,如有必要,我可以更新更多信息。

CreateDeviceDependentResources()下的资源初始化:

namespace GameConstants {
    constexpr DXGI_FORMAT BACKBUFFERFORMAT(DXGI_FORMAT_R10G10B10A2_UNORM); // back buffer to support hdr rendering
    constexpr DXGI_FORMAT HDRFORMAT(DXGI_FORMAT_R16G16B16A16_FLOAT); // format for hdr render targets
    constexpr DXGI_FORMAT DEPTHFORMAT(DXGI_FORMAT_D32_FLOAT); // format for render target depth buffer
    constexpr UINT MSAACOUNT(4u); // requested multisample count
}

...

    //
    // Render targets
    //

    mMsaaHelper = std::make_unique<MSAAHelper>(GameConstants::HDRFORMAT, GameConstants::DEPTHFORMAT, GameConstants::MSAACOUNT);
    mMsaaHelper->SetClearColor(GameConstants::CLEARCOLOR);
    
    mDistortionRenderTex = std::make_unique<RenderTexture>(GameConstants::BACKBUFFERFORMAT);
    mHdrRenderTex = std::make_unique<RenderTexture>(GameConstants::HDRFORMAT);
    mPass1RenderTex = std::make_unique<RenderTexture>(GameConstants::HDRFORMAT);
    mPass2RenderTex = std::make_unique<RenderTexture>(GameConstants::HDRFORMAT);
    mBloomRenderTex = std::make_unique<RenderTexture>(GameConstants::HDRFORMAT);
    mBlurRenderTex = std::make_unique<RenderTexture>(GameConstants::HDRFORMAT);
    
    mDistortionRenderTex->SetClearColor(GameConstants::CLEARCOLOR);
    mHdrRenderTex->SetClearColor(GameConstants::CLEARCOLOR);
    mPass1RenderTex->SetClearColor(GameConstants::CLEARCOLOR);
    mPass2RenderTex->SetClearColor(GameConstants::CLEARCOLOR);
    mBloomRenderTex->SetClearColor(GameConstants::CLEARCOLOR);
    mBlurRenderTex->SetClearColor(GameConstants::CLEARCOLOR);
    
    mMsaaHelper->SetDevice(device); // Set the MSAA device. Note this updates GetSampleCount.
    
    mDistortionRenderTex->SetDevice(device,
        mPostProcSrvDescHeap->GetCpuHandle(SRV_PostProcDescriptors::DistortionMaskSRV),
        mRtvDescHeap->GetCpuHandle(RTV_Descriptors::DistortionMaskRTV));
    
    mHdrRenderTex->SetDevice(device,
        mPostProcSrvDescHeap->GetCpuHandle(SRV_PostProcDescriptors::HdrSRV),
        mRtvDescHeap->GetCpuHandle(RTV_Descriptors::HdrRTV));
    
    mPass1RenderTex->SetDevice(device,
        mPostProcSrvDescHeap->GetCpuHandle(SRV_PostProcDescriptors::Pass1SRV),
        mRtvDescHeap->GetCpuHandle(RTV_Descriptors::Pass1RTV));
    
    mPass2RenderTex->SetDevice(device,
        mPostProcSrvDescHeap->GetCpuHandle(SRV_PostProcDescriptors::Pass2SRV),
        mRtvDescHeap->GetCpuHandle(RTV_Descriptors::Pass2RTV));
    
    mBloomRenderTex->SetDevice(device,
        mPostProcSrvDescHeap->GetCpuHandle(SRV_PostProcDescriptors::BloomSRV),
        mRtvDescHeap->GetCpuHandle(RTV_Descriptors::BloomRTV));
    
    mBlurRenderTex->SetDevice(device,
        mPostProcSrvDescHeap->GetCpuHandle(SRV_PostProcDescriptors::BlurSRV),
        mRtvDescHeap->GetCpuHandle(RTV_Descriptors::BlurRTV));

...

    RenderTargetState ppState(GameConstants::HDRFORMAT, DXGI_FORMAT_UNKNOWN); // 2d postproc rendering

...

    // Set other postprocessing effects

    mBloomExtract = std::make_unique<BasicPostProcess>(device, ppState, BasicPostProcess::BloomExtract);
    mBloomPass = std::make_unique<BasicPostProcess>(device, ppState, BasicPostProcess::BloomBlur);
    mBloomCombine = std::make_unique<DualPostProcess>(device, ppState, DualPostProcess::BloomCombine);
    mGaussBlurPass = std::make_unique<BasicPostProcess>(device, ppState, BasicPostProcess::GaussianBlur_5x5);
    mDownScalePass = std::make_unique<BasicPostProcess>(device, ppState, BasicPostProcess::DownScale_2x2);

CreateWindowSizeDependentResources() 下调整资源大小:

    // Get current backbuffer dimensions
    CD3DX12_RECT outputRect(mDeviceResources->GetOutputSize());

    // Determine the render target size in pixels
    mBackbufferSize.x = std::max<UINT>(outputRect.right - outputRect.left, 1u);
    mBackbufferSize.y = std::max<UINT>(outputRect.bottom - outputRect.top, 1u);

...

    mMsaaHelper->SetWindow(outputRect);

    XMUINT2 halfSize(mBackbufferSize.x / 2u, mBackbufferSize.y / 2u);

    mBloomRenderTex->SetWindow(outputRect);
    mBlurRenderTex->SetWindow(outputRect);
    mDistortionRenderTex->SetWindow(outputRect);
    mHdrRenderTex->SetWindow(outputRect);
    mPass1RenderTex->SizeResources(halfSize.x, halfSize.y);
    mPass2RenderTex->SizeResources(halfSize.x, halfSize.y);

Post-处理实现:

mMsaaHelper->Prepare(commandList);
Clear(commandList);

// Render 3d scene

mMsaaHelper->Resolve(commandList, mHdrRenderTex->GetResource(),
    D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_RENDER_TARGET);

//
// Postprocessing
//

// Set texture descriptor heap in prep for postprocessing if necessary.
// Unbind dsv for postprocess textures and sprites.

ID3D12DescriptorHeap* postProcHeap[] = { mPostProcSrvDescHeap->Heap() };
commandList->SetDescriptorHeaps(UINT(std::size(postProcHeap)), postProcHeap);

// downscale pass

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDownScaleDescriptor(mRtvDescHeap->GetCpuHandle(RTV_Descriptors::Pass1RTV));
commandList->OMSetRenderTargets(1u, &rtvDownScaleDescriptor, FALSE, nullptr);

mPass1RenderTex->BeginScene(commandList);  // transition to render target state
mDownScalePass->SetSourceTexture(mPostProcSrvDescHeap->GetGpuHandle(SRV_PostProcDescriptors::HdrSRV), mHdrRenderTex->GetResource());
mDownScalePass->Process(commandList);
mPass1RenderTex->EndScene(commandList); // transition to pixel shader resource state

// blur horizontal pass

commandList->OMSetRenderTargets(1u, &rtvPass2Descriptor, FALSE, nullptr);

mPass2RenderTex->BeginScene(commandList); // transition to render target state
mGaussBlurPass->SetSourceTexture(mPostProcSrvDescHeap->GetGpuHandle(SRV_PostProcDescriptors::Pass1SRV), mPass1RenderTex->GetResource());
//mGaussBlurPass->SetSourceTexture(mPostProcSrvDescHeap->GetGpuHandle(SRV_PostProcDescriptors::HdrSRV), mHdrRenderTex->GetResource());
mGaussBlurPass->SetGaussianParameter(1.f);
mGaussBlurPass->SetBloomBlurParameters(TRUE, 4.f, 1.f); // horizontal blur
mGaussBlurPass->Process(commandList);
mPass2RenderTex->EndScene(commandList); // transition to pixel shader resource

// blur vertical pass

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvBlurDescriptor(mRtvDescHeap->GetCpuHandle(RTV_Descriptors::BlurRTV));
commandList->OMSetRenderTargets(1u, &rtvBlurDescriptor, FALSE, nullptr);

mBlurRenderTex->BeginScene(commandList); // transition to render target state
mGaussBlurPass->SetSourceTexture(mPostProcSrvDescHeap->GetGpuHandle(SRV_PostProcDescriptors::Pass2SRV), mPass2RenderTex->GetResource());
mGaussBlurPass->SetBloomBlurParameters(FALSE, 4.f, 1.f); // vertical blur
mGaussBlurPass->Process(commandList);
mBlurRenderTex->EndScene(commandList); // transition to pixel shader resource

// render the final image to hdr texture

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHdrDescriptor(mRtvDescHeap->GetCpuHandle(RTV_Descriptors::HdrRTV));
commandList->OMSetRenderTargets(1u, &rtvHdrDescriptor, FALSE, nullptr);

//mHdrRenderTex->BeginScene(commandList); // transition to render target state

commandList->SetGraphicsRootSignature(mRootSig.Get()); // bind root signature
commandList->SetPipelineState(mPsoDepthOfField.Get()); // set PSO

...

commandList->SetGraphicsRootConstantBufferView(RootParameterIndex::PSDofCB, psDofCB.GpuAddress());
commandList->SetGraphicsRootDescriptorTable(RootParameterIndex::PostProcDT, mPostProcSrvDescHeap->GetFirstGpuHandle());

// use the big triangle optimization to draw a fullscreen quad

commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
commandList->DrawInstanced(3u, 1u, 0u, 0u);

...

PIXBeginEvent(commandList, PIX_COLOR_DEFAULT, L"Tone Map");
// Set swapchain backbuffer as the tonemapping render target and unbind depth/stencil for sprites (UI)

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor(mDeviceResources->GetRenderTargetView());
commandList->OMSetRenderTargets(1u, &rtvDescriptor, FALSE, nullptr);

CD3DX12_GPU_DESCRIPTOR_HANDLE postProcTexture(mPostProcSrvDescHeap->GetGpuHandle(SRV_PostProcDescriptors::HdrSRV));
ApplyToneMapping(commandList, postProcTexture);

顶点着色器:

/*

    We use the 'big triangle' optimization that only requires three vertices to completely
    cover the full screen area.

    v0    v1        ID    NDC     UV
    *____*          --  -------  ----
    | | /           0   (-1,+1)  (0,0)
    |_|/            1   (+3,+1)  (2,0)
    | /             2   (-1,-3)  (0,2)
    |/
    *
    v2

*/

TexCoordVertexOut VS(uint id : SV_VertexID)
{
    TexCoordVertexOut vout;

    vout.texCoord = float2((id << 1u) & 2u, id & 2u);

    // See Luna p.687
    float x =  vout.texCoord.x * 2.f - 1.f;
    float y = -vout.texCoord.y * 2.f + 1.f;

    // Procedurally generate each NDC vertex.
    // The big triangle produces a quad covering the screen in NDC space.
    vout.posH = float4(x, y, 0.f, 1.f);

    // Transform quad corners to view space near plane.
    float4 ph = mul(vout.posH, InvProj);
    vout.posV = ph.xyz / ph.w;

    return vout;
}

像素着色器:

float4 PS(TexCoordVertexOut pin) : SV_TARGET
//float4 PS(float2 texCoord : TEXCOORD0) : SV_TARGET
{

...

    // Get downscale texture sample
    float3 colorDownScale = Pass1Tex.Sample(PointSampler, pin.texCoord).rgb;

...

    return float4(colorDownScale, 1.f); // only top-quarter of source input is rendered!
    //return float4(colorOutput, 1.f);
    //return float4(distortCoords, 0.f, 1.f);
    //return float4(colorHDR, 1.f);
    //return float4(colorBlurred, 1.f);
    //return float4(colorBloom, 1.f);
    //return float4((p.z * 0.01f).rrr, 1.f); // multiply by a contrast factor
}

PostProcess class 使用 'full-screen quad' 渲染模型。由于我们可以依赖 Direct3D 10.0 或更高版本 class 硬件,它使用 'self-generating quad' 模型来避免需要 VB.

因此,self-generating 四边形将放置在您设置视口的任何位置。还需要剪刀设置,因为它使用“big-triangle”优化来避免在图像上出现对角接缝,如果您将视口定位在除完整渲染目标之外的任何位置。

我在 Writing custom shaders 教程中有此详细信息,但我忘记在 wiki 上的 PostProcess 文档中复制它。

TL;DR: 当你渲染到较小的渲染目标时,使用:

auto vp = m_deviceResources->GetScreenViewport();

Viewport halfvp(vp);
halfvp.height /= 2.f;
halfvp.width /= 2.f;
commandList->RSSetViewports(1, halfvp.Get12());

然后当我们切换回您的 full-size 渲染目标时,使用:

commandList->RSSetViewports(1, &vp);

更新了 wiki 页面。