
マイケル
みなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
こんにちクマ〜〜〜

マイケル
今日は前回から引き続き、DirectX12を触っていきます!
前回はポリゴン表示まで行ったので、続きとしてDirectXTexというライブラリを使用してテクスチャ描画するところまで進めていこうと思います。
前回はポリゴン表示まで行ったので、続きとしてDirectXTexというライブラリを使用してテクスチャ描画するところまで進めていこうと思います。

エレキベア
ポリゴンにテクスチャを貼り付けるクマね

マイケル
実装方法については公式のサンプルと下記の書籍を参考にさせていただきました!
この記事を読むことで、サンプルのコードの内容も大体理解できるようになると思います。
この記事を読むことで、サンプルのコードの内容も大体理解できるようになると思います。
GitHub – microsoft / DirectX-Graphics-Samples (HelloTexture)
↑公式のサンプル
DirectX 12の魔導書 3Dレンダリングの基礎からMMDモデルを踊らせるまで

エレキベア
日本語の書籍はありがたいクマ〜〜〜

マイケル
なお、今回作ったサンプルについてはGitHubに上げていますので、
全体の実装についてはこちらをご参照ください!
全体の実装についてはこちらをご参照ください!
GitHub – masarito617/cpp-dx12-sample-texture
↑今回作ったサンプル

エレキベア
前回作ったのをベースに実装を加えたものクマね
シェーダーにUV値を渡す

マイケル
それではコードを書いていきますが、前回作ったポリゴン描画のコードを修正する形で進めていきます。
DirectX12の基本についても含めて、前回の記事をご参照下さい!
DirectX12の基本についても含めて、前回の記事をご参照下さい!

エレキベア
ポリゴン描画しただけの状態からテクスチャ描画まで進めていくクマね

マイケル
テクスチャを描画する前に、まずはUV値を渡せるようシェーダー周りを修正していきましょう!
シェーダーの修正

マイケル
頂点シェーダー、ピクセルシェーダーで受け渡すパラメータとして構造体を使用するため、hlsliファイルを追加します。
今回はBasicType.hlsliとして作成し、頂点座標とUV値をまとめたものを構造体として定義しました。
今回はBasicType.hlsliとして作成し、頂点座標とUV値をまとめたものを構造体として定義しました。

//頂点シェーダ→ピクセルシェーダへのやり取りに使用する
//構造体
struct BasicType {
float4 position : SV_POSITION; // 頂点座標
float2 uv : TEXCOORD; // UV値
};

エレキベア
この情報をシェーダー間で受け渡しするクマね

マイケル
頂点シェーダー、ピクセルシェーダーは下記のように修正します。
ピクセルシェーダーに関しては、一旦UV値をそのまま色として出力するようにしています。
ピクセルシェーダーに関しては、一旦UV値をそのまま色として出力するようにしています。
#include"BasicType.hlsli"
BasicType BasicVS(float4 pos : POSITION, float2 uv : TEXCOORD)
{
BasicType output;
output.position = pos;
output.uv = uv;
return output;
}
#include"BasicType.hlsli"
float4 BasicPS(BasicType input) : SV_TARGET
{
return float4(input.uv, 1.0f, 1.0f);
}
↑確認のためUV値を色としてそのまま出力
エレキベア
最終的に渡したUV値が色として出力されたら成功クマね
シェーダーにUV値を渡す

マイケル
それでは実際にUV値を渡してみます。
C++側も頂点シェーダーへ渡す型定義としてVertex構造体を定義します。
C++側も頂点シェーダーへ渡す型定義としてVertex構造体を定義します。
・・・
// リソース
ComPtr<ID3D12Resource> vertexBuffer_;
D3D12_VERTEX_BUFFER_VIEW vertexBufferView_;
ComPtr<ID3D12Resource> indexBuffer_;
D3D12_INDEX_BUFFER_VIEW indexBufferView_;
struct Vertex
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT2 uv;
};
・・・
↑頂点シェーダーに渡す構造体を定義

