005.テクスチャ付き四角形の描画(Dx12版)

 このサンプルはSimplSample005というディレクトリに含まれます。
 BaseCrossDx12.slnというソリューションを開くとDx12版が起動します。

 実行結果は以下のような画面が出ます。

 

図0005a

 


 動画は以下になります。

 

 

【共通解説】

 Dx12、Dx11両方に共通なのはシェーダーです。DxSharedプロジェクト内にシェーダファイルというフィルタがあり、そこに記述されてます。
 今回使用するシェーダは頂点シェーダとピクセルシェーダです。VertexPositionTexture型の頂点を持ち、コンスタントバッファからの入力で、位置を変更させています。
 またテクスチャも描画します。

 更新処理は動きは同じですが、Dx12版の更新処理で説明します。SquareSprite::OnUpdate()関数には、それぞれの四角形を個別に更新する方法が記述されています。

【Dx12版解説】

 BaseCrossDx12.slnを開くと、BaseCrossDx12というメインプロジェクトがあります。この中のCharacter.h/cppが主な記述個所になります。

■初期化■

 初期化は、SquareSprite::OnCreate()関数で行います。ここでは、頂点とインデックスの配列を作成し、それをもとにメッシュリソースを作成します。ここでは、前回までのサンプルとは違いVertexPositionTexture型の頂点になります(つまりテクスチャUV値が含まれます)。
 メッシュを作成した後テクスチャリソースの作成を行います。ここでは、コンストラクタで渡されたテクスチャファイル名を渡すことでテクスチャリソースを初期化します。L"WIC"というのは、テクスチャファイルのファイル形式です。L"WIC"というのはWindows Imaging Componentの略で、BMP、JPEG、PNGなど、一般的な画像形式はこのタイプで読み込めます。L"WIC"以外ではL"DDS"L"TGA"があります。前者はDirectX独特の圧縮ファイル形式で、後者はいわゆるタルガと呼ばれる場合もありますが、フォトショップでも書き出せる画像フォーマットです。
 ただし、これらの読み込みはプロジェクトとして組み込んでいるDirectXTexの機能に依存しています。

 テクスチャを読み込んだ後は、いつものようにDx12リソースの初期化になりますが、今サンプルは前サンプルと、頂点フォーマットも違いますので、リソースの作成も変わります。

■ルートシグネチャ作成■

 ルートシグネチャは、以下のようにRootSignature::CreateSrvSmpCbv()関数で初期化します。
void SquareSprite::CreateRootSignature() {
    //ルートシグネチャ
    m_RootSignature = RootSignature::CreateSrvSmpCbv();
}
 この関数は、以下のようになってます。
//シェーダリソースとサンプラーとコンスタントバッファ
static inline ComPtr<ID3D12RootSignature> CreateSrvSmpCbv() {
    auto Dev = App::GetApp()->GetDeviceResources();
    ComPtr<ID3D12RootSignature> Ret = Dev->GetRootSignature(L"SrvSmpCbv");
    if (Ret != nullptr) {
        return Ret;
    }

    CD3DX12_DESCRIPTOR_RANGE ranges[3];
    ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
    ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
    ranges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);

    CD3DX12_ROOT_PARAMETER rootParameters[3];
    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_ALL);

    CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
    rootSignatureDesc.Init(_countof(rootParameters), rootParameters, 0, nullptr,
         D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

    Ret = CreateDirect(rootSignatureDesc);
    Dev->SetRootSignature(L"SrvSmpCbv", Ret);
    return Ret;
}
 コメントの通りシェーダリソースとサンプラーとコンスタントバッファを持つルートシグネチャということになります。
 まずCD3DX12_DESCRIPTOR_RANGE(デスクプリタレンジ)は3つ作成します
