37.Dx12版サンプルを全面書き換え(1)

今回はいくつかのDx12版サンプルを全面的に書き換えましたGitHubに修正しました。
現時点で書き換えたのはSimpleSample001から004SimpleSample011、012です。

この記事は、
コミットSimplaSample012(Dx12版)の修正。ライブラリ修正あり
から、
コミットSimpleSample001から004と011,012(Dx12版)実装を全面書き換え。
および
コミットSimpleSample011,012(Dx12版)不具合修正。
の間の作業です。
GitHubサイト

https://github.com/WiZFramework/BaseCross

を参照して下さい。
*現時点でコミットは次に進んでますが、不具合修正です。

Dx12に関する情報は、マイクロソフト社のDirectX-Graphics-Samplesが主な情報源となります。

マイクロソフト社のDirectX-Graphics-SamplesのGitHubサイトは以下になります。

https://github.com/Microsoft/DirectX-Graphics-Samples

ぜひ、興味ある方はダウンロードしてみてDx12研究を始めるといいと思います。

今回の変更のポイント

前回、SimpleSample012(Dx12版)をアップしたのですが、その際、Dx12の処理を、1つの構造体にまとめた処理にしましたが、その件でちょっと整理されてないのではないかという意見をもらいました。
たしかにちょっと乱暴だったかと思います。
今回は、そのあたりの修正をすると同時に、SimpleSample001から004と011,012(Dx12版)実装について、その方針に従って書き換えました。
方針と書くと、なんだか大袈裟ですが、Dx12のオブジェクト側初期化処理Dx12のオブジェクト側描画処理を関数に細かく分ける方法にしました。そうすると何が必要かというのが構造的に把握できます。

初期化処理

まず初期化処理ですが、初期化に必要な処理は以下の項目と考えられます。


1、ルートシグネチャ作成
2、デスクプリタヒープ作成
3、サンプラー作成
4、シェーダーリソースビュー作成
5、コンスタントバッファ作成
6、パイプラインステート作成
7、コマンドリスト作成

です。
これを関数にあてはめますと


1、ルートシグネチャ作成
    void CreateRootSignature();
2、デスクプリタヒープ作成
    void CreateDescriptorHeap();
3、サンプラー作成
    void CreateSampler();
4、シェーダーリソースビュー作成
    void CreateShaderResourceView();
5、コンスタントバッファ作成
    void CreateConstantBuffer();
6、パイプラインステート作成
    void CreatePipelineState();
7、コマンドリスト作成
    void CreateCommandList();

となります。これを、Character.h/cpp描画するオブジェクトのメンバ関数として実装します。
前回SimpleSample012の実装で説明した内容とかぶるかもしれませんが、SimpleSample012も前回からの変更で全面的に書き換えられてますので、重複というより、今回の記事が正確な内容となっています(そういう意味では前回の記事は破棄すべきなのかもしれませんが)、開発途中のやりくりを記述するというのが、このブログの趣旨ですので、あえて前回の記事も残します。どこをどう修正したのか、比べてみるといいと思います。
比べられるように、新しいSimpleSample012をもとに説明したいと思います。

新しいSimpleSample012では上記メンバ関数が、SphereObjectクラスに実装されています。
それぞれの関数では、SphereObjectクラスのメンバ変数である、各種Dx12リソースを初期化するわけですが、どのようなDx12リソースを持ってるか以下列挙します。


///ルートシグネチャ
ComPtr<ID3D12RootSignature> m_RootSignature;
///CbvSrvのデスクプリタハンドルのインクリメントサイズ
UINT m_CbvSrvDescriptorHandleIncrementSize{ 0 };
///デスクプリタヒープ
ComPtr<ID3D12DescriptorHeap> m_CbvSrvUavDescriptorHeap;
ComPtr<ID3D12DescriptorHeap> m_SamplerDescriptorHeap;
///GPU側デスクプリタのハンドルの配列
vector<CD3DX12_GPU_DESCRIPTOR_HANDLE> m_GPUDescriptorHandleVec;

