5-4. 頂点入力デスクリプション

この節では頂点入力デスクリプションというものについて解説します。

前回で頂点データをバッファに書き込みました。しかしこのままではバッファがどういう形式で頂点のデータを持っているのか、Vulkanのシステムの側には分かりません。Vertex構造体という勝手に定義してみただけの構造体のことは知りようがないのです。このままではシェーダに読み込ませられません。

そこで頂点入力デスクリプションというものを用意します。適切な説明(デスクリプション)を与えることで「こういう形式でこういうデータが入っている」という情報を与えることができます。データだけでなくデスクリプションを与えて初めてシェーダからデータを読み込めるのです。

データだけでは意味不明

デスクリプションはパイプライン作成時の情報に含めることになります。さっそく準備していきましょう。要するに初期化用構造体の一種でしかありません。

アトリビュートとバインディング

頂点入力デスクリプションには2種類あります。アトリビュートデスクリプション(vk::VertexInputAttributeDescription)とバインディングデスクリプション(vk::VertexInputBindingDescription)です。これはひとえに、シェーダに与えるデータを分ける単位が「バインディング」と「アトリビュート」の2段階あるためです。少しこの辺について説明しましょう。

バインディングが一番上の単位です。基本的に1つのバインディングに対して1つのバッファが結び付けられます。あるバインディングに含まれるデータは頂点ごとに切り分けられ、各頂点のデータとして解釈されます。

アトリビュートはより細かい単位になります。バインディングから切り出された一つの頂点のデータは、複数のアトリビュートとして分けられます。一つの頂点データは「座標」とか「色」みたいな複数のデータを内包していますが、この1つ1つのデータが1つのアトリビュートに相当することになります。

(注意点として、1つのアトリビュートは「多次元ベクトル」や「行列」だったりできるので、アトリビュートは「x座標」とか「y座標」みたいな単位よりはもう少し大きい単位になります。)

大雑把な構造

以上を踏まえて説明をします。

バインディングデスクリプションでは、「1つのバッファをどうやって各頂点のデータに分割するか」という情報を設定します。具体的には、1つの頂点データのバイト数とかを設定します。

そしてアトリビュートデスクリプションでは、「1つの頂点データのどの位置からどういう形式でデータを取り出すか」という情報を設定します。

アトリビュートデスクリプションはアトリビュートの数だけ作成します。1つの頂点データが3種類のデータを含んでいるならば、3つのアトリビュートデスクリプションを作らなければなりません。今回のデータだと含んでいるデータは「座標」だけなので1つだけになりますが、複数のアトリビュートを含む場合はその数だけ作成します。


頂点入力バインディングデスクリプション

まずはvk::VertexInputBindingDescriptionを用意しましょう。

vk::VertexInputBindingDescription vertexBindingDescription[1];
vertexBindingDescription[0].binding = 0;
vertexBindingDescription[0].stride = sizeof(Vertex);
vertexBindingDescription[0].inputRate = vk::VertexInputRate::eVertex;

(要素数1の配列にしていますが、単に後々ちょっと説明しやすくするためというだけです。あまり気にしないでください)

bindingは説明の対象となるバインディングの番号です。各頂点入力バインディングには0から始まる番号が振ってあります。

strideは1つのデータを取り出す際の刻み幅です。つまり上の図でいう所の「1つの頂点データ」の大きさですね。ここではVertex構造体のバイト数を指定しています。前節までで用意したデータはVertex構造体が並んでいるわけなので、各データを取り出すには当然Vertex構造体のバイト数ずつずらして読み取ることになります。

inputRateにはvk::VertexInputRate::eVertexを指定します。これについてはここでは詳しく述べません。インスタンス化というものを行う場合に別の値を指定します。

バインディングデスクリプションはこれだけです。次に移りましょう。


頂点入力アトリビュートデスクリプション

今度はvk::VertexInputAttributeDescriptionを用意します。今回用意した頂点データが含む情報は「座標」だけなので、用意するアトリビュートデスクリプションは1つだけです。

vk::VertexInputAttributeDescription vertexInputDescription[1];
vertexInputDescription[0].binding = 0;
vertexInputDescription[0].location = 0;
vertexInputDescription[0].format = vk::Format::eR32G32Sfloat;
vertexInputDescription[0].offset = 0;

bindingはデータ元のバインディングの番号を指定します。先ほども説明したようにバインディングは複数あり(今回用意するのは1つだけですが)、それぞれ0から始まる番号が振ってあります。上で用意したバインディングは0番なので、ここでも0を指定します。

locationはシェーダにデータを渡す際のデータ位置です。

ここでちょっと5-1. 頂点データの用意に書いたシェーダのコードを見てみましょう。以下のようなコードがあると思います。

layout(location = 0) in vec2 inPos;

「location = 0」という部分がありますね。シェーダがアトリビュートのデータを受け取る際はこのようにデータ位置を指定します。このlayout(location)の指定とアトリビュートデスクリプションのlocationの位置は対応付けて書かなければなりません。

locationの詳細は後々別の章で説明します。

formatはデータ形式です。今回渡すのは32bitのfloat型が2つある2次元ベクトルなので、それを表すeR32G32Sfloatを指定します。ここで使われているvk::Formatは本来ピクセルの色データのフォーマットを表すものなのでRとかGとか入っていますが、それらはここでは無視してください。

「32bit, 32bit, それぞれSigned(符号付)floatである」という情報が読み取れれば十分です。

ちなみにfloat型の3次元ベクトルを渡す際にはeR32G32B32Sfloatとか指定します。ここでもRGBの文字に深い意味はありません。色のデータを渡すにしろ座標データを渡すにしろこういう名前の値を指定します。違和感があるかもしれませんが、こういうものなので仕方ないです。

offsetは頂点データのどの位置からデータを取り出すかを示す値です。今回は1つしかアトリビュートが無いので0を指定していますが、複数のアトリビュートがある場合にはとても重要なものです。


2種類の頂点入力デスクリプションを作成したら、それをパイプラインに設定します。

頂点入力デスクリプションはvk::PipelineVertexInputStateCreateInfo構造体に設定します。パイプライン作成処理を以下のように書き換えましょう。

vk::PipelineVertexInputStateCreateInfo vertexInputInfo;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = vertexBindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 1;
vertexInputInfo.pVertexAttributeDescriptions = vertexInputDescription;

(中略)

pipelineCreateInfo.pVertexInputState = &vertexInputInfo;

これでバッファを渡す準備は出来ました。


この節では頂点入力デスクリプションを設定しました。次節ではいよいよバッファを用いてポリゴン描画を行います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です