。  今回新しく登場したレンジはシェーダーリソースビューサンプラーです(赤くなってます)。
 1つ目のレンジは、シェーダーリソースビューとして使用します。テクスチャはDirectXの内部的には(Dx11も同じ)、シェーダーリソースビューとして作成します。そのためタイプ名はD3D12_DESCRIPTOR_RANGE_TYPE_SRVSRVが付きます。
 2つめのレンジはサンプラーとして作成します。テクスチャはシェーダリソースビューサンプラーがないと表示できません。タイプ名はD3D12_DESCRIPTOR_RANGE_TYPE_SAMPLERです。
 3つ目のレンジはコンスタントバッファです。これは前サンプルと同じです。
 このようにして作成したデスクプリタレンジを使ってルートパラメータを作成します。最初のレンジ(シェーダーリソースビュー)と2番目のレンジ(サンプラー)は、ピクセルシェーダのみに渡します。3つ目のコンスタントバッファはすべてのシェーダに渡します。
 そのようにして作成したルートパラメータを使用して、ルートシグネチャを作成します。

■デスクプリタヒープ作成■

 デスクプリタヒープは、以下のように作成します。デスクリタヒープは2つ作成する必要があります。
 シェーダーリソースビュー及びコンスタントバッファ用のものとサンプラー用のものです。
 m_CbvSrvUavDescriptorHeapが前者で、m_SamplerDescriptorHeapが後者です。
void SquareSprite::CreateDescriptorHeap() {
    auto Dev = App::GetApp()->GetDeviceResources();
    m_CbvSrvDescriptorHandleIncrementSize =
    Dev->GetDevice()->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    //CbvSrvデスクプリタヒープ
    m_CbvSrvUavDescriptorHeap = DescriptorHeap::CreateCbvSrvUavHeap(1 + 1);
    //サンプラーデスクプリタヒープ
    m_SamplerDescriptorHeap = DescriptorHeap::CreateSamplerHeap(1);
    //GPU側デスクプリタヒープのハンドルの配列の作成
    m_GPUDescriptorHandleVec.clear();
    CD3DX12_GPU_DESCRIPTOR_HANDLE SrvHandle(
        m_CbvSrvUavDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),
        0,
        0
    );
    m_GPUDescriptorHandleVec.push_back(SrvHandle);
    CD3DX12_GPU_DESCRIPTOR_HANDLE SamplerHandle(
        m_SamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),
        0,
        0
    );
    m_GPUDescriptorHandleVec.push_back(SamplerHandle);
    CD3DX12_GPU_DESCRIPTOR_HANDLE CbvHandle(
        m_CbvSrvUavDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),
        1,
        m_CbvSrvDescriptorHandleIncrementSize
    );
    m_GPUDescriptorHandleVec.push_back(CbvHandle);
}
 DescriptorHeap::CreateCbvSrvUavHeap()関数シェーダーリソースビュー及びコンスタントバッファ用のヒープを作成します。DescriptorHeap::CreateSamplerHeap()関数サンプラー用のヒープです。ハンドル数は前者が2つ、後者が1つになります。
 その後GPU側デスクプリタヒープのハンドルの配列を作成します。この配列には、m_CbvSrvUavDescriptorHeap内のハンドルとm_SamplerDescriptorHeap内のハンドルを混在させてもかまいません。

■サンプラー作成■

 ここではデフォルトの2Dのサンプラーを作成しておきます。、

■シェーダーリソースビュー作成■

 シェーダーリソースビューは、m_CbvSrvUavDescriptorHeapにテクスチャリソースを結び付ける形で作成します。
 SquareSprite::CreateShaderResourceView()関数がその関数です。
void SquareSprite::CreateShaderResourceView() {
    auto Dev = App::GetApp()->GetDeviceResources();
    //テクスチャハンドルを作成
    CD3DX12_CPU_DESCRIPTOR_HANDLE Handle(
        m_CbvSrvUavDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
        0,
        0
    );
    //テクスチャのシェーダリソースビューを作成
    D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
    srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    //フォーマット
    srvDesc.Format = m_TextureResource->GetTextureResDesc().Format;
    srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MipLevels = m_TextureResource->GetTextureResDesc().MipLevels;
    //シェーダリソースビュー
    Dev->GetDevice()->CreateShaderResourceView(
        m_TextureResource->GetTexture().Get(),
        &srvDesc,
        Handle);
}
 赤くなっているところが、m_CbvSrvUavDescriptorHeapとテクスチャリソースを結びつけるのに重要な処理です。

