【記事メモ】CustomMeshComponentの作成 Part1
UEのカスタムメッシュの作成について、まとめていただいている記事の資料を抜粋して意訳&メモまとめです。
Part0
【記事メモ】CustomMeshComponentの作成 Part0 - SHIBA LOG
今回はPart1。
※自分用メモなので端折っている箇所もあります。参考程度に
※少し古い記事なのでUE5では事情が異なるケースがあります。わかる所は補足予定
※独自メモは@memoで記入
ここではVertexFactoryの概念とその実装について説明。
VertexFactoryに関連する抑えておきたい用語
RHI...Rendering Hardware Interface
グラフィックスAPIの抽象化レイヤ
(https://docs.unrealengine.com/5.0/en-US/API/Runtime/RHI/)
DirectXやVulkanなど異なるグラフィックスAPIをサポートするためのインターフェイス
FRenderResource/FRHIResource
殆どのレンダリング関係のリソースクラスはこのクラスを継承している、重要なクラス。
FRHIResource
頂点バッファ、インデックスバッファなどRHI系のリソースが継承している。
基本的にこれらを直接操作することはない。
FRenderResource...
基本的に自分たちが触るのはこのクラスで、内部でRHIResourceを作成し、カプセル
化している。そのため、InitRHIやReleaseRHIなどの関数がある
VertexFactory
VertexFactoryの目的...頂点管理。頂点情報の受け渡し(CPUからGPUへ)
頂点バッファ作ったり、頂点のレイアウトを定義したり...
先程あげた用語を使いつつ、説明すると
VertexFactoryは「FRenderResource」を継承しておりメッシュデータを何らかの形で取
得し、それを利用して「FRHIResource」を作成する。
レンダラーがメッシュを描画するとき
VertexFactory、厳密に言うとカプセル化した「FRHIResource」を必要とする。
VertexFactoryはいつ、どうやってRHIResourceを作成するか?
ここでは、すでに組み込まれている「LocalVertexFactory」を元に説明を行う。
※LocalVertexFactoryを構成するクラス図
似たような名前のクラス図が色々出てくるので図をみながら確認したほうが良い
https://miro.medium.com/max/700/1*r2ej5rO4Lmc5oj8PKWPOjQ.jpeg
FStaticMeshDataType
VertexFactoryが、RHIResourceを初期化するために必要なデータを持つクラス
LocalVertexFactoryの中ではFStaticMeshDataTypeを継承した「FDataType」というローカルクラスがあり
そこにスキンメッシュで利用する物を含めて、そちらのクラスを利用している。
@memo FVertexStreamComponentとFRHIShaderResourceViewをセットで持っている
FVertexStreamComponent
FDataTypeが持つ(FStaticMeshDataType)クラスの一つとしてStreamComponentがある。
位置やUVなど一つの属性毎に作られている。
UE4では全てのAttribute(位置、UVなど)を一つの頂点バッファに格納するのでは
なく、Attribute毎に頂点バッファを使用している。
理由はこちらに記載
Storing vertex data: To interleave or not to interleave? | Anteru's Blog
@memo 上記リンクをざっくりまとめると
・アクセスの柔軟性
特定のAttributeを無効に簡単にすることが出来る(NULLバッファを差し込んだり)
シャドウマップを作る時は位置情報だけがあれば良いため、他のAttributeと切り離すことで、キャッシュ、GPU帯域幅の利用率が最も効率が良くなる
・キャッシュ効率が良い
といったことがあるらしい
つまり中身としては、VertexBufferリソースと、Streamに関するMetaデータのラッパ。
FVertexStreamComponentで内包している、FVertexBufferはRenderResourceを継承している
FVertexElement
Vertex Declarationを作成するために、使用されるストリームに関するいくつかのデータ(ただし、ストリームの Vertex Buffer ではない)を含んでいる
EVertexElementTypeはデータフォーマット。
DirectXでいうDXGI_FORMAT(https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format)
FVertexDeclarationElementList
上記、FVertexElementの配列
FRHIVertexDeclaration
InputLayoutと同等のRHIResource。
要は頂点情報の並びを定義するもの。位置、法線、頂点カラー、など
VertexFactoryは、上記記載の「FVertexDeclarationElementList」を引数として
InitDeclarationというDeclarationを定義するための関数を持つ
InitDeclarationのコード内部に関しては、注意事項が2つある。
・InitDeclarationを呼び出すときに「StreamType」を指定する必要がある。
このStreamTypeによって、InitDeclarationでVertexDeclarationを割り当てる
変数が違う。
基本的にはDefaultが利用されるのだが
位置のみ/位置&法線のタイプがあり、これらは例えばDepth Passのような特定パスで利用される。
・VertexDeclarationのキャッシュ機能がついている
全く同等のDeclarationは異なるRHIResourceを生成することなく、再利用のためキャッシュ機構が備わっている。
FVertexStream
Vertex Streamをセットするために必要な情報を含む構造体
FVertexStreamComponentと似ているものになる
FVertexInputStream
もう一つの頂点ストリームデータ型。こちらも同じ用なデータを含んでいる。
FVertexStreamComponentに近いが、VertexBufferではなくFRHIBufferを持つ
VertexFactoryのGetStreams関数で、配列として、レンダラーから取得される
内部で持っているFVertexStreamごとにFVertexInputStreamを生成して配列として返す
それ以降の処理としてVertexFactoryにStreamを要求し
実際にBindするコードに興味がある場合は、以下の関数あたりをチェック
FMeshDrawCommand::SubmitDraw
FRHICommandList::SetStreamSourc
FRHICommandList::DrawIndexedPrimitive
Vertex Factory Shader
UE4 は頂点ファクトリーのシェーダーコードにテンプレートベースの方法を使用している
Vertex Factory Shader(LocalVertexFactory.ush/LocalVertexFactoryCommon.ush)は
頂点シェーダを定義しておらず、エントリーポイントを持たない
ただ、特定のインターフェイスに従って関数、構造体を定義すれば
実行中のパスの頂点シェーダによって利用される。
そのため、 VertexFactoryのShaderファイルの拡張子はushで、シェーダヘッダファイルとなっている。
この仕組みにより各Vertex Factory毎にVertexShaderの全体を記載する必要がないため
モジュール化、コードの再利用が行える。
ただし、コンパイルするシェーダ数は多くなる。
UE4のShadrer Permuations
Unreal Engine 4 Rendering Part 5: Shader Permutations | by Matt Hoffman | Medium
Vertex Factory Shader Permutations
ushに応じて、Material*VertexFactoryの最小限の組み合わせをビルドするようになっている
ShouldCompilePermutations→ShouldCompilePermutationから情報を取得
@memo 特定の組み合わせのみをコンパイルするようにフラグでtrueを返す
The Vertex Factory Macros
VertexFactoryはどのように自分が使用するushを把握するか?
まず、VertexFactoryのクラスには
「DECLARE_VERTEX_FACTORY_TYPE」マクロを定義しておく必要がある
このマクロは、定義したクラスにStaticTypeというFVertexFactoryType型の
静的メンバ変数と、StaticType へのポインタを返すだけのメンバメソッドを追加する。
次に、「IMPLEMENT_VERTEX_FACTORY_TYPE」でushへのパスを指定する
@memo UE5では引数の指定方法が少し変わっている。フラグ単位ではなく、enumのビット単位で渡すようになっている
The Vertex Factory Shader Parameters
頂点に特定のシェーダパラメータのセットを渡したいときは
FVertexFactoryShaderParameters
を継承して、クラスを実装する必要がある(具体的なところはPart2にて)
The Local Vertex Factory Uniform Buffer
すべてのVertexFactoryで使われているわけではないが
FLocalVertexFactory+そのサブクラスで利用されるので、言及する
LocalVertexFactoryは汎用的に利用できるものになっていて、複数ケースをサポートできる。
GlobalUniformBufferを利用して、Vertex Shaderに追加データを渡していて
良い例としては、手動頂点フェッチ(MANUAL_VERTEX_FETCH)。
VertexFactoryは、他の追加データと共に、必要なSRVをそのUniformbufferにわたす
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT~
END_GLOBAL_SHADER_PARAMETER_STRUCTを利用して定義する
FLocalVertexFactoryは、内部でテンプレート化されたRHIユニフォームバッファーへの参照を持っている
@memo 「CreateLocalVFUniformBuffer」でUniformBufferの生成が行われていて
手動頂点フェッチではない場合はNULLのデータで埋めていそう
より興味細かい所は以下を参照
Learning Unreal Engine 4: Adding a Global Uniform Shader Parameter(1) | by YiChen Lin | Medium
The Vertex Factory Shader Code (VertexFactory.ush)
VertexFactoryのShaderファイルの拡張子はushとなっていて
ここで定義する必要があるのは、インターフェイスの関数と、構造体だけで良い
例えば、頂点データレイアウトを記述する...
FVertexFactoryInput
FPositionOnlyVertexFactoryInput
FPositionAndNormalOnlyVertexFactoryInput
頂点シェーダーからピクセルシェーダーにデータを渡すための補間関数である...
VertexFactoryGetTangentToLocal
VertexFactoryGetWorldPosition
VertexFactoryGetRasterizedWorldPosition
VertexFactoryGetPositionForVertexLighting
VertexFactoryGetInterpolantsVSToPS
Docを読んだほうがわかりやすいかも
Shader Debugging Workflows Unreal Engine | Unreal Engine 5.0 Documentation
シェーダー開発 | Unreal Engine ドキュメント
@memo
↑のVertexFactoryを読むと、より分かりやすいかも
FLocalVertexFactoryから継承したVertexFactoryはどのようにushを利用するか?
LocalVertexFactory.ushを使うか、独自シェーダを使うか?どちらの方法でも可能。
ただ、現状だとFLocalVertexFactoryから継承しているVertexFactory系のクラスは
全てLocalVertexFactory.ushを使っている。
同じファイルにコードを追加して
シェーダではプリプロセッサマクロを利用して切り替えを行い、アクティブなコードだけが含まれるようにコンパイルしている
VertexFactoryの「ModifyCompilationEnvironment」関数にて、SetDeineを呼び出すことで制御が可能
プリプロセッサマクロにて制御
https://miro.medium.com/max/700/1*DtxGTtT-3p4bbK6KKxb8dQ.jpeg
SetDefine適用
https://miro.medium.com/max/700/1*E2JRZ70IrIEB-F2vP29VKA.jpeg
Part1は以上。Part2へつづく
(以降はサンプルプロジェクトのコード読んだほうがわかりやすいかも)