///コンスタントバッファ
struct StaticConstantBuffer
{
    Matrix4X4 World;
    Matrix4X4 View;
    Matrix4X4 Projection;
    Vector4 LightDir;
    Color4 Emissive;
    Color4 Diffuse;
    StaticConstantBuffer() {
        memset(this, 0, sizeof(StaticConstantBuffer));
    };
};
///コンスタントバッファのオブジェクト側変数
StaticConstantBuffer m_StaticConstantBuffer;
///コンスタントバッファアップロードヒープ
ComPtr<ID3D12Resource> m_ConstantBufferUploadHeap;
///コンスタントバッファのGPU側変数
void* m_pConstantBuffer{ nullptr };
///パイプラインステート
ComPtr<ID3D12PipelineState> m_CullBackPipelineState;
ComPtr<ID3D12PipelineState> m_CullFrontPipelineState;
///コマンドリスト
ComPtr<ID3D12GraphicsCommandList> m_CommandList;

内容はコメントの通りですが、これらのリソースをすべて初期化する必要があります。
初期化順は、上記の番号順で、SphereObject::OnCreate関数にて、メッシュとテクスチャが構築された後に、順次呼び出しています。

1、ルートシグネチャ作成

まず、以下が1、ルートシグネチャ作成です。


///ルートシグネチャ作成
void SphereObject::CreateRootSignature() {
    //ルートシグネチャ
    m_RootSignature = RootSignature::CreateSrvSmpCbv();
}

まず断っておくべきはRootSignature::ではじまる関数についてです。追いかけていくとわかると思いますが、これはネームスペースになっていて、それぞれのリソースのユーティリティ的な実装をカプセル化しています。VSPSDrawContextクラスのように一通りこなす欲張りなものではなく、一般的な記述をカプセル化しています。
RootSignature::のほかにDescriptorHeap::DynamicSampler::PipelineState::CommandList::があります。
これらのユーティリティをわざと使用せずに全部Character.h/cppで記述したものはSimpleSample011にあります。
こちらと見比べてみると、どのような部分をカプセル化しているかがわかると思います。

ルートシグネチャ作成に話を戻します。SphereObject::CreateRootSignature関数ではRootSignatureネームスペースの関数を一つ呼び出して、その戻り値をm_RootSignatureに代入しています。
この関数RootSignature::CreateSrvSmpCbv()は、シェーダーリソース(テクスチャ)、サンプラー、コンスタントバッファを持つルートシグネチャという意味です。
SimpleSample012のサンプルでは、シェーダ処理としてメッシュ(つまり頂点)のほかにテクスチャ処理コンスタントバッファからの入力を受け付けます。テクスチャ処理テクスチャ本体(シェーダーリソース)サンプラーが必要です。ですので、このような設定でルートシグネチャを初期化しています。

2、デスクプリタヒープ作成

続いて2、デスクプリタヒープ作成です。これはSphereObject::CreateDescriptorHeap関数です。


///デスクプリタヒープ作成
void SphereObject::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);
}

ここでは2つのデスクプリタヒープを作成してます。m_CbvSrvUavDescriptorHeapm_SamplerDescriptorHeapです。前者はシェーダーリソース(テクスチャ)もしくはコンスタントバッファ用で、後者はサンプラー用です。
m_CbvSrvDescriptorHandleIncrementSizeというのは、デスクプリタヒープ内での、ハンドルの間隔です。ハンドルというのは、そのデスクプリタヒープ内のそれぞれの情報領域の番号のようなもので、例えばm_CbvSrvUavDescriptorHeapにはシェーダリソースコンスタントバッファハンドルが保持されるわけですが、そのハンドル間のサイズm_CbvSrvDescriptorHandleIncrementSizeとなります。この値は後ほど使用します。
続く、GPU側デスクプリタヒープのハンドルの配列の作成は、シェーダリソース(テクスチャ)のハンドルサンプラーのハンドルコンスタントバッファのハンドルをそれぞれのデスクプリタヒープ内から取得して、それを配列化しています。
サンプラーのハンドルはサンプラーのデスクプリタヒープは一つのサンプラーしかありませんので、先頭にあるので


    CD3DX12_GPU_DESCRIPTOR_HANDLE SamplerHandle(
        m_SamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),
        0,
        0
    );

