ID3D11Bufferのバリエーション
定数バッファを使う際にID3D11Bufferを使いましたが、このクラスが表すことが出来るものにはまだ他にいくつかのバリエーションがあります。
ここではそれについて見ていきます。
ちなみにID3D11Bufferのことをバッファと呼んだりします。
以後、バッファと呼んでいきますので、出てきたときはID3D11Bufferのことだと思ってください。
概要
今パートでは球体を表示するシェーダと単純なデータの受け渡しを行うシェーダの2種類を元に様々なバッファのバリエーションについて見ていきます。
対応するプロジェクトはPart04_TypeOfBufferになります。
1.球体を描画するシェーダ
まず、比較するために定数バッファを使った球体描画シェーダのソースを見てみます。
シェーダ自体は単純なもので、球体を描画をする関数に定数バッファとして宣言したパラメータを渡しているだけです。
球体を描画している部分はCommon.hlsliで定義していますが、本筋ではないので省略します。
このパートのStructuredBufferとByteAddressBufferはこのコードをベースにしています。
注目して見てもらいたい部分は定数バッファのParamの部分がどのように変わり、どのようにデータにアクセスしているかです。
2.StructuredBuffer
それではStructuredBufferについて見ていきましょう。
StructuredBufferは名前の通りCPU側の構造体をシェーダ内で直接読み込むことができるものになります。
シェーダ側
シェーダ内でも構造体を定義することができ、StructuredBufferの“<…>”には型名を指定してください。
データにアクセスするときは配列のように添え字を使ってアクセスします。
あとは、C++の構造体と同じように使います。
ドキュメント:StructuredBuffer
(日本語)
(英語)
StructuredBufferはテクスチャと同じようにシェーダリソースビューとして扱われます。
なので、スロットの指定はテクスチャと同じで”register(t0)“みたいに行います。
CPU側
定数バッファとの作成の違いはいくつか設定する項目が増えただけです。
ドキュメント: D3D11_BUFFER_DESC(日本語) (英語)
また、GPUに設定するときはシェーダリソースビューとして扱うためBindFlagsにD3D11_BIND_SHADER_RESOURCEを設定する必要があります。
次にシェーダリソースビューを生成する際は、ViewDimensionにD3D11_SRV_DIMENSION_BUFFERかD3D11_SRV_DIMENSION_BUFFEREXを設定する必要があります。
各々設定したときのパラメータについてはサンプルを参考にしてください。
データを読み取り始めるオフセットと個数を設定する必要があります。
また、Formatには必ず、DXGI_FORMAT_UNKNOWNを設定する必要があります。
効率的なStructuredBufferの使い方はnVidia GameWorksのブログにて解説されています。
日本語訳もあった気がするのですが、見つからなかったので元記事のリンクを張っておきます。
端的に言うと構造体のサイズを16byteの倍数にすると効率よくデータにアクセスできるようです。
Understanding Structured Buffer Performance
Redundancy and Latency in Structured Buffer Use
How About Constant Buffers?
3.ByteAddressBuffer
シェーダ側
次に、ByteAddressBufferについて見ていきます。ByteAddressBufferは4バイト単位でデータを読み込むことが出来るものになります。
ByteAddressBufferはuintしか読み込むことしかできません。
なので、floatなど別の型を読み込みたい場合はasfloat関数などを利用する必要があります。
また、複数のuintを一度に読み込むことが出来る関数も用意されています。
構造体のようにデータを読み込むにはこちらで並びを意識する必要があるので手間がかかるものになります。
基本的にはStructuredBufferを使った方が手軽なのですが、頂点バッファというグラフィックスパイプラインで使うバッファをシェーダリソースビューとして扱う際はこれを使う必要が出てきます。
こちらのサイトでもByteAddressBufferを扱っているので参考にしてください。
ドキュメント:
ByteAddressBuffer
(日本語)
(英語)
asfloat
(日本語)
(英語)
CPU側
定数バッファとの違いは、MiscFlagsにD3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWSを設定する必要があるだけです。
また、シェーダリソースビューを生成する場合、FormatにはDXGI_FORMAT_R32_TYPELESSを設定する必要があります。
ViewDimensionにはD3D11_SRV_DIMENSION_BUFFEREXを指定し、BufferEx.FlagsにD3D11_BUFFEREX_SRV_FLAG_RAWを設定してください。
4.スタック操作を行うバッファ
DX11からシェーダ内でスタック操作を行うためにAppendStructuredBufferとConsumeStructuredBufferが追加されました。
ここではそれらについて見ていきます。
なお、スタックへのプッシュとポップは同時にはできませんので、別のシェーダをそれぞれ行ってください。
プッシュ操作
ポップ操作
上2つのシェーダがやっていることはとても単純です。
GPU上で用意したデータをスタックに積んで、別のバッファに格納しているだけです。
ただGPU上では処理が並列に実行されているため、データの積まれる順番がどうなるかまでは制御できません。
なので、正しい順序が必要となる場合での使用は避けた方がいいでしょう。
スタック操作とは関係ありませんが、ポップ操作で使用してるRWStructuredBufferは書き込みができるStructuredBufferになります。
ドキュメント:
AppendStructuredBuffer
(日本語)
(英語)
ConsumeStructuredBuffer
(日本語)
(英語)
RWStructuredBuffer
(日本語)
(英語)
CPU側
AppendStructuredBufferとConsumeStructuredBufferはStructuredBufferと同じ設定でバッファを作成します。
ビューはアンオーダードアクセスビューになり、FormatにはDXGI_FORMAT_UNKNOWNを指定し、
Buffer.FlagsにD3D11_BUFFER_UAV_FLAG_APPENDを指定してください。
後はビューは異なりますがStructuredBufferと同じ要領で設定します。
注意点として、シェーダ内でのスタック操作は可能ですが動的にメモリを確保するわけではありません。
作成したときに確保したメモリ量が上限となり、それを超えてプッシュしても追加されませんので注意してください。
スタック操作については以上になります。
サンプルコードのScene::runStackBuffer関数ではGPUからCPUへのデータの転送を行うコードもあるので一度目を通してください。
まとめ
今回はバッファのバリエーションについて見てきました。
ここで上げたもの以外にもまだありますが、似たような内容になるので省略します。
次回からは本題ともいえるグラフィックスパイプラインについて見ていきます。
グラフィックスパイプラインを使うとモデルなど三角形で表現されたものを自由に画面に描画できるようになりますが、
いろいろな決まりごとや裏で行っていることがあるので少しずつ説明していきたいと思います。
補足
レイトレースとラスタライズ法
今回のサンプルでは球体を描画するのに各画面のピクセルからカメラの方向に合ったレイを飛ばして球体と当たっていたら球体を描画するといった手順を踏んでいます。
このレイを飛ばして画面を描画する手法はレイトレースと呼ばれています。
レイトレースは映画やCGの研究などで使わており非常にリアルな絵を描画することができる手法です。
ただ、それ相応に重たい処理でもあるので、ゲームなどリアルタイムに描画する必要がある場合はラスタライズ法と呼ばれる手法が使われています。
ラスタライズ法では三角形や線分などのプリミティブ(英訳:Primitive)を使って物体を表現しており、複雑な物体を描画しようとするとそれだけ大量の三角形を描画する必要が出てきます。
GPUはこのラスタライズ法を高速に処理するための、つまり大量のプリミティブを高速に処理するために作られたハードウェアといえます。
これまで言葉だけが何回か出てきたグラフィックスパイプラインはこのラスタライズ法を行うための工程みたいなもので、ここまでの内容を踏まえますと、プリミティブを画面に描画するための工程だと言い換えることが出来るでしょう。
あと、今回のものはかなり粗末なものですが一応レイトレースと呼んでも差し支えないものになっていると思います。
レイトレースについては詳しくは知らないのですが、レイマーチングと呼ばれる手法を使うと手軽にレイトレースできるそうなので興味がある人は調べてみて下さい。
これがGPUの力!Three.jsによる“リアルタイム”なレイトレーシング
また、レイマーチングを使ったデモシーンが投稿されているサイトもあるようです。
shadertoy
このサイトではGLSLと呼ばれるシェーダ言語を使ったデモシーンを投稿できるサイトになります。
GLSLは文法自体はHLSLと似ているのでそこまで問題にはならないでしょう。
またレイマーチングをする際よく参考にされているサイトもあるので一度目を通してみてください。