다음을 통해 공유


HLSL 5.1을 사용한 동적 인덱싱

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[] 배열로 나타냅니다. 루트 상수로 정의된 matIndex를 사용하여 g_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 항목이 포함되어 있습니다. CityMaterialCountg_txMats[]의 길이를 정의하는 상수이고, +1은 g_txDiffuse에 대한 것입니다. 예제 샘플러의 설명자 테이블에은 항목 1개만 포함하며, LoadAssets 메서드에서 InitAsConstants(…)를 통해 32비트 루트 상수 값 1개만 정의합니다.

 // 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)));
    }
호출 흐름 매개 변수
CD3DX12_DESCRIPTOR_RANGE D3D12_DESCRIPTOR_RANGE_TYPE
CD3DX12_ROOT_PARAMETER D3D12_SHADER_VISIBILITY
CD3DX12_ROOT_SIGNATURE_DESC D3D12_ROOT_SIGNATURE_FLAGS
ID3DBlob
D3D12SerializeRootSignature D3D_ROOT_SIGNATURE_VERSION
CreateRootSignature

 

텍스처 만들기

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;
                    }
                }
            }
        }
호출 흐름 매개 변수
D3D12_RESOURCE_DESC
DXGI_FORMAT
D3D12_RESOURCE_FLAGS
[D3D12_RESOURCE_DIMENSION](/windows/desktop/api/d3d12/ne-d3d12-d3d12_resource_dimension)
CreateCommittedResource
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);
                }
            }
호출 흐름 매개 변수
GetRequiredIntermediateSize
CreateCommittedResource
CD3DX12_HEAP_PROPERTIES
D3D12_HEAP_TYPE
D3D12_HEAP_FLAG
CD3DX12_RESOURCE_DESC
D3D12_RESOURCE_STATES
D3D12_SUBRESOURCE_DATA
UpdateSubresources
ResourceBarrier
CD3DX12_RESOURCE_BARRIER
D3D12_RESOURCE_STATES

 

확산 텍스처 로드

확산 텍스처인 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);
        }
호출 흐름 매개 변수
D3D12_RESOURCE_DESC
D3D12_RESOURCE_FLAGS
D3D12_RESOURCE_DIMENSION
CreateCommittedResource
CD3DX12_HEAP_PROPERTIES
D3D12_HEAP_TYPE
D3D12_HEAP_FLAG
CD3DX12_RESOURCE_DESC
D3D12_RESOURCE_STATES
GetRequiredIntermediateSize
CreateCommittedResource
CD3DX12_HEAP_PROPERTIES
D3D12_HEAP_TYPE
D3D12_HEAP_FLAG
CD3DX12_RESOURCE_DESC
D3D12_RESOURCE_STATES
D3D12_SUBRESOURCE_DATA
ResourceBarrier
CD3DX12_RESOURCE_BARRIER
D3D12_RESOURCE_STATES

 

샘플러 만들기

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);
        }   
호출 흐름 매개 변수
D3D12_SAMPLER_DESC
D3D12_FILTER

D3D12_FLOAT32_MAX (Constants)
D3D12_COMPARISON_FUNC
CreateSampler
CD3DX12_CPU_DESCRIPTOR_HANDLE GetCPUDescriptorHandleForHeapStart
D3D12_SHADER_RESOURCE_VIEW_DESC
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
D3D12_SRV_DIMENSION
CreateShaderResourceView
D3D12_SHADER_RESOURCE_VIEW_DESC
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
DXGI_FORMAT
D3D12_SRV_DIMENSION
CreateShaderResourceView

 

루트 매개 변수 인덱스를 동적으로 변경

이제 장면을 렌더링하면 모든 도시가 동일하게 나타날 것입니다. 루트 상수 matIndex의 값을 설정하지 않았기 때문입니다. 각 픽셀 셰이더는 g_txMats의 0번째 슬롯으로 인덱싱되며 장면은 다음과 같습니다.

모든 도시가 동일한 색으로 표시

루트 상수 값은 FrameResource::PopulateCommandLists에 설정됩니다. 각 도시에 대해 draw 명령이 기록되는 이중 for 루프에서 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);
        }
    }
호출 흐름 매개 변수
SetPipelineState
SetGraphicsRoot32BitConstant
SetGraphicsRootDescriptorTable
DrawIndexedInstanced

샘플 실행

이제 장면을 렌더링할 경우 각 도시는 matIndex에 대해 다른 값을 포함하고 g_txMats[]에서 다른 텍스처를 조회하므로 장면은 다음과 같이 표시됩니다.

모든 도시가 다른 색으로 표시

D3D12 코드 연습

효과 컴파일러 도구

Direct3D 12용 HLSL 셰이더 모델 5.1 기능

HLSL의 리소스 바인딩

셰이더 모델 5.1

HLSL의 루트 서명 지정