のような記述になります。先頭から0番目の意味ですね。
一方シェーダーリソース(テクスチャ)コンスタントバッファは、1つのデスクプリタヒープに入っているので、どこのハンドルかが重要になります。
シェーダーリソース(テクスチャ)はデスクプリタヒープの先頭に置くので


    CD3DX12_GPU_DESCRIPTOR_HANDLE SrvHandle(
        m_CbvSrvUavDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),
        0,
        0
    );

となります。サンプラーと同じような設定ですね。
コンスタントバッファは、シェーダーリソース(テクスチャ)の後ろに置くので


    CD3DX12_GPU_DESCRIPTOR_HANDLE CbvHandle(
        m_CbvSrvUavDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),
        1,
        m_CbvSrvDescriptorHandleIncrementSize
    );

となります。意味あい的にはスタート位置からm_CbvSrvDescriptorHandleIncrementSizeを1つだけインクリメントした場所ということになります。
ここで取得してるのはGPU側ハンドルです。あとでCPU側ハンドルも取得して、ビューというのを作成しなければいけないのですが、この処理は個別に行います。

3、サンプラー作成

サンプラーの作成はSphereObject::CreateSampler関数です。


///サンプラー作成
void SphereObject::CreateSampler() {
    auto SamplerDescriptorHandle = m_SamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
    DynamicSampler::CreateSampler(SamplerState::LinearClamp, SamplerDescriptorHandle);
}

ずいぶんシンプルな記述ですが、これはユーティリティネームスペースDynamicSampler::に追うところが大きいです。
まず、最初の


    auto SamplerDescriptorHandle = m_SamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart();

ですが、これはサンプラーのCPU側ハンドルを取得している記述で


CD3DX12_CPU_DESCRIPTOR_HANDLE SamplerDescriptorHandle(
    m_SamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
    0,
    0
);

と同じです。先頭から0番目なので先頭位置でも一緒ですね。注意したいのはデスクプリタヒープ作成で行った
のはGPU側ハンドルですが、今回はCPU側ハンドルです。
このように、Dx12ではCPU側GPU側という概念がありますので、気を付けましょう。
その後、サンプラーユーティリティによって、LinearClampのサンプラーが作成されます。この関数を追いかけていくとわかるのですが、最終的には、デバイスのCreateSampler関数に行きつきます。
この関数は、個人的にはビューという言葉を入れてほしかったなあ、と思います。つまりCreateSamplerView関数としてくれれば、この後の、コンスタントバッファやシェーダーリソースとの関連性が取れるのに、と思うのです。
コンスタントバッファCreateConstantBufferView関数という形でビューを作成します。シェーダーリソースCreateShaderResourceView関数という関数名です。見たところサンプラーも同様のビューを作成しているようなのですが、名前に関連性がないために、ちょっと間違いやすいです。(それともサンプラーの場合はビュー以外の使い方があるのかもしれませんが・・・)。

LinearClampのサンプラーがどのようなサンプラーなのかは、追いかけていくとわかりますが、SimpleSample011を見ていただければDynamicSampler::ユーティリティを使わずにサンプラーを作成しているので、参考になるかと思います。

4、シェーダーリソースビュー作成

シェーダーリソース(テクスチャ)SphereObject::CreateShaderResourceView関数です。
テクスチャリソースそのものは、あらかじめ作成しておきます。


///シェーダーリソースビュー(テクスチャ)作成
void SphereObject::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);
}