マイケル
そして頂点座標を渡している箇所にUV値も追加します。
元々XMFLOAT3型として渡していた箇所をVertex型へ変更するのも忘れないようにしましょう。
元々XMFLOAT3型として渡していた箇所をVertex型へ変更するのも忘れないようにしましょう。
・・・
// 頂点バッファビューの生成
{
// 頂点定義
Vertex vertices[] = {
{{-0.4f,-0.7f, 0.0f}, {0.0f, 1.0f}} , //左下
{{-0.4f, 0.7f, 0.0f}, {0.0f, 0.0f}} , //左上
{{ 0.4f,-0.7f, 0.0f}, {1.0f, 1.0f}} , //右下
{{ 0.4f, 0.7f, 0.0f}, {1.0f, 0.0f}} , //右上
};
const UINT vertexBufferSize = sizeof(vertices);
auto vertexHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
auto vertexResDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
// 頂点バッファーの生成
ThrowIfFailed(device_->CreateCommittedResource(
&vertexHeapProp,
D3D12_HEAP_FLAG_NONE,
&vertexResDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(vertexBuffer_.ReleaseAndGetAddressOf())));
// 頂点情報のコピー
Vertex* vertexMap = nullptr;
ThrowIfFailed(vertexBuffer_->Map(0, nullptr, (void**)&vertexMap));
std::copy(std::begin(vertices), std::end(vertices), vertexMap);
vertexBuffer_->Unmap(0, nullptr);
// 頂点バッファービューの生成
vertexBufferView_.BufferLocation = vertexBuffer_->GetGPUVirtualAddress();
vertexBufferView_.SizeInBytes = vertexBufferSize;
vertexBufferView_.StrideInBytes = sizeof(Vertex);
}
・・・
↑UV値を渡すよう修正

マイケル
後は今回、構造体ファイルをシェーダーファイル内でincludeするようになったため、
D3D_COMPILE_STANDARD_FILE_INCLUDE フラグも指定するようにすれば完了です!
D3D_COMPILE_STANDARD_FILE_INCLUDE フラグも指定するようにすれば完了です!
・・・
// シェーダーオブジェクトの生成
#if defined(_DEBUG)
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif
ComPtr<ID3DBlob> vsBlob;
ComPtr<ID3DBlob> psBlob;
D3DCompileFromFile(L"BasicVertexShader.hlsl", nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "BasicVS", "vs_5_0", compileFlags, 0, &vsBlob, nullptr);
D3DCompileFromFile(L"BasicPixelShader.hlsl", nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "BasicPS", "ps_5_0", compileFlags, 0, &psBlob, nullptr);
・・・
↑D3D_COMPILE_STANDARD_FILE_INCLUDEフラグを設定する

エレキベア
ここまでは簡単クマね
頂点レイアウトの修正

マイケル
最後に、パイプラインステートに渡している頂点レイアウトにもUV値を追加しておきます。
TEXCOORDがUVを表すセマンティクスになります。
TEXCOORDがUVを表すセマンティクスになります。
・・・
// 頂点レイアウトの生成
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
・・・
↑頂点レイアウトにも追加
実行!

マイケル
この状態で実行すると、下記のように左上が青(0,0,1,1)、右下が白(1,1,1,1)で表示されることが確認できるはずです!


エレキベア
これだけでも結構綺麗クマ〜〜〜
コードで書いたテクスチャを描画する

マイケル
次に、コードで仮のテクスチャを定義して描画してみます。
前回軽く紹介した通り、テクスチャ等の情報をCPUとGPU間でやり取りする場合にはディスクリプタテーブルを使用してレジスタを関連付ける必要があります。
この辺りが少し複雑なので注意しましょう!
前回軽く紹介した通り、テクスチャ等の情報をCPUとGPU間でやり取りする場合にはディスクリプタテーブルを使用してレジスタを関連付ける必要があります。
この辺りが少し複雑なので注意しましょう!
ダミーのテクスチャ

