007.頂点の変更とテクスチャラッピング(Dx12版)
このサンプルは
SimplSample007というディレクトリに含まれます。
BaseCrossDx12.slnというソリューションを開くと
Dx12版が起動します。
実行結果は以下のような画面が出ます。
図0007a
動画は以下になります。
【サンプルのポイント】
今項のサンプルのポイントは
同じ描画を複数のオブジェクトで行う場合のサンプルです。例えばテクスチャやシェーダに渡すパラメータのみが違う、別のオブジェクトの場合、描画処理は1つのクラスで行ったほうが、同じようなコードをいくつも書く必要がなくなります。
また、
頂点の変更の方法も今項のテーマと言えます。
ポジションを動的に変更する場合もありますが、一番ポピュラーなのは
テクスチャUV値の変更でしょう。上記動画を見ればわかる通り、中心にあるオブジェクトは、テクスチャが流れるように描画されます。これは
動的にテクスチャUV値の変更を行っています。
【共通解説】
Dx12、Dx11両方に共通なのは
シェーダーです。
DxSharedプロジェクト内に
シェーダファイルというフィルタがあり、そこに記述されてます。
今回使用するシェーダは
頂点シェーダとピクセルシェーダです。
VertexPositionTexture型の頂点を持ち、
コンスタントバッファからの入力で、位置を変更させています。
またテクスチャも描画します。
更新処理は動きは同じですが、Dx12版の
更新処理で説明します。
OnUpdate()関数には、それぞれの四角形を個別に更新する方法が記述されています。
【Dx12版解説】
BaseCrossDx12.slnを開くと、
BaseCrossDx12というメインプロジェクトがあります。この中の
Character.h/cppが主な記述個所になります。
■初期化■
今項で表示するオブジェクトは2種類です。すなわち、奥の壁になってるオブジェクトと、中心にあるテクスチャが流れるオブジェクトです。
前者は
WallSpriteクラスです。後者は
SquareSpriteクラスです。どちらも、
頂点とテクスチャUVを持つ
VertexPositionTexture型の頂点を持っています。コンスタントバッファも同じ型です。
描画方法が同じなので、
描画クラスを別に作成して各オブジェクトに、メンバとしてインスタンスを持たせます。そのクラスが
PTSpriteDrawクラスということになります。
Dx12版の初期化は
頂点の作成と
DX12リソースの初期化が主な仕事です。前者は各オブジェクトで行い、後者は描画クラスがあるので、そちらのほうで初期化を行います。
頂点の初期化処理は、
WallSpriteクラスは以下のようになってます。
float HelfSize = 0.5f;
//頂点配列(縦横10個ずつ表示)
vector<VertexPositionTexture> vertices = {
{ VertexPositionTexture(Vec3(-HelfSize, HelfSize, 0), Vec2(0.0f, 0.0f)) },
{ VertexPositionTexture(Vec3(HelfSize, HelfSize, 0), Vec2(10, 0.0f)) },
{ VertexPositionTexture(Vec3(-HelfSize, -HelfSize, 0), Vec2(0.0f, 10)) },
{ VertexPositionTexture(Vec3(HelfSize, -HelfSize, 0), Vec2(10, 10)) },
};
//インデックス配列
vector<uint16_t> indices = { 0, 1, 2, 1, 3, 2 };
//メッシュの作成(変更できない)
m_SquareMesh = MeshResource::CreateMeshResource(vertices, indices, false);
ここでテクスチャUV値の設定である、赤になっている部分を見てみましょう。この記述で
横に10個、縦に10個テクスチャを連続して張り付ける設定になります。
SquareSpriteクラスの頂点は以下になります。
float HelfSize = 0.5f;
//頂点配列
m_BackupVertices = {
{ VertexPositionTexture(Vec3(-HelfSize, HelfSize, 0), Vec2(0.0f, 0.0f)) },
{ VertexPositionTexture(Vec3(HelfSize, HelfSize, 0), Vec2(4.0f, 0.0f)) },
{ VertexPositionTexture(Vec3(-HelfSize, -HelfSize, 0), Vec2(0.0f, 1.0f)) },
{ VertexPositionTexture(Vec3(HelfSize, -HelfSize, 0), Vec2(4.0f, 1.0f)) },
};
//インデックス配列
vector<uint16_t> indices = { 0, 1, 2, 1, 3, 2 };
//メッシュの作成(変更できる)
m_SquareMesh = MeshResource::CreateMeshResource(m_BackupVertices, indices, true);
この頂点は
WallSpriteクラスとは少し違います。まず
テクスチャUV値(初期値)ですが、
縦は1.0横は4.0に並ぶように作成します。また、頂点を変更するので
頂点のバックアップを作成しておく必要があります。ですので、ローカル上に頂点の配列を作成するのではなく、
m_BackupVerticesというメンバ変数に作成します。インデックスのほうは変更しないので、そのままローカル上で問題ありません。
また、メッシュリソース作成時に
頂点変更できるというフラグを設定して作成します。
■ルートシグネチャ作成■
ルートシグネチャは
PTSpriteDraw::CreateRootSignature()関数で作成します。前項と同じ内容です。
■デスクプリタヒープ作成■
PTSpriteDraw::CreateDescriptorHeap()関数で作成します。前項と同じ内容です。
■サンプラー作成■
サンプラーは
PTSpriteDraw::CreateSampler()関数で作成します。今項は
ラッピング(包み込む)設定で行います。以下がその設定です。
void PTSpriteDraw::CreateSampler() {
auto SamplerDescriptorHandle = m_SamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
//ラッピングサンプラー
DynamicSampler::CreateSampler(SamplerState::LinearWrap, SamplerDescriptorHandle);
}
■シェーダーリソースビュー作成■
PTSpriteDraw::CreateShaderResourceView()関数で行います。前項と同じです。
PTSpriteDrawクラスにはあらかじめテクスチャリソースを初期化しておく必要があります。それは
PTSpriteDraw::OnCreate()関数で
Dx12リソース作成前に作成してます。
■コンスタントバッファ作成■
PTSpriteDraw::CreateConstantBuffer()関数で行います。前項と同じです。
■パイプラインステート作成■
PTSpriteDraw::CreatePipelineState()関数で行います。前項と同じです。
br />
■コマンドリスト作成■
PTSpriteDraw::CreateCommandList()関数です。デフォルト処理です。
■コンスタントバッファの更新■
PTSpriteDraw::UpdateConstantBuffer()関数です。パラメータに
DiffuseSpriteConstantBuffer& CBuffを渡すようになってます。
■更新処理■
このサンプルの更新処理は
頂点の変更です。
SquareSpriteクラスのみ頂点を変更します。実装は
SquareSprite::UpdateVertex()関数で行います。
void SquareSprite::UpdateVertex(float ElapsedTime) {
m_TotalTime += ElapsedTime;
if (m_TotalTime > 1.0f) {
m_TotalTime = 0;
}
auto Dev = App::GetApp()->GetDeviceResources();
//頂点の変更
vector<VertexPositionTexture> new_vertices;
for (size_t i = 0; i < m_SquareMesh->GetNumVertices(); i++) {
Vector2 UV = m_BackupVertices[i].textureCoordinate;
if (UV.x == 0.0f) {
UV.x = m_TotalTime;
}
else if (UV.x == 4.0f) {
UV.x += m_TotalTime;
}
VertexPositionTexture v = VertexPositionTexture(
m_BackupVertices[i].position,
UV
);
new_vertices.push_back(v);
}
//メッシュの頂点の変更
m_SquareMesh->UpdateVirtex(new_vertices);
}
赤になっているところが頂点の変更です。バックアップの頂点と、変更箇所(UV値)を合わせて、
new_vertices配列を作成します。最後に
m_SquareMesh->UpdateVirtex()関数に渡します。
■描画処理■
描画は
PTSpriteDraw::DrawObject()関数で行います。この関数は
メッシュリソースを引数に持ちます。コンスタントバッファの更新はその前に行っておきます。以下が今回の注目点です。
void PTSpriteDraw::DrawObject(const shared_ptr<MeshResource>& Mesh) {
//中略
Mesh->UpdateResources<VertexPositionTexture>(m_CommandList);
//中略
}
ここでは、メッシュの
UpdateResources()関数を呼び出しています。ここの関数は、更新時に呼び出した
UpdateVirtex()関数とは違います。メッシュリソースとテクスチャリソースは動的に中身を変更できるわけですが、その変更と、描画直前に呼び出す
UpdateResources()関数は別物です。前者はコマンドリストがなくても実行できます。つまりメッシュリソースの内部に保持してあるバッファを更新するだけです。しかし、
UpdateResources()関数はそのバッファとシェーダに渡すGPU向けリソースを結びつけます。そのため、描画直前に、メッシュと(必要であればテクスチャも)
UpdateResources()関数を呼ぶ必要があります。
以上、Dx12側の説明は終わりです。
【まとめ】
今回は
同じシェーダで描画できるオブジェクトを一つの描画クラスで描画する方法をサンプル化しました。
これは
フルバージョンにおける
描画コンポーネントと同じような動きになります。
Dx11にせよDx12にせよ描画処理は、GPUに対していろんな設定(パイプライン設定)を行わなければいけないので、各オブジェクトごとに描画をしていたのでは同じようなコードをあちこちに書かなければならないので大変です。バグのもとにもなります。
ですので
同じシェーダは同じ描画という設計にしたほうが、シンプルになると思います。