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 フラグも使用する必要があります。 次のコマンド ライン オプションは、このシェーダーを Effect-Compiler Tool (FXC) でコンパイルするときに使用されます。
fxc /Zi /E"PSSceneMain" /Od /Fo"dynamic_indexing_pixel.cso" /ps"_5_1" /nologo /enable_unbounded_descriptor_tables
ルート署名の設定
次にルート署名の定義を見てみましょう。特に、どのように無制限の配列のサイズを定義し、ルート定数を matIndex
にリンクするかを確認してください。 このピクセル シェーダーでは、SRV (Texture2D) 用の記述子テーブル、サンプラー用の記述子テーブル、1 つのルート定数の 3 つを定義します。 SRV 用の記述子テーブルには、CityMaterialCount + 1
エントリが含まれています。
CityMaterialCount
は g_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)));
}
テクスチャの作成
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 |
D3D12_RESOURCE_FLAGS [D3D12_RESOURCE_DIMENSION](/windows/desktop/api/d3d12/ne-d3d12-d3d12_resource_dimension) |
CreateCommittedResource |
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 |
[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 | |
D3D12_SUBRESOURCE_DATA | |
UpdateSubresources | |
ResourceBarrier |
拡散テクスチャの読み込み
拡散テクスチャ (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 | |
CreateCommittedResource | |
GetRequiredIntermediateSize | |
CreateCommittedResource | |
D3D12_SUBRESOURCE_DATA | |
ResourceBarrier |
サンプラーの作成
最後に LoadAssets で、拡散テクスチャまたはテクスチャ配列からサンプリングを行うためのサンプラーが 1 つ作成されます。
// 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 で設定されます。 都市ごとに描画コマンドを記録する二重の for ループで、SetGraphicsRoot32BitConstant の呼び出しを記録します。このときに、ルート署名に関するルート パラメーター インデックス (ここでは動的インデックスの値である 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[]
で異なるテクスチャが検索され、シーンが次のように表示されます。