マイケル
ダミーのテクスチャは下記にように定義しておきます。
格子状に白黒が描画されるテクスチャになります。
格子状に白黒が描画されるテクスチャになります。
・・・
std::vector<UINT8> DXApplication::GenerateDummyTextureData()
{
const UINT rowPitch = kDummyTextureWidth * kDummyTexturePixelSize;
const UINT cellPitch = rowPitch >> 3;
const UINT cellHeight = kDummyTextureWidth >> 3;
const UINT textureSize = rowPitch * kDummyTextureHeight;
std::vector<UINT8> data(textureSize);
UINT8* pData = &data[0];
for (UINT n = 0; n < textureSize; n += kDummyTexturePixelSize)
{
UINT x = n % rowPitch;
UINT y = n / rowPitch;
UINT i = x / cellPitch;
UINT j = y / cellHeight;
// Sample: 格子状
if (i % 2 == j % 2)
{
pData[n] = 0x00;
pData[n + 1] = 0x00;
pData[n + 2] = 0x00;
pData[n + 3] = 0xff;
}
else
{
pData[n] = 0xff;
pData[n + 1] = 0xff;
pData[n + 2] = 0xff;
pData[n + 3] = 0xff;
}
}
・・・
↑格子状のテクスチャをコードで書いたもの
・・・
// ダミーのテクスチャ情報
static const UINT kDummyTextureWidth = 256;
static const UINT kDummyTextureHeight = 256;
static const UINT kDummyTexturePixelSize = 4;
std::vector<UINT8> GenerateDummyTextureData();
・・・
↑ヘッダファイルの定義

マイケル
これは公式サンプルのHelloTextureリポジトリ内にあるコードをそのまま拝借したものになります。
ロード処理は一旦後回しにしてこのコードをテクスチャと想定して描画してみましょう!
ロード処理は一旦後回しにしてこのコードをテクスチャと想定して描画してみましょう!
GitHub – microsoft / DirectX-Graphics-Samples (HelloTexture)
↑公式のサンプル

エレキベア
公式サンプルだと画像を読み込まずにコードで描画しているクマね
ピクセルシェーダーの修正

マイケル
そしてUV値を出力していただけのピクセルシェーダーを下記のように修正します。
register(t0)、register(s0)等の部分は、登録したレジスターの0番目から情報を取得している処理になります。
register(t0)、register(s0)等の部分は、登録したレジスターの0番目から情報を取得している処理になります。
#include"BasicType.hlsli"
Texture2D g_texture : register(t0);
SamplerState g_sampler : register(s0);
float4 BasicPS(BasicType input) : SV_TARGET
{
return g_texture.Sample(g_sampler, input.uv);
}
↑レジスタからテクスチャ情報を取得して描画

エレキベア
これからこのレジスタにテクスチャの情報を登録して渡すクマね
ヘッダーファイルの修正

マイケル
ヘッダーファイルには、テクスチャバッファーとシェーダーリソースビュー(SRV)というディスクリプタヒープを追加します。
SRVはテクスチャ情報をまとめたビューで、シェーダーのレジスタとディスクリプタテーブルで紐づけることでシェーダー側から参照できるようにします。
SRVはテクスチャ情報をまとめたビューで、シェーダーのレジスタとディスクリプタテーブルで紐づけることでシェーダー側から参照できるようにします。
・・・
// パイプラインオブジェクト
ComPtr<ID3D12Device> device_;
ComPtr<ID3D12CommandAllocator> commandAllocator_;
ComPtr<ID3D12GraphicsCommandList> commandList_;
ComPtr<ID3D12CommandQueue> commandQueue_;
ComPtr<IDXGISwapChain4> swapchain_;
ComPtr<ID3D12DescriptorHeap> rtvHeap_;
ComPtr<ID3D12DescriptorHeap> srvHeap_; // ★追加
ComPtr<ID3D12Resource> renderTargets_[kFrameCount];
ComPtr<ID3D12PipelineState> pipelinestate_;
ComPtr<ID3D12RootSignature> rootsignature_;
// リソース
ComPtr<ID3D12Resource> vertexBuffer_;
D3D12_VERTEX_BUFFER_VIEW vertexBufferView_;
ComPtr<ID3D12Resource> indexBuffer_;
D3D12_INDEX_BUFFER_VIEW indexBufferView_;
ComPtr<ID3D12Resource> textureBuffer_; // ★追加
・・・
↑テクスチャバッファーとSRVを定義

エレキベア
ややこしいクマ・・・・
ディスクリプタテーブルの生成

マイケル
複雑になってきたので、改めて関連する用語についてまとめておきます。
ディスクリプタテーブルはディスクリプタヒープとシェーダーのレジスタを関連づけたもの、そしてディスクリプタテーブルをまとめるのがルートシグネチャになります。
ディスクリプタテーブルはディスクリプタヒープとシェーダーのレジスタを関連づけたもの、そしてディスクリプタテーブルをまとめるのがルートシグネチャになります。
用語 | 概要 |
---|---|
ディスクリプタ | GPUに送るリソースの仕様について示したもの。 |
ディスクリプタヒープ | ディスクリプタをまとめたもの。 |
ディスクリプタテーブル | ルートシグネチャ内で設定し、ディスクリプタヒープとシェーダーのレジスタを関連付けたもの。 |
ルートシグネチャ | ディスクリプタテーブルをまとめたもの。 |

マイケル
前回は空のルートシグネチャを作成していましたが、今回はディスクリプタテーブルを作成してルートシグネチャに設定する必要があります。
ディスクリプタテーブルの実体であるルートパラメータとサンプラーを生成し、それらを元にルートシグネチャを生成します。
ディスクリプタテーブルの実体であるルートパラメータとサンプラーを生成し、それらを元にルートシグネチャを生成します。
・・・
// ルートシグネチャの生成
{
// ルートパラメータの生成
// ディスクリプタテーブルの実体
CD3DX12_DESCRIPTOR_RANGE1 discriptorRanges[1];
discriptorRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
CD3DX12_ROOT_PARAMETER1 rootParameters[1];
rootParameters[0].InitAsDescriptorTable(1, &discriptorRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);
// サンプラーの生成
// テクスチャデータからどう色を取り出すかを決めるための設定
D3D12_STATIC_SAMPLER_DESC sampler = {};
sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
sampler.MipLODBias = 0;
sampler.MaxAnisotropy = 0;
sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
sampler.MinLOD = 0.0f;
sampler.MaxLOD = D3D12_FLOAT32_MAX;
sampler.ShaderRegister = 0;
sampler.RegisterSpace = 0;
sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
// ルートパラメータ、サンプラーからルートシグネチャを生成
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init_1_1(_countof(rootParameters), rootParameters, 1, &sampler, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> rootSignatureBlob = nullptr;
ComPtr<ID3DBlob> errorBlob = nullptr;
ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_0, &rootSignatureBlob, &errorBlob));
ThrowIfFailed(device_->CreateRootSignature(0, rootSignatureBlob->GetBufferPointer(), rootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(rootsignature_.ReleaseAndGetAddressOf())));
}
・・・
↑ルートシグネチャの生成