■コンスタントバッファ作成■

 コンスタントバッファは、コンスタントバッファ構造体の型は違いますが、作成手順は前サンプルと同じです。

■パイプラインステート作成■

 このオブジェクトは透明処理をするかしないかを分けます。m_Traceメンバ変数がtrueだった場合、透明処理になります。
 透明処理ブレンドステートを変更することで透明にします。
void SquareSprite::CreatePipelineState() {
    D3D12_GRAPHICS_PIPELINE_STATE_DESC PineLineDesc;
    m_PipelineState
    = PipelineState::CreateDefault2D<VertexPositionTexture, 
    VSPTSprite, PSPTSprite>(m_RootSignature,PineLineDesc);
    //透明の場合はブレンドステート差し替え
    if (m_Trace) {
        D3D12_BLEND_DESC blend_desc;
        D3D12_RENDER_TARGET_BLEND_DESC Target;
        ZeroMemory(&blend_desc, sizeof(blend_desc));
        blend_desc.AlphaToCoverageEnable = false;
        blend_desc.IndependentBlendEnable = false;
        ZeroMemory(&Target, sizeof(Target));
        Target.BlendEnable = true;
        Target.SrcBlend = D3D12_BLEND_SRC_ALPHA;
        Target.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
        Target.BlendOp = D3D12_BLEND_OP_ADD;
        Target.SrcBlendAlpha = D3D12_BLEND_ONE;
        Target.DestBlendAlpha = D3D12_BLEND_ZERO;
        Target.BlendOpAlpha = D3D12_BLEND_OP_ADD;
        Target.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
        for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) {
            blend_desc.RenderTarget[i] = Target;
        }
        PineLineDesc.BlendState = blend_desc;
        m_PipelineState = PipelineState::CreateDirect(PineLineDesc);
    }
}
 if (m_Trace)の場合の分岐が透明処理です。

■コマンドリスト作成■

 コマンドリストはデフォルト処理です。

■コンスタントバッファの更新■

 コンスタントバッファの更新は前サンプルと同様です。コンスタントバッファ構造体の内容が違うだけです。

■更新処理■

 更新処理は、SquareSprite::OnUpdate()関数で実装されます。左右に移動しながら、中心からの距離が一定以上になれば逆向きに移動する形です。回転も移動方向に応じて別の回転をかけています。
 この処理はDx11版も変わりません。

■描画処理■

 描画処理で気を付けたいのは、テクスチャです。すでにシェーダーリソースビューの作成で、デスクプリタヒープと結び付けられているので、描画処理でテクスチャを設定する必要はありません。その代りデスクプリタヒープは2つ、しっかりと設定する必要があります。以下は描画処理の抜粋ですが、デスクプリタヒープを設定しているところを抜粋しています。
void SquareSprite::DrawObject() {
    //中略

    ID3D12DescriptorHeap* ppHeaps[] = 
    { m_CbvSrvUavDescriptorHeap.Get(), m_SamplerDescriptorHeap.Get() };
    m_CommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

    for (size_t i = 0; i < m_GPUDescriptorHandleVec.size(); i++) {
        m_CommandList->SetGraphicsRootDescriptorTable(i, m_GPUDescriptorHandleVec[i]);
    }

    //中略
}
 以上、Dx12側の説明は終わりです。

【まとめ】

 こうしてみると、Dx12版は初期化時に設定してしまう作りになっているのがわかります。つまり、動的に描画方法などを変更する場合はDx11版のほうが記述しやすいと思います。
 ただ、動的な変更ができるとは言っても、Dx11内部ではそのためにいろいろ負担はかかっているわけで、全部自分で記述しなければならないDx12版のほうが軽くしようと思えばいろいろ検討できるというのがわかります。
 もちろんDx12版であっても動的に変更できるようには記述できます。Dx11内部で行っているであろう処理を、ゲームプログラマ自身で、記述すればいいのですから・・・。