こんにちは、前回はテクスチャの貼り付けを行いました。今回はシャドウマッピングをやってみようと思い立ったのでやってみます。
ソースコードは例によってGitにあげてあります。
1.変更点
###ⅰ.シェーダ
シャドウマップのテクスチャを読み込んで影を表示する用と、シャドウマップの深度書き込み用とシャドウマップがちゃんと書き込まれているかを確認するようのシェーダを追加しました。通常描画用のシェーダはこんな感じです。
cbuffer cbTansMatrix : register(b0){
float4x4 WVP;
float4x4 World;
};
cbuffer cbLight : register(b1){
float4x4 LightVP;
float4 LightColor;
float3 LightDir;
};
Texture2D<float4> tex0 : register(t0);
Texture2D<float> shadow_map : register(t1);
SamplerState samp0 : register(s0);
SamplerState samp1 : register(s1);
struct VS_INPUT{
float3 Position : POSITION;
float3 Normal : NORMAL;
float2 UV : TEXCOORD;
};
struct PS_INPUT{//(VS_OUTPUT)
float4 Position : SV_POSITION;
float4 PosSM : POSITION_SM;
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, 0.0f);
output.Position = mul(Pos, WVP);
output.Normal = mul(Nrm, World);
output.UV = input.UV;
Pos = mul(Pos, World);
Pos = mul(Pos, LightVP);
Pos.xyz = Pos.xyz / Pos.w;
output.PosSM.x = (1.0f + Pos.x) / 2.0f;
output.PosSM.y = (1.0f - Pos.y) / 2.0f;
output.PosSM.z = Pos.z;
return output;
}
float4 PSMain(PS_INPUT input) : SV_TARGET{
float sm = shadow_map.Sample(samp1, input.PosSM.xy);
float sma = (input.PosSM.z - 0.005f < sm) ? 1.0f : 0.5f;
return tex0.Sample(samp0, input.UV) * sma;
}
//シャドーマップ計算用頂点シェーダ
float4 VSShadowMap(VS_INPUT input) : SV_POSITION{
float4 Pos = float4(input.Position, 1.0f);
Pos = mul(Pos, World);
Pos = mul(Pos, LightVP);
return Pos;
}
シャドウマップのテクスチャとサンプラがTexture2D<float> shadow_map : register(t1);
とSamplerState samp1 : register(s1);
になります。あとの計算はおなじみです。
デバッグ用のシェーダはこんな感じです。
cbuffer cbTansMatrix : register(b0){
float4x4 WVP;
};
Texture2D<float> 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;
float2 UV : TEXCOORD;
};
PS_INPUT VSMain(VS_INPUT input){
PS_INPUT output;
float4 Pos = float4(input.Position, 1.0f);
output.Position = mul(Pos, WVP);
output.UV.x = input.UV.x;
output.UV.y = 1.0f - input.UV.y;
return output;
}
float4 PSMain(PS_INPUT input) : SV_TARGET{
float color = tex0.Sample(samp0, input.UV);
return float4(color, color, color, 1.0f);
}
深度バッファテクスチャを受け取って白黒にして表示するだけです。
###ⅱ.深度バッファとデスクリプタヒープ
シャドウマップ用の深度バッファが必要になるため作成します。例によってデスクリプタにも登録しなければなりませんが、今回はデプスバッファ用のデスクリプタヒープとテクスチャ用のデスクリプタヒープに登録しています。深度情報を書き込む際はパイプラインにデプスバッファ用のデスクリプタを投げて描画し、影の描画を行う際はテクスチャとしてパイプラインに投げるというように使い分けるためです。
HRESULT D3D12Manager::CreateShadowBuffer(){
HRESULT hr;
D3D12_DESCRIPTOR_HEAP_DESC descriptor_heap_desc{};
descriptor_heap_desc.NumDescriptors = 1;
descriptor_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
descriptor_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
descriptor_heap_desc.NodeMask = 0;
hr = device_->CreateDescriptorHeap(&descriptor_heap_desc, IID_PPV_ARGS(&dh_shadow_buffer_));
if(FAILED(hr)){
return hr;
}
descriptor_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
descriptor_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
hr = device_->CreateDescriptorHeap(&descriptor_heap_desc, IID_PPV_ARGS(&dh_shadow_texture_));
if(FAILED(hr)){
return hr;
}
D3D12_HEAP_PROPERTIES heap_properties{};
D3D12_RESOURCE_DESC resource_desc{};
D3D12_CLEAR_VALUE clear_value{};
heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT;
heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heap_properties.CreationNodeMask = 0;
heap_properties.VisibleNodeMask = 0;
resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resource_desc.Width = 1024;
resource_desc.Height = 1024;
resource_desc.DepthOrArraySize = 1;
resource_desc.MipLevels = 0;
resource_desc.Format = DXGI_FORMAT_R32_TYPELESS;
resource_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
resource_desc.SampleDesc.Count = 1;
resource_desc.SampleDesc.Quality = 0;
resource_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
clear_value.Format = DXGI_FORMAT_D32_FLOAT;
clear_value.DepthStencil.Depth = 1.0f;
clear_value.DepthStencil.Stencil = 0;
hr = device_->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &resource_desc, D3D12_RESOURCE_STATE_GENERIC_READ, &clear_value, IID_PPV_ARGS(&shadow_buffer_));
if(FAILED(hr)){
return hr;
}
//深度バッファのビューの作成
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc{};
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsv_desc.Format = DXGI_FORMAT_D32_FLOAT;
dsv_desc.Texture2D.MipSlice = 0;
dsv_desc.Flags = D3D12_DSV_FLAG_NONE;
device_->CreateDepthStencilView(shadow_buffer_.Get(), &dsv_desc, dh_shadow_buffer_->GetCPUDescriptorHandleForHeapStart());
D3D12_SHADER_RESOURCE_VIEW_DESC resourct_view_desc{};
resourct_view_desc.Format = DXGI_FORMAT_R32_FLOAT;
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;
device_->CreateShaderResourceView(shadow_buffer_.Get(), &resourct_view_desc, dh_shadow_texture_->GetCPUDescriptorHandleForHeapStart());
return hr;
}
ここで作ったテクスチャ用のデスクリプタヒープはデバッグ表示用のパイプラインに渡すためのものです。
この深度バッファはSphereオブジェクトとPlaneオブジェクトのデスクリプタにも登録されています。
//テクスチャ用のデスクリプタヒープの作成
D3D12_DESCRIPTOR_HEAP_DESC descriptor_heap_desc{};
descriptor_heap_desc.NumDescriptors = 2;
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;
}
//シェーダリソースビューの作成
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);
resourct_view_desc.Format = DXGI_FORMAT_R32_FLOAT;
handle_srv.ptr += device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
device->CreateShaderResourceView(sm, &resourct_view_desc, handle_srv);
これはPlaneオブジェクトのデスクリプタヒープの作成部分ですが、NumDescriptorsが2になっていて2つのリソースを登録できるようになっています。そして
handle_srv = dh_texture_->GetCPUDescriptorHandleForHeapStart();
device->CreateShaderResourceView(texture_.Get(), &resourct_view_desc, handle_srv);
resourct_view_desc.Format = DXGI_FORMAT_R32_FLOAT;
handle_srv.ptr += device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
device->CreateShaderResourceView(sm, &resourct_view_desc, handle_srv);
の部分で画像のテクスチャと深度バッファのテクスチャを1つのデスクリプタヒープに登録しています。
なぜこのように深度バッファをいろいろなデスクリプタヒープに登録するようなめんどくさいことをしているかといいますと、DirectX12では1度の描画ではデスクリプタは各種1つずつしか登録できないっぽいからです (この辺で若干ハマりかけました)。つまりCBV用のデスクリプタ1つ、SRV用のデスクリプタ1つ...と言った具合です。なので1度の描画実行で使いたいテクスチャが複数ある場合はその全てを1つのSRV用のデスクリプタヒープに登録しておく必要があるということです。
###ⅲ.PSOの追加
前回まではシングルパスで描画が済んでいたのでPSOは1つで済んでいましたが、今回からはマルチパスレンダリングになるため、PSOを複数作る必要があります。前回までの通常描画用のPSOに加えて、深度書き込み用のPSOとデバグ表示用のPSOを追加しました。通常描画用のPSOのシェーダと深度書き込み用のPSOのシェーダはレジスタの設定が共通なのでルートシグネチャも共通のものを使いまわします。デバッグ用のシェーダは専用なのでそれ用のルートシグネチャを宣言しています。デバッグ用のオブジェクトはShadowMapDebugクラスにまとめてあります。
###ⅳ.深度情報のレンダリング
深度情報のレンダリング部分ですが次のようになっています。
//シャドーマップ用の深度バッファの描画------------------------------------------------------------------------------------------
//シャドーマップ用のテクスチャを深度書き込みに設定
SetResourceBarrier(shadow_buffer_.Get(), D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_DEPTH_WRITE);
//シャドーマップ用の深度バッファをクリア
command_list_->ClearDepthStencilView(dh_shadow_buffer_->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
//ルートシグネチャとPSOの設定
command_list_->SetGraphicsRootSignature(root_sugnature_.Get());
command_list_->SetPipelineState(shadow_map_pso_.Get());
//ビューポートとシザー矩形の設定
command_list_->RSSetViewports(1, &viewport_sm_);
command_list_->RSSetScissorRects(1, &scissor_rect_sm_);
//深度バッファの設定
command_list_->OMSetRenderTargets(0, nullptr, FALSE, &dh_shadow_buffer_->GetCPUDescriptorHandleForHeapStart());
//ライトの設定
command_list_->SetGraphicsRootConstantBufferView(1, light_buffer_->GetGPUVirtualAddress());
//球の描画
sphere_.Draw(command_list_.Get());
//板ポリの描画
plane_.Draw(command_list_.Get());
//シャドーマップ用のテクスチャをジェネリックリードに設定(そのうちちゃんと設定する)
SetResourceBarrier(shadow_buffer_.Get(), D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_GENERIC_READ);
hr = command_list_->Close();
ExecuteCommandList();
シャドウマップ用の深度バッファは作成時にD3D12_RESOURCE_STATE_GENERIC_READとして作成されているので、このまま深度を書き込もうとすると例外が発生してしまいます。なので最初に状態をD3D12_RESOURCE_STATE_DEPTH_WRITEに変更するためSetResourceBarrier関数を読んでいます。あとは普通に使う値をセットしてパイプラインを実行すると深度情報が書き込まれます。深度バッファは次の通常描画でテクスチャとして使用されるため、最後に状態をD3D12_RESOURCE_STATE_GENERIC_READに戻しています。
#2.実行
実行するとこんな感じで影が表示されます。地球の陰は境目がギザギザするので余力があったら修正しようと思います。
#3.おわりに
とりあえずDirectX12でシャドウマッピングまでできました。今後何しようか考え中です。