エレキベア
あ、頭が痛いクマ・・・
テクスチャバッファ、シェーダーリソースビューの生成

マイケル
次にテクスチャバッファ、シェーダーリソースビューの生成を行います。
まずはディスクリプタヒープの初期化処理にシェーダーリソースビューも追加しましょう。
まずはディスクリプタヒープの初期化処理にシェーダーリソースビューも追加しましょう。
・・・
// ディスクリプタヒープの初期化
{
// レンダーターゲットビュー
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = kFrameCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(device_->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(rtvHeap_.ReleaseAndGetAddressOf())));
// シェーダーリソースビュー
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 1;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(device_->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(srvHeap_.ReleaseAndGetAddressOf())));
}
・・・
↑シェーダーリソースビューをディスクリプタヒープとして初期化

マイケル
そして冒頭で載せたダミーのテクスチャを読み込み、それを元にテクスチャバッファ、シェーダーリソースビューを生成します。
・・・
// TODO ここは後々実際にテクスチャを読み込むようにする
// テクスチャのロード処理
std::vector<UINT8> texture;
D3D12_SUBRESOURCE_DATA textureData = {};
{
// ダミーテクスチャ読込
texture = GenerateDummyTextureData();
textureData.pData = &texture[0];
textureData.RowPitch = kDummyTextureWidth * kDummyTexturePixelSize;
textureData.SlicePitch = textureData.RowPitch * kDummyTextureHeight;
}
// シェーダーリソースビューの生成
{
// テクスチャバッファの生成
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.Width = kDummyTextureWidth;
textureDesc.Height = kDummyTextureHeight;
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
auto textureHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
ThrowIfFailed(device_->CreateCommittedResource(
&textureHeapProp,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(textureBuffer_.ReleaseAndGetAddressOf())));
// シェーダーリソースビューの生成
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = textureDesc.Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
device_->CreateShaderResourceView(textureBuffer_.Get(), &srvDesc, srvHeap_->GetCPUDescriptorHandleForHeapStart());
}
・・・
↑テクスチャバッファ、シェーダーリソースビューの生成