ここではCPU側デスクプリタハンドルを作成します。サンプラーのところでも説明したのでわかりますね。
この関数ではシェーダーリソースビューを作成するのが目的なのですが、そのためにはビューを作成するためにテクスチャの構造をデバイスに伝えなければなりません。
それを行っているのがD3D12_SHADER_RESOURCE_VIEW_DESC構造体です。この構造体に、このテクスチャのフォーマット、ミップレベルなどを設定します。この内容は、DirextXTexライブラリによって作成されテクスチャリソース(つまりm_TextureResource)にまとめてありますので、それを利用するわけです。
Dx11の場合は、シェーダーリソースビューDirextXTexライブラリによって作成されますが、Dx12の場合は情報を取り出さなければなりません。
ただDx11シェーダーリソースビューは、それ自体が変数(インターフェイス)なのですが、Dx12の場合はデバイスに作成を命じるというだけで、変数として使えるわけではありません。そのあたりのDx11とDx12の違いには、最初、戸惑うかもしれません。

5、コンスタントバッファ作成

コンスタントバッファ作成はSphereObject::CreateConstantBuffer関数です。ここではビューも作成するわけですが、その前に、コンスタントバッファに転送するためのアップロードヒープというのを作成しなければなりません。


///コンスタントバッファ作成
void SphereObject::CreateConstantBuffer() {
    auto Dev = App::GetApp()->GetDeviceResources();
    //コンスタントバッファリソース(アップロードヒープ)の作成
    ThrowIfFailed(Dev->GetDevice()->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(sizeof(StaticConstantBuffer)),
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(&m_ConstantBufferUploadHeap)),
        L"コンスタントバッファ用のアップロードヒープ作成に失敗しました",
        L"Dev->GetDevice()->CreateCommittedResource()",
        L"SphereObject::CreateConstantBuffer()"
    );
    //コンスタントバッファのビューを作成
    D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
    cbvDesc.BufferLocation = m_ConstantBufferUploadHeap->GetGPUVirtualAddress();
    //コンスタントバッファは256バイトにアラインメント
    cbvDesc.SizeInBytes = (sizeof(StaticConstantBuffer) + 255) & ~255;
    //コンスタントバッファビューを作成すべきデスクプリタヒープ上のハンドルを取得
    //シェーダリソースがある場合コンスタントバッファはシェーダリソースビューのあとに設置する
    CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(
        m_CbvSrvUavDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
        1,
        m_CbvSrvDescriptorHandleIncrementSize
    );
    Dev->GetDevice()->CreateConstantBufferView(&cbvDesc, cbvSrvHandle);
    //コンスタントバッファのアップロードヒープのマップ
    CD3DX12_RANGE readRange(0, 0);
    ThrowIfFailed(m_ConstantBufferUploadHeap->Map(0, &readRange, reinterpret_cast<void**>(&m_pConstantBuffer)),
        L"コンスタントバッファのマップに失敗しました",
        L"pImpl->m_ConstantBufferUploadHeap->Map()",
        L"SphereObject::CreateConstantBuffer()"
    );
}

ここで行っているのは、アップロードヒープの作成コンスタントバッファのビューを作成コンスタントバッファのアップロードヒープのマップです。
コンスタントバッファは常時変更されるリソースです。そのため、簡単にデータをGPU側に転送できるようにマップを行っておきます。
アップロードヒープというのは、シェーダに転送されるバッファと考えられます。それをC++側のポインタにマップすることで、このポインタにコピーすることでコンスタントバッファの更新ができるようにします。
コンスタントバッファのビューシェーダリソースビューサンプラーと同じです。デスクプリタヒープのCPU側の位置でハンドルを作成し、そのハンドルと定義構造体で、コンスタントバッファのビューを作成します。
ここで気を付けたいのはCPU側ハンドルの作成です。GPU側と同じように、コンスタントバッファのデスクプリタヒープは、シェーダリソースと兼用するので、シェーダーリソースのハンドルの後に設定します。


    CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(
        m_CbvSrvUavDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),
        1,
        m_CbvSrvDescriptorHandleIncrementSize
    );

