D3D12DynamicIndexing 示例演示着色器模型 5.1 中提供的一些新的 HLSL 功能(尤其是动态索引和无界数组),以多次呈现同一网格,每次使用动态选择的材料呈现该网格。 借助动态索引,着色器现在可以对数组进行索引,而无需了解编译时的索引的值。 与未绑定的数组结合使用时,会为着色器作者和艺术管道添加另一个间接级别和灵活性。

  • 设置像素着色器
  • 设置根签名
  • 上载纹理数据
  • 加载漫射纹理
  • 创建取样器
  • 动态更改根参数索引
  • 设置像素着色器

    让我们先来看一下着色器本身,下面的示例是一个像素着色器。

    Texture2D        g_txDiffuse : register(t0);
    Texture2D        g_txMats[]  : register(t1);
    SamplerState     g_sampler   : register(s0);
    struct PSSceneIn
        float4 pos : SV_Position;
        float2 tex : TEXCOORD0;
    struct MaterialConstants
        uint matIndex;  // Dynamically set index for looking up from g_txMats[].
    ConstantBuffer<MaterialConstants> materialConstants : register(b0, space0);
    float4 PSSceneMain(PSSceneIn input) : SV_Target
        float3 diffuse = g_txDiffuse.Sample(g_sampler, input.tex).rgb;
        float3 mat = g_txMats[materialConstants.matIndex].Sample(g_sampler, input.tex).rgb;
        return float4(diffuse * mat, 1.0f);
    

    未绑定的数组功能由 g_txMats[] 数组表示,因为它未指定数组大小。 动态索引用于使用 matIndexg_txMats[] 进行索引,将定义为根常量。 着色器不了解大小、数组或编译时的索引的值。 两个属性都在与着色器一起使用的管道状态对象的根签名中定义。

    若要在 HLSL 中充分利用动态索引功能,需要使用 SM 5.1 编译着色器。 此外,若要使用无界数组,还必须使用 /enable_unbounded_descriptor_tables 标志。 以下命令行选项用于使用效果编译器工具 (FXC) 编译此着色器:

    fxc /Zi /E"PSSceneMain" /Od /Fo"dynamic_indexing_pixel.cso" /ps"_5_1" /nologo /enable_unbounded_descriptor_tables
    

    设置根签名

    现在,让我们看一下根签名定义,特别是如何定义未绑定数组的大小并将根常量链接到 matIndex。 对于像素着色器,我们定义以下三项:SRV 的描述符表(我们的 Texture2D)、取样器的描述符表和单个根常量。 SRV 的描述符表包含 CityMaterialCount + 1 个条目。 CityMaterialCount 是定义 g_txMats[] 的长度的常量,+ 1 代表 g_txDiffuse。 取样器的描述符表只包含一个条目,我们通过 LoadAssets 方法中的 InitAsConstants(…) 仅定义一个 32 位根常量值。

     // Create the root signature.
            CD3DX12_DESCRIPTOR_RANGE ranges[3];
            ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1 + CityMaterialCount, 0);  // Diffuse texture + array of materials.
            ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
            ranges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
            CD3DX12_ROOT_PARAMETER rootParameters[4];
            rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_PIXEL);
            rootParameters[1].InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_PIXEL);
            rootParameters[2].InitAsDescriptorTable(1, &ranges[2], D3D12_SHADER_VISIBILITY_VERTEX);
            rootParameters[3].InitAsConstants(1, 0, 0, D3D12_SHADER_VISIBILITY_PIXEL);
            CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
            rootSignatureDesc.Init(_countof(rootParameters), rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
            ComPtr<ID3DBlob> signature;
            ComPtr<ID3DBlob> error;
            ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error));
            ThrowIfFailed(m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature)));
    

    g_txMats[] 的内容是 LoadAssets 中创建的按顺序生成的纹理。 场景中呈现的每个城市共享相同的漫射纹理,但每个城市也有自己的按顺序生成的纹理。 纹理数组跨越彩虹色谱来轻松呈现索引技术。

     // Create the textures and sampler.
            // Procedurally generate an array of textures to use as city materials.
                // All of these materials use the same texture desc.
                D3D12_RESOURCE_DESC textureDesc = {};
                textureDesc.MipLevels = 1;
                textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
                textureDesc.Width = CityMaterialTextureWidth;
                textureDesc.Height = CityMaterialTextureHeight;
                textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
                textureDesc.DepthOrArraySize = 1;
                textureDesc.SampleDesc.Count = 1;
                textureDesc.SampleDesc.Quality = 0;
                textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
                // The textures evenly span the color rainbow so that each city gets
                // a different material.
                float materialGradStep = (1.0f / static_cast<float>(CityMaterialCount));
                // Generate texture data.
                vector<vector<unsigned char>> cityTextureData;
                cityTextureData.resize(CityMaterialCount);
                for (int i = 0; i < CityMaterialCount; ++i)
                    CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
                    ThrowIfFailed(m_device->CreateCommittedResource(
                        &heapProps,
                        D3D12_HEAP_FLAG_NONE,
                        &textureDesc,
                        D3D12_RESOURCE_STATE_COPY_DEST,
                        nullptr,
                        IID_PPV_ARGS(&m_cityMaterialTextures[i])));
                    // Fill the texture.
                    float t = i * materialGradStep;
                    cityTextureData[i].resize(CityMaterialTextureWidth * CityMaterialTextureHeight * CityMaterialTextureChannelCount);
                    for (int x = 0; x < CityMaterialTextureWidth; ++x)
                        for (int y = 0; y < CityMaterialTextureHeight; ++y)
                            // Compute the appropriate index into the buffer based on the x/y coordinates.
                            int pixelIndex = (y * CityMaterialTextureChannelCount * CityMaterialTextureWidth) + (x * CityMaterialTextureChannelCount);
                            // Determine this row's position along the rainbow gradient.
                            float tPrime = t + ((static_cast<float>(y) / static_cast<float>(CityMaterialTextureHeight)) * materialGradStep);
                            // Compute the RGB value for this position along the rainbow
                            // and pack the pixel value.
                            XMVECTOR hsl = XMVectorSet(tPrime, 0.5f, 0.5f, 1.0f);
                            XMVECTOR rgb = XMColorHSLToRGB(hsl);
                            cityTextureData[i][pixelIndex + 0] = static_cast<unsigned char>((255 * XMVectorGetX(rgb)));
                            cityTextureData[i][pixelIndex + 1] = static_cast<unsigned char>((255 * XMVectorGetY(rgb)));
                            cityTextureData[i][pixelIndex + 2] = static_cast<unsigned char>((255 * XMVectorGetZ(rgb)));
                            cityTextureData[i][pixelIndex + 3] = 255;
    
    CD3DX12_HEAP_PROPERTIES
    D3D12_HEAP_TYPE
    [D3D12_HEAP_FLAG] (/windows/desktop/api/d3d12/ne-d3d12-d3d12_heap_flags)
    CD3DX12_RESOURCE_DESC
    [D3D12_RESOURCE_STATES](/windows/desktop/api/d3d12/ne-d3d12-d3d12_resource_states)
    XMVECTOR
    XMVectorSet
    [XMColorHSLToRGB] (/windows/desktop/api/directxmath/nf-directxmath-xmcolorhsltorgb)

    上载纹理数据

    纹理数据通过上载堆上载到 GPU,并且将为每个纹理数据创建 SRV 并将其存储在 SRV 描述符堆中。

             // Upload texture data to the default heap resources.
                    const UINT subresourceCount = textureDesc.DepthOrArraySize * textureDesc.MipLevels;
                    const UINT64 uploadBufferStep = GetRequiredIntermediateSize(m_cityMaterialTextures[0].Get(), 0, subresourceCount); // All of our textures are the same size in this case.
                    const UINT64 uploadBufferSize = uploadBufferStep * CityMaterialCount;
                    CD3DX12_HEAP_PROPERTIES uploadHeap(D3D12_HEAP_TYPE_UPLOAD);
                    auto uploadDesc = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);
                    ThrowIfFailed(m_device->CreateCommittedResource(
                        &uploadHeap,
                        D3D12_HEAP_FLAG_NONE,
                        &uploadDesc,
                        D3D12_RESOURCE_STATE_GENERIC_READ,
                        nullptr,
                        IID_PPV_ARGS(&materialsUploadHeap)));
                    for (int i = 0; i < CityMaterialCount; ++i)
                        // Copy data to the intermediate upload heap and then schedule 
                        // a copy from the upload heap to the appropriate texture.
                        D3D12_SUBRESOURCE_DATA textureData = {};
                        textureData.pData = &cityTextureData[i][0];
                        textureData.RowPitch = static_cast<LONG_PTR>((CityMaterialTextureChannelCount * textureDesc.Width));
                        textureData.SlicePitch = textureData.RowPitch * textureDesc.Height;
                        UpdateSubresources(m_commandList.Get(), m_cityMaterialTextures[i].Get(), materialsUploadHeap.Get(), i * uploadBufferStep, 0, subresourceCount, &textureData);
                        auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_cityMaterialTextures[i].Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
                        m_commandList->ResourceBarrier(1, &barrier);
    

    加载漫射纹理

    漫射纹理(g_txDiffuse)以类似的方式上传并获取自己的 SRV,但纹理数据已在 occcity.bin 中定义。

    // Load the occcity diffuse texture with baked-in ambient lighting.
            // This texture will be blended with a texture from the materials
            // array in the pixel shader.
                D3D12_RESOURCE_DESC textureDesc = {};
                textureDesc.MipLevels = SampleAssets::Textures[0].MipLevels;
                textureDesc.Format = SampleAssets::Textures[0].Format;
                textureDesc.Width = SampleAssets::Textures[0].Width;
                textureDesc.Height = SampleAssets::Textures[0].Height;
                textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
                textureDesc.DepthOrArraySize = 1;
                textureDesc.SampleDesc.Count = 1;
                textureDesc.SampleDesc.Quality = 0;
                textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
                CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
                ThrowIfFailed(m_device->CreateCommittedResource(
                    &heapProps,
                    D3D12_HEAP_FLAG_NONE,
                    &textureDesc,
                    D3D12_RESOURCE_STATE_COPY_DEST,
                    nullptr,
                    IID_PPV_ARGS(&m_cityDiffuseTexture)));
                const UINT subresourceCount = textureDesc.DepthOrArraySize * textureDesc.MipLevels;
                const UINT64 uploadBufferSize = GetRequiredIntermediateSize(m_cityDiffuseTexture.Get(), 0, subresourceCount);
                CD3DX12_HEAP_PROPERTIES uploadHeap(D3D12_HEAP_TYPE_UPLOAD);
                auto uploadDesc = CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize);
                ThrowIfFailed(m_device->CreateCommittedResource(
                    &uploadHeap,
                    D3D12_HEAP_FLAG_NONE,
                    &uploadDesc,
                    D3D12_RESOURCE_STATE_GENERIC_READ,
                    nullptr,
                    IID_PPV_ARGS(&textureUploadHeap)));
                // Copy data to the intermediate upload heap and then schedule 
                // a copy from the upload heap to the diffuse texture.
                D3D12_SUBRESOURCE_DATA textureData = {};
                textureData.pData = pMeshData + SampleAssets::Textures[0].Data[0].Offset;
                textureData.RowPitch = SampleAssets::Textures[0].Data[0].Pitch;
                textureData.SlicePitch = SampleAssets::Textures[0].Data[0].Size;
                UpdateSubresources(m_commandList.Get(), m_cityDiffuseTexture.Get(), textureUploadHeap.Get(), 0, 0, subresourceCount, &textureData);
                auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(m_cityDiffuseTexture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
                m_commandList->ResourceBarrier(1, &barrier);
    

    最后,对于 LoadAssets,将创建单个取样器以从漫射纹理或纹理数组中取样。

     // Describe and create a sampler.
            D3D12_SAMPLER_DESC samplerDesc = {};
            samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
            samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
            samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
            samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
            samplerDesc.MinLOD = 0;
            samplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
            samplerDesc.MipLODBias = 0.0f;
            samplerDesc.MaxAnisotropy = 1;
            samplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
            m_device->CreateSampler(&samplerDesc, m_samplerHeap->GetCPUDescriptorHandleForHeapStart());
            // Create SRV for the city's diffuse texture.
            CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(m_cbvSrvHeap->GetCPUDescriptorHandleForHeapStart(), 0, m_cbvSrvDescriptorSize);
            D3D12_SHADER_RESOURCE_VIEW_DESC diffuseSrvDesc = {};
            diffuseSrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
            diffuseSrvDesc.Format = SampleAssets::Textures->Format;
            diffuseSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
            diffuseSrvDesc.Texture2D.MipLevels = 1;
            m_device->CreateShaderResourceView(m_cityDiffuseTexture.Get(), &diffuseSrvDesc, srvHandle);
            srvHandle.Offset(m_cbvSrvDescriptorSize);
            // Create SRVs for each city material.
            for (int i = 0; i < CityMaterialCount; ++i)
                D3D12_SHADER_RESOURCE_VIEW_DESC materialSrvDesc = {};
                materialSrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
                materialSrvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
                materialSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
                materialSrvDesc.Texture2D.MipLevels = 1;
                m_device->CreateShaderResourceView(m_cityMaterialTextures[i].Get(), &materialSrvDesc, srvHandle);
                srvHandle.Offset(m_cbvSrvDescriptorSize);
    

    动态更改根参数索引

    如果我们现在要呈现场景,则所有城市将显示相同的场景,因为我们未设置根常量 matIndex 的值。 每个像素着色器将对 g_txMats 的第 0 个槽进行索引,并且场景如下所示:

    根常量的值是在 FrameResource::PopulateCommandLists 中设置的。 在为每个城市记录绘制命令的双循环中,我们记录对 SetGraphicsRoot32BitConstants 的调用,指定有关根签名的根参数索引(在这种情况下为 3)以及动态索引和偏移量的值(在这种情况下为 0)。 由于 g_txMats 的长度等于我们呈现的城市数量,因此以增量方式为每个城市设置索引的值。

     for (UINT i = 0; i < m_cityRowCount; i++)
            for (UINT j = 0; j < m_cityColumnCount; j++)
                pCommandList->SetPipelineState(pPso);
                // Set the city's root constant for dynamically indexing into the material array.
                pCommandList->SetGraphicsRoot32BitConstant(3, (i * m_cityColumnCount) + j, 0);
                // Set this city's CBV table and move to the next descriptor.
                pCommandList->SetGraphicsRootDescriptorTable(2, cbvSrvHandle);
                cbvSrvHandle.Offset(cbvSrvDescriptorSize);
                pCommandList->DrawIndexedInstanced(numIndices, 1, 0, 0, 0);