エレキベア
これでテクスチャのデータ自体は出来たクマね
テクスチャバッファの転送処理

マイケル
テクスチャデータの場合、パイプラインの外から設定されるデータのため、CPUからGPUへ転送を行う必要もあります。
アップロード用バッファの生成と、実際に転送を行う処理は下記になります。
アップロード用バッファの生成と、実際に転送を行う処理は下記になります。
・・・
// テクスチャアップロード用バッファの生成
ComPtr<ID3D12Resource> textureUploadBuffer;
{
const UINT64 textureBufferSize = GetRequiredIntermediateSize(textureBuffer_.Get(), 0, 1);
auto textureUploadHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
auto textureUploadDesc = CD3DX12_RESOURCE_DESC::Buffer(textureBufferSize);
ThrowIfFailed(device_->CreateCommittedResource(
&textureUploadHeapProp,
D3D12_HEAP_FLAG_NONE,
&textureUploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&textureUploadBuffer)));
}
// コマンドリストの生成
{
// テクスチャバッファの転送
UpdateSubresources(commandList_.Get(), textureBuffer_.Get(), textureUploadBuffer.Get(), 0, 0, 1, &textureData);
auto uploadResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(textureBuffer_.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
commandList_->ResourceBarrier(1, &uploadResourceBarrier);
// 命令のクローズ
commandList_->Close();
}
// コマンドリストの実行
{
ID3D12CommandList* commandLists[] = { commandList_.Get() };
commandQueue_->ExecuteCommandLists(1, commandLists);
}
// GPU処理の終了を待機
{
ThrowIfFailed(commandQueue_->Signal(fence_.Get(), ++fenceValue_));
if (fence_->GetCompletedValue() < fenceValue_) {
ThrowIfFailed(fence_->SetEventOnCompletion(fenceValue_, fenceEvent_));
WaitForSingleObject(fenceEvent_, INFINITE);
}
}
・・・

マイケル
描画処理とは別に、ここでもコマンドリストに転送処理を格納して実行しています。
GPU側の処理になるため、フェンスでの待機処理を入れるのも忘れないようにしましょう!
GPU側の処理になるため、フェンスでの待機処理を入れるのも忘れないようにしましょう!

エレキベア
これでようやくGPU側でテクスチャが使えるようになるクマね
描画処理の修正

マイケル
最後に、描画処理でディスクリプタテーブルとシェーダーリソースビューを紐づければ完了です!!
・・・
// パイプラインステートと必要なオブジェクトを設定
commandList_->SetPipelineState(pipelinestate_.Get()); // パイプラインステート
commandList_->SetGraphicsRootSignature(rootsignature_.Get()); // ルートシグネチャ
commandList_->RSSetViewports(1, &viewport_); // ビューポート
commandList_->RSSetScissorRects(1, &scissorrect_); // シザー短形
// ディスクリプタテーブル
// ルートパラメータとディスクリプタヒープを紐づける
ID3D12DescriptorHeap* ppHeaps[] = { srvHeap_.Get() };
commandList_->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
commandList_->SetGraphicsRootDescriptorTable(0, srvHeap_->GetGPUDescriptorHandleForHeapStart());
・・・

エレキベア
長かったクマ〜〜〜〜
実行!

マイケル
ここで実行して下記のように表示されれば成功です!


マイケル
ダミーのテクスチャ部分を適当に変えて模様を変えてみるのも面白いですね
・・・
// Sapmle: グラデーション
float rate = x / static_cast<float>(rowPitch);
UINT dispColor = rate * 256;
pData[n] = dispColor;
pData[n + 1] = dispColor;
pData[n + 2] = dispColor;
pData[n + 3] = 0xff;
・・・

・・・
// Sample: ノイズ
pData[n] = rand() % 256;
pData[n + 1] = rand() % 256;
pData[n + 2] = rand() % 256;
pData[n + 3] = 255;
・・・


エレキベア
コードで書いていると模様を変えるのも楽クマね
DirectXTexで読み込んだテクスチャを描画する

マイケル
さあここまで来れば後一息!
DirectXTexというライブラリを使用して実際にテクスチャを読み込んで描画させてみます!
DirectXTexというライブラリを使用して実際にテクスチャを読み込んで描画させてみます!

エレキベア
やっとクマ〜〜〜!!
DirectXTexの導入

マイケル
DirectXTexはGitHubで公開されています。
こちらをローカル環境にクローンしましょう!
こちらをローカル環境にクローンしましょう!
git clone https://github.com/microsoft/DirectXTex.git

マイケル
そして自身のVisualStudioのバージョンに合わせて DirectXTex_Desktop_XXX_Win10.sln となっているソリューションを開き、必要なプラットフォームでビルドします。

マイケル
ビルドすると [workspace]\DirectXTex\DirectXTex\Bin\Desktop_2022_Win10\x64\Debug といったフォルダの配下にlibファイルが出力されるため、こちらをincludeして使用します。

エレキベア
使用するために一度手元でビルドする必要があるのクマね

マイケル
そしてインクルードディレクトリ、ライブラリディレクトリにそれぞれ下記のパスを追加します。
・追加のインクルードディレクトリ
[workspace]\DirectXTex\DirectXTex
・追加のライブラリディレクトリ
[workspace]\DirectXTex\DirectXTex\Bin\Desktop_2022_Win10\x64\Debug
※x64、Debugビルドの場合

マイケル
[workspace]\DirectXTex\DirectXTex までの部分は環境変数として定義しておくと綺麗になるのでおすすめです。




マイケル
後は下記のようにインクルードを追加してみて、実行してエラーが出ていなければ導入完了です!
・・・
#pragma once
#include <d3d12.h>
#include <dxgi1_6.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>
#include <DirectXTex.h> // ★追加
#include <wrl.h>
#include "d3dx12.h"
#include <stdexcept>
#include <vector>
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "DirectXTex.lib") // ★追加
・・・
↑インクルードの追加

