Unreal Engine 4のポストプロセスは、Blueprintsと同様にノードを組み合わせて作ることが基本です。
ノードを駆使することで色々出来るのですが、マテリアルノードにはFor文が無く、ループを回すことが出来ないほか、if文もノードではどうしても見づらくなりがちです。
画面全体にブラーフィルタをかけるポストプロセスをノードのみで作る場合、フィルタのカーネルを作って出力画像に適用するのですが、
例えば5×5のガウシアンフィルタを力づくで作ると以下のように結構大変です。いや、結構どころではなく相当辛いです。辛かったです。
上記ノードネットワーク中に登場するMF_GetOffsetPixelColorというのは、いま注目しているピクセル座標から指定した(x, y)ピクセルだけ離れた位置のピクセルカラーを取得して、且つ係数をかけるMaterial Functionで以下のように作りました。
さらに、この中に登場するMF_GetOffsetPixelというのが、いま注目しているピクセル座標から指定した(x, y)ピクセルだけ離れたピクセルの、画面上のUV座標を取得するためのMaterial Functionで以下のようになっています。
ここで、「画面上のUV座標」というのは何かというと、GBufferに格納されている画像は左上が(U, V) = (0, 0)、右下が(U, V) = (1, 1)となっており、縦横1ピクセル分のUVは、画像の横のピクセル数がX, 縦のピクセル数がYなら(delta U, delta V) = (1 / X, 1 / Y)です(多分)。
それを、SceneTextureのInvSizeで取得することが出来ます。
いま注目しているピクセル座標のUVはTextureCoordinateノードで取得できます。ノードネットワーク上ではTexCoord[0]と表示されています。
ちなみにInvSizeの取得はSceneTextureであればSceneColorでもCustomDepthでも何でも良い…と思いきや、InvSizeしか使っていなくてもSceneColorだとエラーが出ますのでご注意ください(確かUE 4.7くらいまでは大丈夫だったのですが、その後仕様変更になっています)。
このあたりの、ノードを駆使するポストプロセスは何人かの方が日本語でも解説してくださっておりますので、詳しくはそちらをご参照ください。
参考記事:
UE4のポストプロセスマテリアルで色々してみた 基本編 – Unreal Engine 4 (UE4) Advent Calendar 2014 – ぼっちプログラマのメモ
しかし、誰がどう見てもノードネットワークによるフィルタ処理は効率が悪いです。「ぼかしの効果を強くしたいから5×5ではなくて7×7にしよう!」とか思った瞬間にノードの繋ぎ直しが大量発生します。11×11だったら…27×27だったら…考えたくありません。
…というわけで、Unreal Engine 4に一応おまけ機能(?)として備わっている、HLSLコードを直接書けるCustom Expressionsを用いてフィルタ処理を書きましょう。
しかし公式の解説は情報が少なすぎて全然わかりません…。
参考記事:
Custom Expressions
ここでも日本語で一番わかりやすくまとめて下っているのは上記と同じ方ですので、詳細はそちらに譲ります。
参考記事:
UE4のCustomノード(カスタムHLSLシェーダ)を使ってみた – ぼっちプログラマのメモ
いま注目しているピクセル座標のUVとInvSize、それから目的のGBufferの値とをコード上で取得出来れば後はどうにかなります。
というわけで、色々調べた結果、以下が当該コードです。
・いま注目しているピクセル座標のUVの取得
float2 UV = GetSceneTextureUV(Parameters);
但し、Unreal Engine 4.19からは以下に変更になっているようです。
float2 UV = GetDefaultSceneTextureUV(Parameters, SceneTextureId);
SceneTextureIdについては後述します。ParametersはそのままParametersと書いておけば良いようです。
・InvSizeの取得
float2 InvSize = View.ViewSizeAndInvSize.zw;
・GBuffer上の値の取得(最終的に描画されている色の場合)
float3 CurrentPixelColor = SceneTextureLookup(UV, /*SceneTextureId*/14, 0).rgb;
この14というのが、SceneTextureの中でPostProcessInput0のIDです。IDは、マテリアルエディタのSceneTexutreノードのScene Texture Idに並んでいる順番で0から始まっているようです。
その次の0はbool Filteredに該当し、通常はfalse (0)で良いはずです。
こちらの記事がとても詳しくわかりやすかったです。
参考記事:
Unreal Engine 4 Custom Shaders Tutorial – Ray Wenderlich
Unreal Engine 4 Paint Filter Tutorial – Ray Wenderlich
ちなみに最終的に描画されている色を取得する場合には
float3 CurrentPixelColor = CalcSceneColor(UV);
でも大丈夫なようです。ただ、より一般化するためにはSceneTextureLookupを使っておいた方が良いような気がします。
・ぼかしフィルタ実装
ガウシアンフィルタの係数を決める部分は若干数式が複雑になりますので、今回は任意の値nを入力すると(2n + 1) * (2n + 1)の単純なぼかしフィルタを行うCustom Expressionsを作りました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/* Copyright Hirofumi Seo, M.D. */ static const int SceneTextureId = 14; /*PostProcessInput0*/ float2 InvSize = View.ViewSizeAndInvSize.zw; float2 UV = GetSceneTextureUV(Parameters); /*from 4.19*/ /*float2 UV = GetDefaultSceneTextureUV(Parameters, SceneTextureId);*/ float3 PixelColor = float3(0, 0, 0); int NumIgnorePixels = 0; for (int y = -Range; y <= Range; y++) { for (int x = -Range; x <= Range; x++) { float2 OffsetUV = UV + float2(x, y) * InvSize; if (OffsetUV.x < 0 || OffsetUV.x > 1 || OffsetUV.y < 0 || OffsetUV.y > 1) { NumIgnorePixels++; } else { /* old version?? */ /*float3 CurrentPixelColor = CalcSceneColor(OffsetUV);*/ float3 CurrentPixelColor = SceneTextureLookup(OffsetUV, SceneTextureId, 0).rgb; PixelColor += CurrentPixelColor; } } } PixelColor /= ((2 * Range + 1) * (2 * Range + 1) - NumIgnorePixels); return float4(PixelColor.r, PixelColor.g, PixelColor.b, 1); |
Customノードを作ってこのコードを貼り付けて、Output Typeと必要なInputsの情報を追加します。
OUtput Typeは出力の型を明示的に指定する必要があるのですが、Inputには型情報の入力欄がありません。おそらく、ノードネットワーク上で入力された変数の型によって自動的に決まるものと思われます。
上記コードで「変数Rangeの定義はどこだよ!」という疑問の答えがこれです。
で、ノードネットワークを組めば終わりなのですが、一つだけ超厄介なことがあります。上記ノードネットワーク図で、意味不明なIfノードがありますが、
SceneTextureLookup関数を使うためには、ノードネットワークにSceneTextureノードが絶対に1つ以上繋がれていなければいけない、という仕様になっているようです。
試しにBlurノードの出力を直接Emissive Colorにつなぐと、
error X3004: undeclared identifier ‘SceneTextureLookup’
と言われてコンパイルできません…。そのため、ダミーでも何でもよいのでSceneTextureノードを繋ぐためにこのようにしています。
と、いうわけでこれでリアルタイムブラーの完成です!
画像は、ぼかし無し、21×21ピクセルでのぼかし、41×41ピクセルでのぼかしの例です。
これで比較的単純な画像フィルタであればコードでガシガシ書けそうです!
※本記事内容は、国立研究開発法人 日本医療研究開発機構(AMED)の平成29年度 「未来医療を実現する医療機器・システム研究開発事業『術中の迅速な判断・決定を支援するための診断支援機器・システム開発』」採択課題である「術前と術中をつなぐスマート手術ガイドソフトウェアの開発」(代表機関名:東京大学、研究開発代表者名:齊藤延人)に、東京大学大学院情報理工学系研究科の学術支援専門職員として参画している瀬尾拡史が、研究開発として行っているものやその成果を一部含んでいます。