スタート位置からm_CbvSrvDescriptorHandleIncrementSizeを1つ分加算したハンドルという意味です。

6、パイプラインステート作成

パイプライステートは、3D用の設定を行います。SphereObject::CreatePipelineState関数です。


///パイプラインステート作成
void SphereObject::CreatePipelineState() {
	//パイプラインステートの作成
	D3D12_GRAPHICS_PIPELINE_STATE_DESC PineLineDesc;
	PipelineState::CreateDefault3D<VertexPositionNormalTexture, VSPNTStatic, PSPNTStatic>(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;
	}

	PineLineDesc.RasterizerState.FillMode = D3D12_FILL_MODE::D3D12_FILL_MODE_SOLID;
	PineLineDesc.RasterizerState.CullMode = D3D12_CULL_MODE::D3D12_CULL_MODE_FRONT;

	m_CullFrontPipelineState = PipelineState::CreateDirect(PineLineDesc);

	PineLineDesc.RasterizerState.CullMode = D3D12_CULL_MODE::D3D12_CULL_MODE_BACK;
	m_CullBackPipelineState = PipelineState::CreateDirect(PineLineDesc);
}

ここでちょっと、PipelineStateユーティリティの使用方法として、テクニックを使ってます。


	//パイプラインステートの作成
	D3D12_GRAPHICS_PIPELINE_STATE_DESC PineLineDesc;
	PipelineState::CreateDefault3D<VertexPositionNormalTexture, VSPNTStatic, PSPNTStatic>(m_RootSignature, PineLineDesc);

により本来ならPipelineState::CreateDefault3D関数の戻り値がパイプライステートになるのですが、その戻り値は使わずに、引数で渡しているPineLineDescを再利用しています。
すなわち、PipelineState::CreateDefault3D関数はパイプラインステートを作成するだけではなく、その作成に使った設定値を戻してくれます。
実際には、そのPineLineDescの中のブレンドステートとラスタライザを差し替えて、今度はPipelineState::CreateDirect関数を呼び出して、その戻り値を使用します。
このような手法を使うことで、デフォルトのパイプラインステートの値を少しカスタマイズしたパイプラインステートを作り出すことができます。
ここでは、透明の場合は、フロントカリングとバックカリングの2種類のパイプラインステートが必要ですので、その2種類を作成しています。

7、コマンドリスト作成

初期化の最後はコマンドリスト作成です。Dx12コマンドリストという命令文のかたまりみたいなものをため込み、最後に実行という形で描画処理します。
コマンドリストをどのような単位で実装するのか、この辺りは根本的な設計の問題なのですが、BaseCrossの場合は、できるだけ汎用的に配置オブジェクトを作成できるように、配置オブジェクト単位コマンドリストを持つようにしています。ただ、この設計が必ずしもベストとかはぎりません。例えば何種類ものオブジェクトを一つのコマンドリストで実装することもできるでしょう。しかしその場合、頻繁にパイプラインステートを変更したりが必要になります。
実際にコマンドリストをオブジェクト単位で持つのと、ゲーム全体で少数のコマンドリストで実装するのでは、どのくらい速度に差があるのか、現時点では計測してませんし、今後も計測する気はありません。BaseCrossとしては配置オブジェクト単位で持つ、という仕様があるだけです。ただ、1つの配置オブジェクトに、複数のメッシュやテクスチャを持たせることは可能です(メンバ変数に持てばいいのです)。ですから、配置オブジェクトの設計いかんではどのようにもなる、ということです(特にシンプルバージョンではより自由度が高いと思います)。


///コマンドリスト作成
void SphereObject::CreateCommandList() {
	//コマンドリストは裏面カリングに初期化
	m_CommandList = CommandList::CreateDefault(m_CullBackPipelineState);
	CommandList::Close(m_CommandList);
}