エレキベア
簡単クマ〜〜〜〜
テクスチャのロード処理の追加

マイケル
導入が完了したところで、今回は下記の適当に描いた画像をロードしてみます。


エレキベア
(マジで適当クマ・・・)

マイケル
ダミーのテクスチャを読み込んでいた箇所を下記のように修正します。
今回、画像は Assets/test_image.png というパスで格納しています。
今回、画像は Assets/test_image.png というパスで格納しています。
・・・
// テクスチャのロード処理
DirectX::TexMetadata metadata = {};
DirectX::ScratchImage scratchImg = {};
std::vector<D3D12_SUBRESOURCE_DATA> textureSubresources;
{
ThrowIfFailed(LoadFromWICFile(L"Assets/test_image.png", DirectX::WIC_FLAGS_NONE, &metadata, scratchImg));
ThrowIfFailed(PrepareUpload(device_.Get(), scratchImg.GetImages(), scratchImg.GetImageCount(), metadata, textureSubresources));
}
・・・
↑実際に画像を読み込んでみる

マイケル
そしてテクスチャバッファの生成、転送処理を読みこんだテクスチャに合わせて修正すれば完了です!
・・・
// シェーダーリソースビューの生成
{
// テクスチャバッファの生成
D3D12_RESOURCE_DESC textureDesc = {};
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.Width = static_cast<UINT>(metadata.width); // ★修正
textureDesc.Height = static_cast<UINT>(metadata.height); // ★修正
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
auto textureHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
ThrowIfFailed(device_->CreateCommittedResource(
&textureHeapProp,
D3D12_HEAP_FLAG_NONE,
&textureDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(textureBuffer_.ReleaseAndGetAddressOf())));
// シェーダーリソースビューの生成
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = textureDesc.Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
device_->CreateShaderResourceView(textureBuffer_.Get(), &srvDesc, srvHeap_->GetCPUDescriptorHandleForHeapStart());
}
・・・
// コマンドリストの生成
{
// テクスチャバッファの転送
UpdateSubresources(commandList_.Get(), textureBuffer_.Get(), textureUploadBuffer.Get(), 0, 0, textureSubresources.size(), textureSubresources.data()); // ★修正
auto uploadResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(textureBuffer_.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
commandList_->ResourceBarrier(1, &uploadResourceBarrier);
// 命令のクローズ
commandList_->Close();
}
・・・
↑テクスチャバッファの生成、転送処理を一部修正

エレキベア
差し替えは楽だったクマね

マイケル
一点注意点として、LoadFromWICFile処理ではCoInitializeExを呼び出していることを前提としているようで、下記のように事前に呼び出し処理を記述しておかないとE_NOINTERFACEエラーになるようでした。
今回はmain.cppの頭に追加しています。
今回はmain.cppの頭に追加しています。
#include "Win32Application.h"
#include "DXApplication.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
// WICファイル読込のため、COMを初期化しておく
auto result = CoInitializeEx(NULL, COINITBASE_MULTITHREADED);
if (FAILED(result)) return 1;
DXApplication dxApp(1280, 720, L"DX Sample");
Win32Application::Run(&dxApp, hInstance);
return 0;
}
↑CoInitializeExを追加

エレキベア
これは思わぬ罠クマね〜〜
実行!

マイケル
ここまで実装したら実行!!
下記のように適当な画像が描画されれば成功です!!
下記のように適当な画像が描画されれば成功です!!


エレキベア
やったクマ〜〜〜〜
くだらない画像を出すために大変だったクマ〜〜〜〜
くだらない画像を出すために大変だったクマ〜〜〜〜
おわりに

マイケル
というわけで今回はDirectXTexを用いたテクスチャ描画でした!
どうだったかな??
どうだったかな??

エレキベア
テクスチャ一つ描画するだけで大変すぎるクマ・・・
Unity使いたいクマ・・・
Unity使いたいクマ・・・

マイケル
慣れもあるのかもしれないけど、やっぱり生のDirectXとなると大変だね・・・
とりあえず3Dモデル描画までは頑張ってみよう!!
とりあえず3Dモデル描画までは頑張ってみよう!!

マイケル
というわけで今回はこの辺で!!
アデューーー!!!
アデューーー!!!

エレキベア
クマ〜〜〜〜〜
【DirextX12】第二回 DirextX12を使ったゲーム開発 〜DirectXTexを用いたテクスチャ描画〜 〜完〜
※次回の記事はこちら!
コメント