概要
3Dモデルにボーンを入れて動かす事を最優先の目的としている。
こちらに興味がある方は、迷うことはありません。 Unity を活用しましょう。 間違いなく最適解です。
このページに書き留めた事々は、Unity やUnrealEngineを利用することで、貴方にとってデバッグ時点ですら不要となる筈です。
このページでは、VulkanとDirect3D12を用いて、簡単なレンダリング関数群を実装した時に理解した事々を連ねております。
予備知識
読み進める上で必要な事を連ねております。
描画パイプラインのイメージ
図を作成するのは面倒臭いので省略します。
簡単に言えば、描画パイプラインとは、並列演算とハードウェア実装による支援を実現することに最適化したプロシージャと考えて、大きな誤りはないと考えております。
- Vulkan, Direct3D12における用語としての(描画)パイプラインとは、固定機能ステージ(入力アセンブラ、ラスタライザ、など)
- 描画パイプラインの中核機能は「ラスタライザ」と「各シェーダ」
- シェーダは、テクスチャと呼ばれる「多次元メモリ」を参照可能
- 「多次元」メモリは、GPU 上に確保され、目的別にいくつかに分類される
- 描画ターゲット(個人的には、画素バッファ[8]と深度バッファ[1]。 最近は、画素バッファと呼ばれる場所に、画素(=色)以外を含める)
- シェーダリソース(主にピクセルシェーダが参照するテクスチャ。 法線マップ、深度バッファシャドウに代表されるように、最近は、画素以外も含める)
- 定数バッファ(行列(ボーン[n]、ワールド、ビュー、射影)とシェーダパラメータを格納。 個人的に、最近の実装だとあんまり行列を利用していない)
- 頂点シェーダは、頂点配列、索引配列を入力レイアウトの定義に基づいてフェッチ(=入力)
- 入力アセンブラは、並列に頂点シェーダスレッドをアクティブにして、頂点配列の要素を各頂点シェーダに与える
- 画素シェーダ(=ピクセルシェーダ、フラグメントシェーダ)は、大抵の場合、なんらかのテクスチャを参照
- 画素シェーダは、複数の描画ターゲットに要素を出力可能(Defferd Rendering, Shadow Mapping[3])
- 幾何シェーダ(=ジオメトリシェーダ)は、ストリーム出力以外には、あんまり使わない感触(AndroidのVulkanだと未実装がある様子)
主文(メモ)
ディスクリプタ(記述子)ってなんぞ?
シェーダがGPU リソースにアクセスする際に間接参照するポインタテーブル。
シェーダで迷わない為に必要な前提理解として「明示しない限り、若い番号から昇順にレジスタを割り当てる」点があります。
Vulkan(GLSL)だとLayoutの宣言でSet, Bindingがレジスタ番号に当たり、HlSLだと「t0, b0, s0」がレジスタ番号に相当するようです。
ですが、API 仕様とシェーダ言語を見る限り、レジスタというよりも「ディスクリプタ ヒープ テーブル」の要素番号に近い感触があります。
実際のハードウェア実装では、間違いなくレジスタにロードされるのは疑いありませんが、プログラムレベルでイメージを掴むなら、この感覚の方が理解しやすいと考えます。
何故、レジスタ番号の話をするのか?と言えば、Vulkanの場合とDirect3Dでは、全く異なる指定方法を取り、この設計に留意しておかないと、まともにシェーダが掛けません。
そして、このページで記述したい点も、この部分です。
さて、シェーダと描画パイプラインは、何を参照するのでしょうか?
予備知識で簡単に書きましたが、描画ターゲット(RTV)、深度ステンシル(DSV)、索引配列、頂点配列、シェーダリソース(SRV:テクスチャ)、定数バッファ(CBV)です。
ディスクリプタというのは、これらを参照させるためのポインタの役割を持つと理解して、間違いないと考えます。
これらの理解をオーソライズする大学教授のダメだし(添削)が欲しい所ですが、無料の情報ですから、その辺りの担保は諦めてください。
自分の実験結果からは、この理解で大きな誤りはない(※漏れがないとは言ってない)と考えております。
いずれにしろ、大事な事は、ただのポインタテーブルと捉えて、怖がらない、忌避しない、穢れみたいに排斥せず、受け入れる事が肝要です。
ディスクリプタって美味しいの?
美味しいです。 かなり美味しいと思います。 このディスクリプタ(たぶん日本語だと記述子)の概念が導入された理由は、一言で言えば、これだと考えております。
固定機能ステートと描画パイプラインを使い回すな。 描画パイプラインが並列化しずらいんじゃ! 並列化時代にメモリ節約思想は、速度性能を落とす
じゃないかと。 VulkanとDirect3D12(下敷きのMantle)の設計思想は、排他制御(競合)の排除を目指したものと考えております。
ディスクリプタの目的
シェーダと描画パイプラインが並列に実行する為に必要な情報とは、何か?という観点に直結していると考えます。
もっとざっくり言えば「シェーダと描画パイプラインは、何を入力して、何を参照して、何を出力するの?」という意味です。
- 入力アセンブラ
- 索引配列(頂点配列の要素番号を持ち、頂点配列に含める頂点を圧縮する目的の配列) → 頂点情報を共有すると、画素、UV座標を区別できないから、最近は、殆ど同一頂点座標を共有なんてしていないと思われる
- 頂点配列(頂点座標、頂点重み、ボーン行列索引番号、法線ベクトル、従法線ベクトル、接線ベクトルなど)を収めた頂点情報の配列
- シェーダリソース
- 定数バッファ(BWVP行列、シェーダパラメータを収める構造体定数) → シェーダから観て「参照のみ」のアクセスになるから「定数」という意味で、プロセッサからは比較的自由に数値を更新できる
- テクスチャ(貴方の意思と設計で多次元に編成できるGPU メモリ。 直感的には、二次元の平面テクスチャを連想しやすい) → よっぽどの事がない限り、キューブマップを選択する設計にはしないと思われる。 無意味、無参照な要素が多すぎる嫌いがあるから
- サンプラ(テクスチャをフェッチする際にフィルタちっくに動作するサンプラの挙動を決定するためのパラメータ定数) → 「Vulkan Tutorial」によれば、もう今時だとサンプラを使わないでテクスチャを参照してグラフィクスを描画することはないと言われているらしい
- 描画パイプライン出力
- 描画ターゲット(主に「色(=画素)」を出力するメモリ。 最近のレイトレーシング(DXR?)や遅延シェーディング(Differed Shading)だと色以外も出力) → 深度バッファシャドウの「深度値の計測」という考え方を準用して、光線の反射強度(レイディアンス?とかなんとか)?なんかを書き込む?と思われる。
- 深度ステンシル(正規化デバイス座標系における「Z座標」を書き込むメモリ。 ステンシルは使った事がないから知らない) → 最近は、普通に活用されていて、射影行列を三つ準備して、深度を三分割するとか。
→ もっと言えば「プリミティヴトポロジでTriangleList以外を使ってなんていない」と思われる
→ 実質的には、GPU メモリの参照範囲をページ内に集約して、フェッチされたキャッシュページのヒット率を高める効果が趣旨?
→ 高頻度に数値を変更するとフレームレートを落とし、ファンを高回転させる要因になりやすい
→ でも「計算シェーダ」であれば、サンプラは誤差を生むので、サンプラを通すとは考えにくい
→ 「瞬間をレンダリング」するシェーダという発想から来た概念(?)と思われる
→ なお、三つの要素を書き込むから、こっちではなく「描画ターゲット」の[0]以外のターゲットに三分割した「深度値」を出力する筈
→ 次のDirect3D13なんかでは、この「深度/ステンシル」っていう概念も無くなるんじゃなかろうか…? ぱっと見で<レガシー>に思える
VulkanとDirect3D12におけるディスクリプタに関する違い
まず関係する要素を列挙します。
- Vulkan
- ディスクリプタセットレイアウト(VkDescriptorSetLayout)
- ディスクリプタプール(VkDescriptorPool)
- ディスクリプタセット(VkDescriptorSet)
- Direct3D12
- ルートシグネチャ(RootSignature)
- ルートシグネチャ内のパラメータテーブル(ParameterTable)
- ディスクリプタテーブル(DescriptorTable)
- ディスクリプタヒープ(DescriptorHeap)
役割 | Vulkan | Direct3D12 |
---|---|---|
シェーダリソースとレジスタの紐付け | ディスクリプタセットレイアウト(VkDescriptorSetLayoutBinding) | ディスクリプタテーブル(RootSignature.ParameterTable=DESCRIPTOR_TABLE) |
シェーダが利用するレジスタ数 | ディスクリプタプールサイズ(VkDescriptorPoolSize) | (たぶん、ない。 強いて言えば「D3D12_DESCRIPTOR_RANGE」だけど、これはレジスタ番号を決定することが趣旨) |
シェーダが参照するポインタを収める実体 | ディスクリプタセット(VkDescriptorSet) | ディスクリプタヒープ(DescriptorHeap) |
シェーダのヘッダ構造体 | N/A(D3D12に存在するのは、使いやすくするための工夫) | ルートシグネチャ(RooSignature) |
整理すると綺麗ですよね。 シンプルで理解が進む。 数学や物理学は「数式」で整理する。 数式は、本当に美しい。 綺麗で洗練されていて、かつ必要充分に網羅されやすい
情報工学は「表」で整理、理解すると分かり易い。
でも、奇特な人が「2018/08/23」に読んでいたら、この節は、少し懐疑的に読んで欲しいです。 現在、飲み過ぎで、まともに読み直していないので。
まとめ
その意欲が湧けば、このページは、改訂する予感があります。
新しい構造と用語で吐き気がするかもしれませんが、自分なりに理解すれば、それなりに分かり易く感じられ、忌避感を軽減できると考えております。
OpenGL, DirectX9c 頃に黎明期を迎えたコンピュータグラフィクス技術のコンシューマ化は、一つの区切りを迎えたような感覚を覚えています。
プリミティヴなプログラムとは、別に趣味の領域ではなく、デバッグ、テスト、改修効率の改善、理解の深層化に必要な"情報"であり、不可欠なものです。
ブラックボックスに理解して、魔術的、手順的な理解で合理化、最適化と思い込むのは、合理ですが、そこで立ち止まり、思考停止するのは、合理的ではありません。
アメリカは、LionでありHunter、Frontierを開拓するPionierであっても、まったくScavengerを志していない。
そこにアメリカを賞賛する種(seeed)がある。 俺もPionierでありたい。
附則
大した文章ではありませんが、学生、生徒、児童、学術機関、教育機関は、好きに参考、改変、引用、コピペして構いません。 連絡も要りません。
商業活動で活用するならば、プロなら、せめてコピペせず手打ちでコピーしてください。
Direct3D13が出るまでが寿命の文章、そして新しい世代が無駄に迷うことを回避(=ショートカット)させる事を趣旨とした文章ですから。
どうせ「プロw」が守る事はないだろうけどな。
毒を盛りましたが、やっぱりプリミティヴなAPIを使ったプログラムは面白く、楽しい。
どこかの大学教授(岡山大学?だったかな?)が数年前にプリミティヴなAPIによる実装に関する迷ったような言葉を発していましたが、そんな事はないと私は思っています。
自虐的な言葉になりますが、これは「擬似的、妄想的」だけど数学のような楽しさ、好奇心を満たす楽しさがある。
けれど、商業目的でプログラムをするならば、迷うことなくUnityやUnrealEngineを使え。 特にUnityは、UEとは別格の価値がある。(トーンシェーディング)
どうせ、不十分な理解でデバッグに挑むと深い理解を必要とするから。