コマンドリストの作成はこのようにシンプルです。作成時にパイプラインステートを渡してますが、これは描画途中でも変更できますので、あまり重要ではありません。コマンドリストを作成したらクローズさせます。

更新描画処理

更新描画処理については、一応、以下のように分けています


1、OnUpdate処理
2、コンスタントバッファの更新
3、描画処理

1、OnUpdate処理OnUpdateで実行される処理です。ここでは、回転したり移動したりの処理になります。ここは説明は割愛します。

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

コンスタントバッファの更新SphereObject::UpdateConstantBuffer関数です。ここではOnUpdate処理によって変更された位置や回転値に従って(あるいはカメラの要素も加味して)、ワールド、ビュー、射影各行列を作成します。
作成した行列はそのままコンスタントバッファの構造体に代入し、それをコンスタントバッファの作成でマップしたアップロードヒープに転送します。


void SphereObject::UpdateConstantBuffer() {
	//行列の定義
	Matrix4X4 World, View, Proj;
	//ワールド行列の決定
	World.AffineTransformation(
		m_Scale,			//スケーリング
		Vector3(0, 0, 0),		//回転の中心(重心)
		m_Qt,				//回転角度
		m_Pos				//位置
	);
	//転置する
	World.Transpose();
	//ビュー行列の決定
	View.LookAtLH(Vector3(0, 2.0, -5.0f), Vector3(0, 0, 0), Vector3(0, 1.0f, 0));
	//転置する
	View.Transpose();
	//射影行列の決定
	float w = static_cast<float>(App::GetApp()->GetGameWidth());
	float h = static_cast<float>(App::GetApp()->GetGameHeight());
	Proj.PerspectiveFovLH(XM_PIDIV4, w / h, 1.0f, 100.0f);
	//転置する
	Proj.Transpose();
	//ライティング
	Vector4 LightDir(0.5f, -1.0f, 0.5f, 0.0f);
	LightDir.Normalize();
	m_StaticConstantBuffer.World = World;
	m_StaticConstantBuffer.View = View;
	m_StaticConstantBuffer.Projection = Proj;
	m_StaticConstantBuffer.LightDir = LightDir;
	m_StaticConstantBuffer.Diffuse = Color4(1.0f, 1.0f, 1.0f, 1.0f);
	m_StaticConstantBuffer.Emissive = Color4(0, 0, 0, 0);
	//更新
	memcpy(m_pConstantBuffer, reinterpret_cast<void**>(&m_StaticConstantBuffer),
		sizeof(m_StaticConstantBuffer));
}

ここではm_StaticConstantBufferC++側の構造体インスタンスですので、これを、m_pConstantBufferにコピー(memcpy関数使用)することでアップロードヒープへの転送ができます。m_pConstantBufferアップロードヒープにマップされているので可能なわけです。

3、描画処理

描画処理OnDraw関数に記述するわけですが、ここでは、


void SphereObject::OnDraw() {
	//コンスタントバッファの更新
	UpdateConstantBuffer();
	//描画
	DrawObject();
}

のように、UpdateConstantBuffer()呼び出しをした後DrawObject関数呼び出しをしています。UpdateConstantBuffer()呼び出しについては上記説明しました。
残るDrawObject関数が描画処理となります。


