こんにちは。前回は板ポリを描画しましたので、今回はテクスチャを張ってみたいと思います。
#1.変更点
###頂点フォーマット
前回のコードから変更があった部分について重要な部分を書きます。まずは頂点フォーマットです。今回はテクスチャを貼るのでUV座標を追加します。また、色は必要ないため、削除します。
struct Vertex3D{
XMFLOAT3 Position; //位置
XMFLOAT3 Normal; //法線
XMFLOAT2 UV; //UV座標
};
法線は今回は使いませんが、そのうち使うため追加しておきました。
また、PipelineStateObjectを作成するときのフォーマット設定も変えています。
D3D12_INPUT_ELEMENT_DESC InputElementDesc[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
###シェーダ
シェーダもテクスチャ用に変更します。
cbuffer cbTansMatrix : register(b0){
float4x4 WVP;
};
Texture2D<float4> tex0 : register(t0);
SamplerState samp0 : register(s0);
struct VS_INPUT{
float3 Position : POSITION;
float3 Normal : NORMAL;
float2 UV : TEXCOORD;
};
struct PS_INPUT{//(VS_OUTPUT)
float4 Position : SV_POSITION;
float4 Normal : NORMAL;
float2 UV : TEXCOORD;
};
PS_INPUT VSMain(VS_INPUT input){
PS_INPUT output;
float4 Pos = float4(input.Position, 1.0f);
float4 Nrm = float4(input.Normal, 1.0f);
output.Position = mul(Pos, WVP);
output.Normal = mul(Nrm, WVP);
output.UV = input.UV;
return output;
}
float4 PSMain(PS_INPUT input) : SV_TARGET{
return tex0.Sample(samp0, input.UV);
}
Texture2D<float4> tex0 : register(t0);
がテクスチャがセットされるレジスタです。SamplerState samp0 : register(s0);
はUVによってテクスチャのどの部分を取ってくるかを設定するサンプラ用のレジスタです。
#2.テクスチャの作成
まずは画像の読み込みですが。画像データは読み込みやすいように自分で加工した物を読み込んでいます。横幅、縦幅、BGRA形式のデータの順で格納されています。DirectX12からはファイルの読み込みなどの関数は一切サポートが無いので、自力で読み込むしか無いです。
std::vector<byte> img;
std::fstream fs("earth", std::ios_base::in | std::ios_base::binary);
if(!fs){
return E_FAIL;
}
int width;
int height;
fs.read((char*)&width, sizeof(width));
fs.read((char*)&height, sizeof(height));
img.resize(width * height * 4);
fs.read((char*)&img[0], img.size());
fs.close();
次に定数バッファや頂点バッファのようにテクスチャを格納するバッファを作成します。定数バッファなどと同じ設定にするとCreateCommittedResourceが失敗するので、設定を変えて作成しています。その後、デスクリプタヒープを作成します。デスクリプタヒープは、メモリ上のリソースを指すポインタのようなもので、DirectX11までのViewに相当するもののようです。
D3D12_DESCRIPTOR_HEAP_DESC descriptor_heap_desc{};
descriptor_heap_desc.NumDescriptors = 1;
descriptor_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
descriptor_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
descriptor_heap_desc.NodeMask = 0;
hr = device->CreateDescriptorHeap(&descriptor_heap_desc, IID_PPV_ARGS(&dh_texture_));
if (FAILED(hr)) {
return hr;
}
テクスチャは、シェーダリソースビューとして扱うので、TypeをD3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAVにしています。
次はデスクリプタヒープにビューを作成します。
D3D12_CPU_DESCRIPTOR_HANDLE handle_srv{};
D3D12_SHADER_RESOURCE_VIEW_DESC resourct_view_desc{};
resourct_view_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
resourct_view_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
resourct_view_desc.Texture2D.MipLevels = 1;
resourct_view_desc.Texture2D.MostDetailedMip = 0;
resourct_view_desc.Texture2D.PlaneSlice = 0;
resourct_view_desc.Texture2D.ResourceMinLODClamp = 0.0F;
resourct_view_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
handle_srv = dh_texture_->GetCPUDescriptorHandleForHeapStart();
device->CreateShaderResourceView(texture_.Get(), &resourct_view_desc, handle_srv);
これでようやくテクスチャに必要なリソースの作成が完了しました。最後に画像データを書き込んで終了です。
#3.RootSignatureの設定
###定数バッファの設定
ある意味今回のメインのRootSignatureです。今回は座標変換行列以外にテクスチャとサンプラをシェーダに渡すので、前回に比べると設定項目が多くなっています。RootSignatureでどのような設定をすると、シェーダのレジスタやCommandListへのリソースの渡し方がどうなるかを見比べようと思います。まず定数バッファです。
root_parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
root_parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
root_parameters[0].Descriptor.ShaderRegister = 0;
root_parameters[0].Descriptor.RegisterSpace = 0;
command_list->SetGraphicsRootConstantBufferView(0, constant_buffer_->GetGPUVirtualAddress());
cbuffer cbTansMatrix : register(b0){
float4x4 WVP;
};
root_parametersの0番目に設定しているので、SetGraphicsRootConstantBufferViewの第一引数が0になっています。また、root_parameters[0].Descriptor.ShaderRegisterに0を渡しているのでシェーダはregister(b0)になっています。もしShaderRegisterを1にすればregister(b1)になります。
###テクスチャの設定
次にテクスチャです。
range[0].NumDescriptors = 1;
range[0].BaseShaderRegister = 0;
range[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
range[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
root_parameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
root_parameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
root_parameters[1].DescriptorTable.NumDescriptorRanges = 1;
root_parameters[1].DescriptorTable.pDescriptorRanges = &range[0];
ID3D12DescriptorHeap* heaps[] = {dh_texture_.Get()};
command_list->SetDescriptorHeaps(_countof(heaps), heaps);
command_list->SetGraphicsRootDescriptorTable(1, dh_texture_->GetGPUDescriptorHandleForHeapStart());
Texture2D<float4> tex0 : register(t0);
テクスチャはデスクリプタヒープを介して渡すので、rangeの設定が必要になります。rangeはデスクリプタヒープが持っているデスクリプタの数やレンジのタイプを設定します。このrangeをroot_parametersに設定します。root_parametersのDescriptorとDescriptorTableはUnionになっているので、どちらかを設定したらどちらかは無効になります(Constantsというメンバも共用体で宣言されていますがここでは割愛)。
ここもroot_parametersの1番目に設定しているので、SetGraphicsRootDescriptorTableの第一引数が1になっています。そして、デスクリプタヒープを渡しています。rangeのBaseShaderRegisterを0に設定しているので、register(t0)となっています。
###サンプラの設定
最後にサンプラです。サンプラはサンプリングステートを動的に変えられるものと、静的に決めておくものがあります。今回はサンプリングステートを動的に変える必要は無いので、スタティックサンプラを設定します。
sampler_desc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
sampler_desc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sampler_desc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sampler_desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sampler_desc.MipLODBias = 0.0f;
sampler_desc.MaxAnisotropy = 16;
sampler_desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
sampler_desc.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
sampler_desc.MinLOD = 0.0f;
sampler_desc.MaxLOD = D3D12_FLOAT32_MAX;
sampler_desc.ShaderRegister = 0;
sampler_desc.RegisterSpace = 0;
sampler_desc.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
//なし
SamplerState samp0 : register(s0);
スタティックサンプラは何もしなくてもシェーダに勝手に渡されます。これもsampler_desc.ShaderRegisterとregister(s0)が対応しています。ちなみにダイナミックサンプラを使用する場合は、サンプラのリソースを作ってデスクリプタヒープを作ってビューを作ってrangeの設定をしてCommandListにデスクリプタヒープを渡すことになります。
#実行
というわけで実行すると地球が描画されます。地軸の向きとか気にしたら負けです。
#終わりに
そんなこんなでテクスチャの貼り付けまでできました。なんか色々やること多くて大変ですね。次回はシャドウマッピングをやります。
DirectX12でシャドウマッピング