【UE5】頂点IDノードを追加してみた – Unreal Engine 5.4

Unreal Engine

水ノ茉の宣伝

準備中...
ゲームを作る予定なの
水ノ茉こおり

始まり

マテリアルから頂点IDを取得できるノードを追加します。

頂点IDとは?

読んで字の如く、各頂点に割り振られるユニークな番号です。

エンジン改造とは?

ゲームエンジンのコードを書き換える行為をエンジン改造と呼びます。

尚エンジン改造という呼称が正式かは知らんです。

これ以上の真面目な説明はググればヒットするので割愛します。

以下、実際の描画まわりの改造事例です。

前髪を透かしたり

毛先まで綺麗に表現できるポストプロセスなアウトラインを描いたり

キャラと背景を別々のシャドウマップに書き込んでセルフシャドウを抑制したり

髪影を落としたり

頂点IDノードを追加して、それを元に鼻に線を引いたり

リポジトリ

最新バージョンのみ保守しています。

過去バージョンが動かないと言われても基本的には知らんがなスタイルです。

実装

前提として独自のシェーディングモデルの実装が必要です。

VertexFactoryの対応範囲を広げれば恐らくデフォルトのシェーディングモデルでも使用可能だと思いますが、筆者が基本的にフルスクラッチなシェーダーしか使わないため、リポジトリに含める気は今のところないです。

Engine/Source/Runtime/Engine/Classes/Materials/MaterialExpressionVertexId.h

頂点IDノードのクラスです。

UMaterialExpressionを継承するだけで書き方は全て規格化されているので、特に気に留める必要はないです。

githubで実装を見る

Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp

MaterialExpressionVertexId.hのインクルードです。

githubで実装を見る(L292)

UMaterialExpressionVertexIdの各実装ですね。

上から、コンストラクタはピンの出力チャンネル数を決めていたり、シェーダーコンパイラを通す条件を定義していたり、Captionはノードの表示名だったかな、最後のはノードにカーソルを合わせた際に表示する説明文ですね。

シェーダーコンパイルやマテリアルエディタでしか使わない実装部分は、WITH_EDITORで囲われている感じです。

頂点シェーダー以外で使おうとするとこんな感じでコンパイルエラーが出ます。

非対応なシェーディングモデルで使おうとした場合もこんな感じでコンパイルエラーが出ます。

githubで実装を見る(L28593-L28646)

Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressionHLSL.cpp

MaterialExpressionVertexId.hのインクルードです。

githubで実装を見る(L239)

GenerateHLSLExpressionの実装だけこっちに書きます。

githubで実装を見る(L5101-L5106)

Engine/Source/Runtime/Engine/Private/Materials/HLSLMaterialTranslator.h

ただの関数宣言です。

仮想関数になっているのはライトマスやマテリアルのエクスポーターでは、ノード展開を変えられる設計になっており、ライトマスの方でオーバーライドすれば、専用の展開が出来たりするのです。

今回はオーバーライドしていないので、種類に問わず同じ展開です。場合によっては動作不良起こすでしょうが、筆者さんの作業範囲で触れることはない機能たちなので、対応する気はねぇのです。

githubで実装を見る(L1375)

Engine/Source/Runtime/Engine/Private/Materials/HLSLMaterialTranslator.cpp

HLSLコードとして展開する際の実装部分です。

AddInlinedCodeChunkは、読んで字の如く、指定された文字列をインラインで展開、埋め込むだけです。

githubで実装を見る(L15114-L15127)

Engine/Source/Runtime/Engine/Public/MaterialCompiler.h

ただの関数宣言です。

githubで実装を見る(L728)

ただの実装呼び出しです。

githubで実装を見る(L1504)

Engine/Source/Runtime/Engine/Public/MaterialHLSLTree.h

この辺りの機能はブレークポイントを張っても引っかかることが無かったので、よく分かっていない実装です。

VertexColorノードが対応していたので、友人であろうVertexIdもやっておくべきかなぁ程度です。

実装読めばいいんですが、マテリアルエディタはなげぇのでお断りなのです。

githubで実装を見る(L211)

Engine/Source/Runtime/Engine/Private/Materials/MaterialHLSLTree.cpp

よく分からない機能の実装部分です。