///描画処理
void SphereObject::DrawObject() {
	//コマンドリストのリセット
	if (m_Trace) {
		CommandList::Reset(m_CullFrontPipelineState, m_CommandList);
	}
	else {
		CommandList::Reset(m_CullBackPipelineState, m_CommandList);
	}

	m_SphereMesh->UpdateResources<VertexPositionNormalTexture>(m_CommandList);
	m_TextureResource->UpdateResources(m_CommandList);

	//描画
	m_CommandList->SetGraphicsRootSignature(m_RootSignature.Get());
	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]);
	}
	auto Dev = App::GetApp()->GetDeviceResources();
	m_CommandList->RSSetViewports(1, &Dev->GetViewport());
	m_CommandList->RSSetScissorRects(1, &Dev->GetScissorRect());

	CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(
		Dev->GetRtvHeap()->GetCPUDescriptorHandleForHeapStart(),
		Dev->GetFrameIndex(),
		Dev->GetRtvDescriptorSize());
	CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(
		Dev->GetDsvHeap()->GetCPUDescriptorHandleForHeapStart()
	);
	m_CommandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);

	m_CommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	m_CommandList->IASetIndexBuffer(&m_SphereMesh->GetIndexBufferView());
	m_CommandList->IASetVertexBuffers(0, 1, &m_SphereMesh->GetVertexBufferView());
	m_CommandList->DrawIndexedInstanced(m_SphereMesh->GetNumIndicis(), 1, 0, 0, 0);
	if (m_Trace) {
		m_CommandList->SetPipelineState(m_CullBackPipelineState.Get());
		m_CommandList->DrawIndexedInstanced(m_SphereMesh->GetNumIndicis(), 1, 0, 0, 0);
	}

	//コマンドリストのクローズ
	CommandList::Close(m_CommandList);
	//デバイスにコマンドリストを送る
	Dev->InsertDrawCommandLists(m_CommandList.Get());
}

ここではもっぱらコマンドリストに命令をため込む作業になります。
ここでデスクプリタヒープの作成で作成したGPU側デスクプリタヒープハンドルの配列を使って登録をします。
また、m_CommandList->SetDescriptorHeaps関数で、使用するデスクプリタヒープを設定します。
最後に、m_CommandList->DrawIndexedInstanced関数で描画するわけですが、透明処理をする場合パイプラインステートを変更して2回描画しています。(最初に裏面、そして次に表面)
描画が終わったら、コマンドリストをクローズして、コマンドリストの保存プールにインサートします。


	//デバイスにコマンドリストを送る
	Dev->InsertDrawCommandLists(m_CommandList.Get());

がその処理です。Dev->InsertDrawCommandLists関数はDx12の関数ではありません。BaseCross内の関数です。

まとめ

前回に続きSimpleSample012の説明でした。GitHubのコミットをさかのぼってみると、どのように書き換えたのかがわかると思います。いくらか前回よりは見やすくなったかと思います。
今回の更新でほかのサンプルもいくつか書き換えています。基本的にSimpleSample012のような記述方法を使ってます。最初のほうのサンプルは、サンプラーやテクスチャを使わないのもあります。そのような場合、ルートシグネチャなども設定が変わります。
また、SimpleSample011SimpleSample012と同じような処理ですが、SimpleSample011はわざとCommandListネームスペースなどの各種ユーティリティを使用しないで、ガチでDx12を作りこむ記述方法をしています。SimpleSample012との違いを確認すると、Dx12の学習に役立つと思います。

またSimpleSample004は少し特殊な処理で、コンスタントバッファを大量に使う(サンプルでは100個)場合の参考になるでしょう。もっと多い場合は、後で出てくるインスタンス描画もいいかもしれません。

いずれにせよ、いろいろ実験を繰り返しながら、Dx12の実装も形が見えてきました。
僕自身も、さまざまな方法を試すことで自由度簡略化(ライブラリ化)の間を行き来しながら、勉強できたと感じています。
今後はここまで得た知識や情報をもとに、残りのシンプルバージョンの作成(修正)と、いいよいよフルバージョンの作成にも取り掛かりたいと思います。
今後ともよろしくお願いします。
では今回はこの辺で。

カテゴリー

ピックアップ記事

  1. 2016092201
    今回は前回のサンプルを少し機能を追加しまして、いろんなオブジェクトを追加しています。FullTuto…
  2. 2016092001
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル003をアップしました。内…
  3. eyecatch
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル002で懸案となっていまし…
PAGE TOP