githubで実装を見る(L175)

同上。

githubで実装を見る(L467)

Engine/Shaders/Private/MaterialTemplate.ush

FMaterialVertexParametersに頂点アトリビュートから取ってきた頂点IDを格納しています。

この構造体は FMaterialVertexParameters Parameters こういう感じで関数の引数としてよく使われます。VertexParametersという名前の通り、基本的には頂点シェーダーでのみ使用される構造体です。ピクセルシェーダーの方はPixelParametersというのが別途存在します。

Parametersだけ見てピンとくる方もいるかな。
先ほどHLSLコードとしてインライン展開したやつです。

こんな感じのフローで頂点IDを返しているのです。

githubで実装を見る(L686-L687)

Engine/Shaders/Private/GpuSkinVertexFactory.ush

ToonLitの場合は頂点IDが渡されるように頂点アトリビュートを改変しています。

対応するシェーディングモデルを増やしたい場合はここに追加すれば基本的にはいいんですが、独自のシェーディングモデル以外でやろうとすると、これ以外のVertexFactorも対応入れないといけなかったりするかもなので、意外と面倒かもしれませんね。知らんけど。

githubで実装を見る(L123-L124)

頂点アトリビュートからFMaterialVertexParametersの方に受け渡しです。

githubで実装を見る(L369-L371)

頂点IDを元にお鼻の陰を描いてみる

RenderDocやPIXなどで頂点IDを特定します。
Preview内でマウスを右クリックすると最短の頂点がフォーカスされます。

こんな感じのノードを組みます。

頂点シェーダーで同一頂点IDの場合は1.0を返して、その結果をピクセルに運んで2値化という流れです。

VertexIdノードは頂点シェーダー専用なので、絶対にVertexIntepolatorを挟んで下さい。
忘れてもコンパイルエラーが出るようになっているので、それほど留意する必要もないかもですが。

大量にequalをする場合はint4とかに纏めてanyで判定するのもいいかもですね。
とはいえ最近のハード性能であればそこまで根を詰めた最適化を考える必要もないかもですが。

int Ids[] =
{
    53462,  // 鼻の中心の上
    53464,  // 鼻の中心の上
    53465,  // 鼻の中心の上
};

int VertexId = round(InVertexId);

float Weight = 0.0;
UNROLL
for (uint i = 0; i < 3; ++i)
{
    Weight += (Ids[i].x == VertexId) ? 1.0 : 0.0;
}

Weight = min(1.0, Weight);

return Weight;

こんな感じです。

アニメ寄りなセル調はベタ塗りが基本なので、こういうちょっとした表現で一気に立体感が増しますね。

使用上の注意点として、頂点からピクセルに情報を渡しているVertexIntepolatornointerpolationが指定できません。要は全て線形補間で返されるため、閾値を中途半端にすると、2値化した際に得られる形状が歪になります。

おわり!!!

ほんの些細な表現ですが、有ると無いとでは結構雰囲気が変わりますね。

ちなみに『テクスチャに描けば良くない?』という疑問に対しては『はい』という返答のみです。

あくまでこういうアプローチもあるよという意味合いが強いです。

あとはアーティストを確保せずともエンジニアだけで出来る描画手法って感じかな。

雑談

ソース元は忘れましたが(というか言伝で聞いただけ)、学マスのお鼻に掛かる陰のレンダリング手法を予想している方が居たらしく、その方曰く『頂点IDでやってるんじゃね?』とのことでした。

それで『ほほー』と思った筆者さんが勢いだけで実装したのがこの子です。
予定通りの実装が素直に出来ちゃったので、そのまま満足して記憶喪失していました。

コメントの日付がシェーディングモデルの追加よりも若いのはそういう理由なのです。
あの日付もリファクタ後だから仮実装はもっと前なんだけどね。もう記憶がない。

ちなみに、CEDECで答え合わせがあり正解は、普通にアーティストがメッシュを作って、画角によって押し出しの制御をしているだけという、資金力で実現しているだけ、ちゃんちゃん、というオチでした。

そういえば、もう少しでCEDECの早期申し込みが開始しますね。

バカ高いですけど、数万払って他人の技術を盗めると思えば個人的には割安なのかなぁと受け入れています。