UE4用NVIDIA Image Scalingプラグイン実装について

UE4 Advent Calendar 2021 12/5の記事です。

はじめに

PS4/XboxOne世代は1080p出力が、PS5/XboxSeriesX世代は4K出力が基本になっています。
しかし、大体の場合でレンダリング解像度はそれより低い解像度で行い、ハードウェアのアップスケーラーを利用するか、もしくは何らかのアップスケール技術を使うのが割と基本です。
残念ながら、これからもその状況はあまり変わらないでしょう。

そうなってくると、どのようなアップスケール技術を使うか?が問題になってきます。
現在、アップスケール技術としてよく取り沙汰されるのはNVIDIA社のDeep Learning Super Sampling (DLSS)です。
名前の通り、Deep Learning技術を利用した超解像技術で、Temporal Reprojectionも利用するためアンチエイリアシングの機能も兼ね備えているようです。
UE4でいうならTemporal AA Upsampling (TAAU)の代替手段になります。
ただし、この技術はGeforce RTX以上を必要とするため、ハードウェアが限定されます。
当然、コンソールゲーム機には使用できません。

そのような状況で新しく現れたのがAMD社のFidelityFX Super Resolution (FSR)です。
こちらはDLSSと違い、ハードウェアを限定しません。Shader Model 5.0?くらいが使用できれば使えます。
これは昨今のPCならほぼ全て使えますし、PS4以降のハードでも使用が可能です。
しかもUE4.27.1用ではありますが、すでにプラグインとして公開もされています。

https://gpuopen.com/learn/ue4-fsr/

また、FSRは入力として低解像度の最終レンダリング画像のみを求めます。
アンチエイリアシングは別途行う必要がありますが、FSR自体にゴースティングの心配はありません。

UE5ではTemporal Super Resolution (TSR)が追加されています。
こちらはUE4への移植も可能ではありますが、一応PS5/XboxSeriesX世代以降対応ということになっています。
PS4世代で動作するかは不明です。
この技術はTAAUの代替手段ですので、アンチエイリアシング機能も含まれます。

さて、つい先月、DLSSのバージョンが2.3になりました。
しかしそれと同時に、新しい超解像技術も発表されました。
それがNVIDIA Image Scaling (NIS)です。

https://github.com/NVIDIAGameWorks/NVIDIAImageScaling

このアップスケーラーもFSRと同様に、最終出力のみを受け取りアップスケールします。
アンチエイリアシングは行いませんが、やはりNIS自体にゴースティングは発生しません。
加えてコンピュートシェーダによる実装であり、Shader Model 5.1以上に対応していれば利用可能です。
今どきのPCならどんなGPUでも使えますし、PS4世代でも使えるはずです。

残念なことに、まだNISUE4プラグインは提供されていません。
発表されたばかりだからということもあるかと思いますが、そのうち出てくるんじゃないかとは思います。

プラグインを作った話

が、そのうち出てくるだろうと思っていまテストしてみたい!FSRと比較したい!
そう思ったので作ってみました。

https://github.com/Monsho/NISPluginForUE4

一応ライセンスはMITにしています。NIS SDKがMITライセンスなので。
インストールについてですが、Readmeを読んでください。
残念ですが、ビルド済みバイナリは含めていませんので、エンジンに突っ込んでビルドしてください。
プラグインの追加だけなのでエンジンコード自体は汚しません。

なお、UE4.27.1以上対象のプラグインです。
UE4.26以下では利用できません。UE4.27.0なら使えるかも。
というのも、FSRプラグインを参考にして作ったのですが、そこで使用されている機能がUE4.27で追加された機能なので。

以降ではその辺の機能や、プラグイン作成時の注意点について簡単に解説します。

プラグイン解説

ISpatialUpscaler

実は、FSRUE4.26から対応していたのですが、その頃はエンジンコードを変更するパッチとして提供されていました。
UE4.26ではTAAをサードパーティ製に置き換える手段は用意されているのですが、FSRは最終段のPrimary、もしくはSecondaryのUpscalerを置き換えるものなのでTAAの置換手段は利用できません。
なお、DLSSはこのTAAを置換する手段を用いてプラグインを実装しています。

しかしFSRプラグイン実装のためなのか、UE4.27ではこのPrimary、SecondaryのUpscalerを置換する手段が用意されました。
これを利用するにはISpatialUpscalerを継承したクラスを作成し、FSceneViewFamily::SetPrimarySpatialUpscalerInterface()、もしくはFSceneViewFamily::SetSecondarySpatialUpscalerInterface()で登録する必要があります。
これが登録されているとデフォルトのアップスケーラーの代わりに、プラグインで実装したアップスケーラー、つまりFSRNISを実行できるという仕組みです。

FSceneViewExtensionBase

ここで注意点。
FSceneViewFamilyは基本的に毎フレーム生成・削除が行われるようです。
そのため、ISpatialUpscalerを継承したクラスは毎フレーム登録する必要があります。
そこでFSceneViewExtensionBaseを利用します。

このクラスを継承したクラスは生成時に自動的にエンジンが持っているViewExtensionsに登録されます。
ViewExtensionsに登録されているクラスは、ViewFamilyのセットアップ時や描画の開始・終了時にViewFamilyに対して追加処理を行うことができます。
NISプラグインでは、FNISViewExtensions::BeginRenderViewFamily()関数が描画開始時に実行される関数で、ここでPrimaty UpscalerにFNISUpscalerを登録しています。

登録する場合は常にnewする必要があります。
FSceneViewFamilyが削除されるタイミングで登録したUpscalerも勝手に削除されるためです。
寿命管理はこちらで制限できなので、TSharedPtrなどで作成してポインタだけ登録する、というような手法は使えないと考えてください。

FNISUpscaler

実際にNISを実行しているのはこのクラスです。
FNISUpscaler::AddPasses()関数でNISを実行するパスを追加しています。

ここでもまた注意点があります。
FSRはコンピュートシェーダでもピクセルシェーダでも実装することができるのですが、NISはコンピュートシェーダのみの実装となっています。
つまり、NISの出力テクスチャはUAVでなければならないのですが、最終出力となるPassInputs.OverrideOutputはUAVとして使用できません。
そのため、NISプラグインではUAVとして使用できる2Dテクスチャを作成し、ここにNISレンダリングしてから最終出力にコピーするという手段を用いています。
ピクセルシェーダでも実装できるなら良かったのですが、NISはLDSを使用してるっぽいので無理なんですよね…

また、NISでは係数を保存したテクスチャを渡す必要があります。
この係数はNISが用意しているもので、入力画像によって変化するものではありません。
そのため最初に生成するだけでよいのですが、FNISUpscalerは毎フレーム削除されてしまうので、コンストラクタで生成・デストラクタで破棄とやるのは大変無駄です。
そこで、コンストラクタで外部から渡すという手法を用いているのですが、ここでちょっと面倒なことが…
この理由も含めて次節で解説します。

2つのモジュール

NISプラグインImageScalingImageScalingExtensionという2つのモジュールから成り立っています。
FSRも同様の仕様なのですが、わざわざ2つのモジュールに分けているのは初期化タイミングの問題のためです。

まず、FNISUpscalerはグローバルシェーダを利用するため、独自のシェーダを利用できるように登録する必要があります。
そのためにモジュール開始時にシェーダのディレクトリを登録することになるのですが、この場合のモジュールはPostConfigInitで実行されなければなりません。
このタイミングはエンジン初期化より前のタイミングであるため、この設定になっているモジュールの初期化段階ではエンジンの機能を利用することができないのです。

エンジン初期化の前にできないこととは何かというと、それはViewExtensionの登録とテクスチャの作成です。
FNISViewExtensionを作成してしまうと自動的に登録されてしまうわけで、その登録先はエンジンなわけです。
テクスチャ生成もRHIなどを利用するため、エンジンが初期化されていなければ実行できません。
グローバルシェーダを使うためにはPostConfigInitでなければならないが、ViewExtensionを生成するのはPostEngineInitでなければならないのです。

そのためにViewExtensionを生成するためのモジュールが必要になります。それがImageScalingモジュールというわけです。
テクスチャ生成もこのモジュール開始時に行い、FNISViewExtensionに登録しています。
FNISUpscalerが利用するテクスチャはさらにFNISViewExtensionから渡されるという形になっています。
面倒ではありますが、こうする以外の方法を見出すことができませんでした。

使い方の注意

NISプラグインを使う場合の注意点として、FSRとの併用はできないという点に注意してください。
プラグインを両方とも有効にすることはできますが、その状態でFSRNISを有効にするとISpatialUpscalerの登録時にチェックで引っかかります。
チェックを無視すれば先に進めることはできますが、多分メモリリークとなります。

FSRはデフォルトで有効になってしまうので、NISはデフォルトで無効にしています。
NISを利用する場合はまずFSRを無効にしてから(r.FidelityFX.FSR.Enabled = 0)、NISを有効にしましょう(r.NVIDIA.NIS.Enabled = 1)。 その逆もまた同じです。無効にしてから有効にする。これを守りましょう。

もう1つ。これはFSRでも同様ですが、テクスチャのミップレベル調整は自動的に行われません。
r.ScreenPercentageで設定したパーセンテージに合わせて、r.MipMapLODBiasの値を調整しましょう。

比較

1440pネイティブ f:id:monsho:20211205022850p:plain

720p->1440p デフォルトアップスケーラー f:id:monsho:20211205022946p:plain

720p->1440p FSR f:id:monsho:20211205023317p:plain

720p->1440p NIS f:id:monsho:20211205023241p:plain

さいごに

このプラグインは現段階ではWindowsPCのD3D12バージョンでしか動作を確認していません。
他の環境で動作するかという点については不明です。
多分動くんじゃないかとは思うのですが、シェーダコンパイルで失敗するとかの可能性はあると思います。
そうなった場合はバグを修正してプルリクでも投げてください。

というわけで今年のAdvent Calendar記事はこれにて終了!
前回の更新も去年のアドカレだったんだなぁ…

NVIDIA RTXブランチについて

Unreal Engine 4 アドベントカレンダーその1 2日目

はじめに

今年、Ray Tracing Night Week 2020にてUE4レイトレのアップデートについて講演を行いました。

UE4.25のレイトレーシングで出来ること/出来ないこと

この中で謎の半導体メーカーであるNVIDIA様のRTXブランチについて言及したところ、これについての質問が寄せられました。
質疑応答の際に答えはしたのですが、動画内で言葉だけで説明したのだとわかりにくいですし機能についてもよくわからないと思いますので、ブログにて簡単ですが紹介させていただこうと思った次第です。

この記事はRTXブランチに興味がある方向けのものですが、エンジンビルドについては言及しません。
エンジンビルドについて知りたい方は株式会社ヒストリア様の以下の記事を参考にしてください。

historia.co.jp

また、あくまでもRTXブランチ紹介記事ですので、以下のような方は対象としていません。

  • RTXブランチをすでに使っている方
  • NVIDIA社の中の人

ソースコードのダウンロード

RTXブランチのソースコードGitHubから行います。URLは以下です。

https://github.com/NvPhysX/UnrealEngine/tree/RTX-4.25

ブランチは他にもいくつかあり、RTX以外のブランチも多く存在していますが、今回はそちらの解説は行いません。
URLにアクセスしても見られないという方はGitHubのアカウントを作成し、Epic Games様のUE4ソースコードにアクセスできるようにしましょう。
ダウンロードからビルドの流れは前述の記事の通りです。

現在はUE4.25までしか対応していませんが、UE4.26が正式版になるとそちらへのマージ、対応が行われる可能性があります。

機能について

このブランチの目的はNVIDIA様がレイトレーシングの様々な機能のテスト、パフォーマンス調整を行うものと思われます。
ここで実装された機能がUE4本流にマージされている例もあります。 機能についての簡単な解説はREADME.mdファイルに記述されているので、とりあえずどんな機能があるかを知るだけならGitHubのWebページでも見ることが出来ます。

以下で各機能について解説を行いますが、一部の機能については少し詳しく解説を加えています。

Direct Optimizations

出力結果に変化がない、特にデメリットのない最適化です。
必ず高速化するわけではありませんが、使用することで映像に変化があるということは基本的にありません。

バウンス回数の固定

UE4のリフレクションではマルチバウンスに対応しており、バウンス回数は変更可能です。
リフレクションシェーダは内部にバウンス回数に対する動的なループを持っていますが、動的なループは基本的に負荷が高くなります。
シェーダコンパイル時にループ回数が固定できると動的ループが展開され、一般的には高速化します。
この修正はそのループ回数を固定してシェーダコンパイルが可能な修正ですが、現在はバウンス回数が1回のときのみ固定されます。

半透明マスキング

UE4のレイトレ半透明は画面全体にレイトレースを行いますが、半透明マテリアルにレイが衝突しないピクセルでもレイトレースします。
これは無駄なので、一旦半透明メッシュをステンシルバッファに描画し、ステンシルでマークされた部分以外はレイトレースをキャンセルします。
画面全体を半透明メッシュが覆うような状況では高速化しない可能性がありそうですが、r.RayTracing.Translucency.MaskのCVarでON/OFFを切り替えることができるので、これを使ってパフォーマンス計測しましょう。

インスタンススタティックメッシュ(ISM)のカリング最適化

FoliageのISMのカリングを最適化しています。
ISMカリングのCPU負荷が高い状況では有効な機能です。

動的メッシュのバッチ作成の並列化

レイトレ界に登録する動的メッシュは静的メッシュとは違った処理を行う必要があります。
そのためのコマンドを発行する部分は通常では並列化されていませんが、大量に動的メッシュがある場合に不利になります。
r.RayTracing.ParallelMeshBatchSetupのCVarをONにすることで動的メッシュの処理を並列化出来ます。

マテリアルバインドの高速化

r.RayTracing.BatchMaterialsでON/OFFできる機能で、マテリアルバインド処理を単純化することで高速化するもののようです。
バインドするマテリアル数が多い状況で高速化するのかな?

Quality Tradeoffs

これらの最適化は映像クオリティとのトレードオフを要求します。
コメントでは違いを見つけるのは困難な程度のしきい値で設定されていると書かれています。

シャドウレイのシザリング

ポイントライトなどの効果範囲が限定されるライトでは画面全体をレイトレースせずに効果範囲のみをレンダリングするほうが効果的です。
どうやらこの機能はレイトレースシャドウを効果範囲でシザリングする機能を提供しているようです。
単純なレイトレースシャドウであればトレードオフはありませんが、デノイザーが絡むとシザーボックス境界付近に問題が出ることがあります。

影を落とすライトの優先度付け

r.RayTracing.Shadow.MaxLightsr.RayTracing.Shadow.MaxDenoisedLightsがCVarに追加されます。
この指定によってシャドウを落とすライトの数を制限することが可能です。
制限されるとライトを距離や範囲で優先度付けして、優先度の高いものから順に制限数までシャドウを落とすようにします。
この設定はMovableライトには適用されず、Static, Stationaryライトに適用されます。

また、r.RayTracing.Shadow.FallbackToSharpを利用すると、エリアライトで本来ならデノイズが必要なレイトレースシャドウをシャープな影にしてデノイズを避けるようです。

ライトの優先度付け

リフレクションやGIではレイトレースしてヒットしたマテリアルに対してライティング計算を行います。
この際に影響するライトの数はエンジン側で256個で制限されていますが、シーン中にはそれを超えるライトの配置が可能です。
配置されているライト数が256を超える場合、デフォルトでは頭から順に256個を利用するようにしますが、この機能を使うことでライトに優先度を付けて上位から選択するようにします。
r.RayTracing.Lighting.MaxLightsは登録するライトの個数を指定します。優先度付けしたライトを指定個数まで登録するようになります。
r.RayTracing.Lighting.MaxShadowLightsはリフレクションなどのライト計算時にシャドウレイを飛ばすライトの個数を制限します。
これらの設定は単純なカリング処理となるので、大量にライトが配置されている場所ではポッピングするので注意してください。

f:id:monsho:20201122102039p:plain

ライトの優先度付けルールはr.RayTracing.Lighting.Priority.~である程度の調整ができますので、ゲームに合わせて対応すると良いでしょう。
例えば屋外の場合はフラスタムを重視するほうが良さそうですが、屋内の狭い部屋ならカメラ周辺やカメラ後方を重要視したほうがいいかもしれません。

ラフネスへの乗算値

ラフネスが高いと反射レイは様々な方向に飛んでノイズが大きくなります。するとデノイズでは間に合わなくなり、もやもやしたノイズが残ることになります。
ラフネス値をマテリアルごとに調整する事もできますが、大変ですしラスタライザでのライティング結果にも影響を及ぼします。
r.RayTracing.Reflections.RoughnessMultiplierを利用すると、レイトレリフレクションのときのみラフネス値に補正をかけることが出来ます。
r.RayTracing.Reflections.MaxRoughnessと違ってレイトレをキャンセルすることはありませんが、ノイズの調整には扱いやすいと思います。

Enhanced Features

UE4本流にはない機能を追加しています。
あなたのアプリケーションに必要な機能が見つかるかもしれません。
見つけてしまった場合、Epicさんにマージ要求するか、自前でマージしましょう。

Hybrid Translucency

Ray Tracing Night Week 2020でも言及した機能で、デフォルトのレイトレ半透明の代替として使用することが出来ます。

デフォルトのレイトレ半透明は半透明に対する反射と屈折を1つのレイトレシェーダ内で別のレイトレースで実現します。
この手法は高品位ではあるものの、処理負荷が高く、その上半透明が多重に重なっている場合に問題が発生することがあります。

Hybrid Translucencyは反射のみをレイトレで対応し、屈折についてはこれまでどおりのラスタライザで対応します。
反射は別バッファにレンダリングすることで対応しますが、複数の重なりに対してはレイヤー数を増やしてカバーします。
レイヤー数が少なければデフォルトのレイトレ半透明より負荷が軽くなる可能性が高く、また、半透明の重なりによるレンダリングエラーにも対応しやすいので映像作品よりゲーム作品向けと言えます。

詳しくはshikihuikuさんの記事をご覧ください。

UE4 RTX-4.23ブランチのHybrid Translucencyは何をしているのかshikihuiku.wordpress.com

ライトファンクション、ライトチャンネル

UE4本流では対応していないライトファンクションとライトチャンネルに対応する修正です。
これらの機能を多用しているアプリケーションではマージを検討したほうが良いでしょう。
特にライトチャンネルはパフォーマンスが上がることもあります。

余談ですが、UE4本流では使用されていないCallable Shaderがライトファンクションでは使われています。
自分も使ったことがなかった機能なのでちょっと感動したけど、PSやCSでもCallable Shader使いたい…

Cast Static Shadows/Cast Dynamic Shadows対応

デフォルトではリフレクションのシャドウキャスト設定はCast Shadowsフラグだけをチェックしていますが、RTXブランチではCast Static Shadows/Cast Dynamic Shadowsにも対応しています。
ただ、フラグに対応しているというだけで、静的/動的なメッシュにきちんと対応するわけではなく、この2つのフラグが落ちていたらシャドウを落とさない、という形になるだけです。

非対称ScreenPercentage

レイトレリフレクション/GIではScreenPercentage設定が存在しますが、この設定は細かく調整されているわけではなく100%(デフォルト)の後は50%、25%という順番で下がっていきます。
つまり、縦横が1/2、1/4という形でしか下がっていきません。

この修正では縦横比が変わる形での調整ができるようになっていて、70%では横だけが1/2になります。
50%だとどうしても汚くて困る、という場合に試してみると良いかもしれません。

Subsurfaceのバックフェースライトカリング

UE4本流では一部のシェーディングモデルにおいて、ライトの方向と法線方向が水平より下(つまり背面)になっている場合にシャドウレイの発射をキャンセルする機能があります。
DefaultLitでは特に問題ないようですが、Subsurfaceでは陰影のエッジが非常に汚くなってしまいます。
r.RayTracing.Shadow.CullTransmissivesを利用すると正しくシャドウレイを飛ばすようになり、陰影のエッジが綺麗になります。

f:id:monsho:20201122114505p:plain

Debugging & Visualization Features

デバッグ用の機能追加です。

BVHの視覚化

ShowFlag.VisualizeBVHComplexity/VisualizeBVHOverlapを利用してBVHの複雑さを視覚化出来ます。
レイトレーシングではオブジェクトやポリゴンが重なり合っている部分では処理負荷が高くなる傾向がありますので、できる限り分散して配置した方が有利です。
この機能を使って負荷が高い場所、つまり重なりが複雑な部分をチェックしてパフォーマンス改善に役立てることが出来ます。

f:id:monsho:20201122120758p:plain

レイトレーシング負荷の視覚化

ShowFlag.VisualizeRayTimingを利用してレイトレーシング自体の負荷を確認することが出来ます。
レイトレは様々な要素がパフォーマンスに複雑に関わってくるため、この視覚化はマテリアルの複雑性のみを視覚化しているわけではないことに注意してください。
また、レイの反射方向などによって結果が大きく変わるためか、かなりノイジーです。
しかし、明確に重い部分はわかりやすいので、最適化の指針としては十分使えるでしょう。

f:id:monsho:20201122121941p:plain

Miscellaneous Features & Fixes

その他の機能です。

半透明マテリアルのシャドウ対応

UE4本流では半透明マテリアルもシャドウを落とす設定になっています。
オブジェクト単位、マテリアル単位でシャドウキャストフラグを操作する方法ももちろんありますが、r.RayTracing.ExcludeTranslucentsFromShadowsを利用することで一括で半透明マテリアルのシャドウを削除できます。

ただ、エディタ上でON/OFFした場合、何故か即時反映はされず、メッシュのCast ShadowフラグをON/OFFし直したらそのメッシュだけ適用されるという状態になりました。
不具合なのか仕様なのかは不明ですが、やはり明示的にマテリアル側で指定するほうが良いでしょう。

シャドウの遮蔽面の統一

レイトレシャドウなどの遮蔽はデフォルトで表面、裏面ともにシャドウを落とします。これはr.RayTracing.Shadows.EnableTwoSidedGeometryをOFFにすることで片面のみシャドウを落とすようにすることが出来ます。
しかしこのシャドウを落とす面がシャドウマップとレイトレで逆になっているという問題があります。

f:id:monsho:20201122124629p:plain

r.RayTracing.OcclusionCullDirectionをONにすることで、この逆転現象を解消することが出来ます。

f:id:monsho:20201122124759p:plain

メッシュが閉塞されている場合は意味がない機能ではあるのですが、時折ペラ1枚の書き割りなどを利用することがあったりします。
このような場合で両面シャドウで代用できない場合には有効な機能ですが、かなり使用場面は限定されるのではないかと思います。

さいごに

先日ぷちコンゲームジャムでレイトレ半透明を利用してレンズ越しにのみ表示されるマテリアルという特殊な環境を作ってみました。
この際にRayTracingQualitySwitchReplaceを利用したのですが、OpacityMaskに利用したところ半透明越しじゃなくても表示されてしまうという不具合にぶつかりました。
この理由が普通に半透明部分以外もレイトレして、普通にライティング計算しているからだったわけなのですが、エンジン側のシェーダを修正して対応しました。

その後、この記事を書いていて気づいた。

RTXブランチの半透明マスキング使えばエンジン改造しないで済んだじゃん!

NVIDIA様、さすがでございます。

明日はUE4でアニメーションといえばこの人、”無敵の龍”ほげたつさんのControl Rigの記事です。 お楽しみに!

Substance Designerでシェーダを書いてみた話

ちょっと遅くなりましたが、先々週にあったSubstance Designerゆるゆる会で作成したマテリアルについてです。

ゆるゆる会のテーマはスタイライズドでした。
私が選んだのは『あつまれ どうぶつの森』に出てくる床の1つである”みなものゆか”でした。

f:id:monsho:20201101141808p:plain

この床はゲーム中で動きます。また、カメラを動かすと白っぽい部分(コースティクス的な?)と青い部分に視差があることがわかります。
いわゆるParallax Mappingを施されているテクスチャです。
で、まずは普通にそれっぽく再現するところから始めてみました。出来たのがこれ。

f:id:monsho:20201101143104p:plain

色味とかに差はありますが、まあ似た感じになったかなと。
しかし物足りない。
原因は2つ。動かない。視差がない。
え?再現性が低い?ごめんなさい…

とにかくこの2つをなんとかしたい。
動かない点についてはSubstance Designerの性質上仕方ない部分もありますが、timeパラメータを使って対応すると確認が難しい上に、パラメータが変更されるとテクスチャが生成し直しになるので時間がかかります。
これくらいのマテリアルならそこまで遅くはないものの、なめらかに動いているようには見えません。

もう1つの視差がない点については、Substance DesignerにはParallax Occlusion Mappingが存在しています。
これを使えば再現できるかというと、これは無理です。
POMは高さ方向でOcclusion、つまり遮蔽されてしまうので、例えば白い部分を高くするようにしてしまうとその部分が山になっているように見えてしまいます。

通常、この手の表現はシェーダ側で行います。UE4のマテリアルなんかが最終出力はシェーダですね。
しかしSubstance Designerの最終出力はテクスチャです。シェーダではありません。
テクスチャというのはシェーダで利用するリソースの1つでしかありません。実際、Substance Designerでもシェーダは使われていて、3DビューメニューのMaterialからプリセットのシェーダを選択することが出来ます。
しかしこの中にはみなものゆかを表現できるシェーダが存在しません。

ないなら作れ!

というわけで作ってみました。

f:id:monsho:20201101150925p:plain

似たような感じではありますが、カメラを動かすと水面と水底に視差があるように見えます。
そして水底の影が水面のコースティクスを参照するようになっています。
このように、Substance Designerは(Painterもですが)自作のシェーダを使用することが出来ます。

シェーダの解説は行いませんが、作成されているシェーダとこのマテリアルはゆるゆる会のTrelloにアップロードされていますので、興味ある人は試してみてください。

trello.com

Substance DesignerのシェーダはGLSLで書く必要があります。書けるのはオブジェクト表面用のシェーダだけで、ポストプロセス的なものは出来ません。マルチパスレンダリングも無理。
しかし環境マップやライト情報も取得できるのでライティングも出来ます。カメラ情報も取得できるのでカメラ角度に応じた変化も対応できます。

とはいえ、書き方やパラメータ設定の仕方はGLSLを知ってるだけでは難しいでしょう。
そんなあなたに朗報です。なんと、ビルトインシェーダのコードが読めます!
以下のフォルダを開いてみてください。

$(SubstanceDesignerインストールフォルダ)\resources\view3d\shaders

ここにビルトインされたシェーダが入っています。
このフォルダ直下にある.glslfxファイルはどのシェーダステージにどのシェーダコードを利用するか、パラメータはどんな名前で設定するか、テクスチャはどんなIDで設定するかといった内容を記述します。
実際のシェーダコードはこのフォルダ内の各マテリアル名のフォルダにあります。
頂点シェーダやテッセレーションはあまりいじることはないかもしれませんが、その場合はcommonフォルダ内にデフォルトのシェーダがあるのでそれを使うことが出来ます。
もちろん、頂点シェーダも作成することも可能ですが、多くの場合はそこまでする必要はないでしょう。

シェーダが書けるという点ではSubstance Painterでも書くことが出来ますが、SDとの互換性がなさそうな感じです。
なぜそこに互換性がないのか…謎。

もしシェーダの内容に質問などがあるようでしたらコメントなりいただければ追記しますが、特に難しいことはしてないです。多分。

でまあ、こんな感じでシェーダを書くことに意味があるのかという話をしてしまうと、多分ほとんどの場合では必要ないです。
しかしゲームエンジンを使うにしろインハウスエンジンを使うにしろSubstance DesignerやSubstance Painterと同じ結果にするというのはなかなか難しいです。
ポストプロセスの問題もありますが、ライティングモデルが別だったり、それこそトゥーンシェーダのようなPBRではないライティングを行っているものもあるでしょう。
そういった違いが制作効率を下げるような状況であれば十分使えるはずです。
とはいえ、SPならともかく、SDで使うことはほとんどないと思います。
こんな事もできるよ~という程度のものとして覚えておくくらいで良いでしょう。

Substance Designer + UE4でWangのタイルを実装してみる

3/14に開催した第6回Substance Designerゆるゆる会のテーマは地面でした。
特に自然か人工かは指定していない、一般的にGroundとして扱われるもの全般ということでいろいろな地面を参加者の方が作っていらっしゃいました。
SDの会だってのにHoudiniの解説しかしない人が出てくるという珍事もありましたが、盛況に終わったのではないかと思います。

さて、そのHoudiniの解説しかしなかったちょっとあれな人は骨折して会場に来れなかったということでリモートで参加されていたのですが、もんしょさんの作ったものはブログで解説してくれるんですよね?とか言ってきたわけで。
解説予定もなかったのですが、そう言われたら解説するしかないやろってことで解説します。

私はSandy Groundという名前で一応砂地に石が混じってるような地面を作ったのですが、割と簡単に作ったもので特筆すべき部分はありません。

f:id:monsho:20200315201942p:plain

こんな感じ。

砂地の地面の部分は凹凸もほとんどなく、砂粒のノーマルと荒野っぽさを少し出すためにちょっとした段差が入ってる程度です。
もちろん石の部分は高さがありますが、埋まってる設定なのでちょっと頭を出してる程度。
石は最近追加された [Atlas Scatter] ノードを使っていますが、Atlasで作成した石は本当に適当に作りすぎてヤバいレベルです。
この辺はゆるゆる会で解説したのでここでは割愛します。

このマテリアルには実はちょっとした仕掛けが施されています。
それがタイトルにあるWangのタイルです。
Wangのタイルとはどういうものかというと…面倒なので以下のサイトを参照してください。

www.pathofexile.com

図にあるようにテクスチャを16分割し、縦の青いライン、横の青いライン、縦の赤いライン、横の赤いラインはすべて接続が可能にします。
タイルに1~16の番号を付けた場合、 1番タイルの右側は赤いラインなので、赤いラインを左側に持つタイル(4番とか9番とか)と接続できるわけです。
このようにタイルごとの接続情報を利用していくらでも置き換えができ、かつ無限とも言えるような接続パターンで接続していける、いわゆるタイリングパターンを見せないでタイリングができるというわけです。

実際にUE4でやってみたのがこちら。

f:id:monsho:20200315205025p:plain

左は普通のタイリング、右はWangタイルです。
左はタイリングがバレバレですが、右はある程度のパターンが見えるものの、明らかにタイリングパターンがわかりにくくなっています。

では、どのように作成しているかというと、上のサイトで使われているものをほぼそのまま踏襲しています。
ただしノーマルやラフネスなども同様にWangタイルを作成する方法で、上のサイトのようなベースカラーだけWangタイルをして、その結果からノーマルを求めるという形は採用していません。

縦/横の青/赤ラインの対応する部分はすべて同じ場所からテクスチャを持ってきます。
ただの長方形をそのまま割り当てるのは良くないので、接続部はノイズで歪ませたブレンドマスクを使ってブレンドしています。
また、四隅の部分はすべてのタイルで接続ができなければならないので四隅だけはすべての場所で同じ場所からテクスチャを持ってきてブレンドします。
こうすることでそれぞれのタイルの対応するラインが接続可能になります。

注意点としては、接続に使用するテクスチャはあまり特徴的すぎないほうがいいという点です。
あまりに特徴的すぎるとその特徴が縦横に連続することになるので割とバレます。
残念ながら今回作成したWangのタイル生成用グラフはそういう特徴が出ないように手で調整するという形を採用していますが、よりよい結果を求めるのであればDeep Learningなどを利用して自動的にいい感じの場所をいい感じにブレンドしてもらうようにすべきでしょう。
現在のSubstance Designerには出来ませんが、将来的にはSubstance Alchemistでできるようになるかもしれませんね。

UE4のマテリアルはこんな感じです。

f:id:monsho:20200315210811p:plain

Customノードを多用していたり、なにげにDetail Normalを使っていたりしますが、コピペすれば多分使えるはずです。
今回はタイルパターンが元のUV値に合わせて自動的に生成されるようになっていますが、自前のパターンテクスチャを使用することももちろん可能です。
その場合は接続が正しくなるようにパターンを生成する必要があるので注意してください。

また、この手法は普通にテクスチャサンプリングしてしまうと接続部分でミップレベルがおかしくなるという弱点がありますが、こちらもミップレベルを自前で計算するという手法で対応しています。
その関係でテクスチャのサイズを指定しなければならないのがちょっと面倒です。

以下はWangのタイルでリンク場所がバレバレな失敗例。

f:id:monsho:20200315212824p:plain

四角いタイル形状が非常にわかりやすいですね。
これは元マテリアルにPerlinノイズによる緩やかな傾斜を与えた場合の結果ですが、なぜこうなるかというとノーマルマップを見るとうっすらわかります。

f:id:monsho:20200315213221p:plain

緩やかな凹凸から生成されたノーマルが思いっきりずれてしまっていて、ノーマルの段階でタイルの接続部がバレるようになってしまっています。
今回のSandy Groundマテリアルはこのような問題に引っかからないように注意して作成されているのです。

というわけで適当ではありますが解説はこれで終了します。
マテリアルはTrelloの方で公開していますし、UE4のアセットも内包するReadmeにリンクを書いておいたので、そこからダウンロードできるようになっています。

trello.com

興味がありましたらDLして調べてみてください。

BlenderとDem Bonesでブレンドシェイプをボーンモーションにする

つい先日のことですが、Electronic ArtsさんがDem Bonesというライブラリを公開しました。
GitHub上で、BSD 3-Clauseライセンスとなっています。

github.com

ヘッダオンリーライブラリで、依存しているのもEigenとOpenMPくらいらしいので、自前ライブラリに組み込んだりDCCツールのプラグインを作ったりしやすいと思います。

GitHub上ではコードの他にサンプル的なコマンドラインツールも付属しているので、とりあえず試してみるということが簡単にできます。
というわけで、Blenderブレンドシェイプとそれを使ったアニメーションを作り、これをDem Bonesで変換するという一連の流れを試してみました。

Blenderでベースメッシュを作成する

まずはBlender上でベースメッシュを作成します。
といっても普通にメッシュを作るだけですが、その段階でブレンドシェイプも仕込んじゃいましょう。

とりあえずはスザンヌさんにご登場願い、これをベースメッシュとします。
そして、ブレンドシェイプを適当に追加。

f:id:monsho:20200130230228p:plain

追加したシェイプキーを選択した状態でEdit Modeで頂点を編集すればブレンドシェイプを作成できます。
編集後、Object ModeでValueの値をいじってみてブレンドシェイプが有効になっているかどうかチェックしましょう。

ここまでのデータをFBXで出力します。
ここで出力したFBXのベース形状がボーンを埋め込んだスキニングメッシュのベースポーズとなります。

出力時の注意ですが、FBX Export設定のScaleを0.01にしておきます。
BlenderでのFBX出力サイズとAlembic出力サイズがどうも異なっている (というか、Dem BonesがFBXの単位を考慮してない?) ようなので、これをやっておかないとDem Bonesで変換したFBXのモーションがおかしくなります。

f:id:monsho:20200130231244p:plain

ブレンドシェイプアニメーションを作ってAlembicを書き出す

次にブレンドシェイプを使ってアニメーションを作成します。
特に難しいことはなく、時間変化に合わせてシェイプキーのValueにキーを打つだけです。

f:id:monsho:20200130231211p:plain

満足したアニメーションが出来たらAlembic形式で出力します。
特に設定は必要ないので、ファイル名だけ指定して出力しましょう。

Dem Bonesで変換する

GitHubからDem Bonesをダウンロードしたら bin フォルダの中に DemBones.exe があることを確認します。
そしてコンソールウィンドウを出し (PowerShellでOKですし、batファイルを作ってもいい)、次のようにしてDem Bonesを実行します。

DemBones.exe -a="Alembic File.abc" -i="Base Mesh.fbx" -o="Skeletal Mesh.fbx" -b=n

オプションの指定方法は -x=~ で、ファイルパスを示す場合は "" で囲みます。
-a オプションは必須で、ブレンドシェイプアニメーションを施したAlembicファイルパスを指定します。
-iオプションも必須で、こちらがベースポーズを指定するFBXファイルとなります。
-oオプションも必須で、こちらがボーンを組み込んだ出力FBXファイルです。
-bオプションはボーンの組み込みが行われていないベースFBXを指定する場合は必須となり、ボーン数を指定します。
ベースポーズのFBXには予めボーンを仕込んでおくことも可能なようですが、これは1つのメッシュに複数のブレンドシェイプアニメーションを施す場合に使用するのではないかと思われます。

とりあえずこれでデータに問題がなければ-oオプションで指定したFBXファイルが出力されるので、Blenderで読み込んでみましょう。
Import設定でScaleを100倍するのを忘れずに。

f:id:monsho:20200130233139p:plain

こんな感じにボーンが埋め込まれてモーションしたら成功です。
出力時のScaleを設定しておかないと、モーションしたらメッシュがクシャッとなってしまうので注意しましょう。

というわけで、簡単なDem Bonesの使い方でした。

いいところにビス打ちをしようと思って失敗した話

先日のSubstance Designerゆるゆる会にて出た話。

機械とかで形状のコーナーにビス打ちやネジ止めしたい場合にSDだとどうやってプロシージャルにやるの?
チュートリアル動画だと普通に位置を目合わせして配置してるけど、スマートじゃないしそれならPhotoshopでいいんじゃないか?
Houdiniならできるよ!←このセリフが大量発生

で、できらぁー!

えっ?いい感じの場所に完全自動で?

そんな中、参加者の一人がいいアイデアを出されてました。
仕組みとしてはベベルなどを利用して形状のコーナーと思われる部分だけを検出して [Flood Fill Mapper] で形状を散らすという方法。
なるほど、たしかにこの方法ならなんとかなるか?
とはいえ、四角形はなんとかなったけど六角形とかだとうまくいかないなど、いろいろ問題もあるっぽい。

私の方でも同じ方法でなんとかならないかと調整してみたのですが、色んなパターンを網羅するのが難しかったので別の方法を考えることにしました。
とはいえ、基本はコーナーを検出、Flood Fillで領域作成、Flood Fill Mapperで形状をばらまくという手法を採っています。
ただ、すこし凝った作りをして、エラーを最小限にしようと試みた次第です。

Flood Fill Mapperとは

[Flood Fill Mapper] ノードは Flood Fill の情報を利用するノードで、求められたAABBの範囲に収まるように指定された形状を配置するノードです。
形状のコーナー部分を検出し、Flood Fill でAABBが配置できれば形状を撒くのは難しくないのでは?と思われるかもしれません。
しかし、Flood Fill Mapper は Flood Fill に通した元形状の範囲でしか画像が出てくれません。

どういうことかというと、下の図を見てください。

f:id:monsho:20190901161522p:plain

この図はある形状(白)と Flood Fill のAABB(緑)、Flood Fill Mapperによる合成(赤)を示したものです。
Flood Fill のAABBは図のように三角形を取り込むわけですが、Flood Fill Mapper によって形状を撒いた場合、その形状が表示されるのは元の三角形の白い部分のみとなります。
つまり、黒い部分には形状は表示されませんので、ビス穴のようなものをAABBに対して撒いても正常な形状にはなりません。
コーナーを角丸などを駆使して検出した場合、くの字型になりやすいわけですが、その場合はくの字の中心部分は隙間となってしまうのでビス穴が発生しづらくなるというわけです。

なので単純にコーナーを検出するだけではなく、コーナーをそれなりに適切に拡大していってビス穴を取り付けられる程度の大きさと中身が詰まったAABBを作成しなければならないというわけです。

コーナー検出

コーナー検出は [Bevel] や [Non Uniform Blur] を利用して角丸を作り、元の形状からの差分で検出を試みました。
しかしこれは四角形であれば割と悪くない結果をもたらすのですが、六角形のように角の角度が甘い部分では検出結果が長く伸びすぎてAABBの精度がいまいちでした。

そこでもう少し検出結果を良くするため、[Pixel Processor] を使ったレイマーチ的な手法を用いることにしました。
図のようにあるピクセルから8方向にレイを飛ばし、エッジ部分を検出したレイの本数を計数します。
このレイの本数がしきい値を超えるようならコーナーとして認識し、白を設定、そうでない場合はコーナーではないとして黒を設定します。
レイは8ステップで処理し、パラメータで指定可能な距離を最大距離としてチェックを行います。

レイ1本分の処理は Pixel Processor で以下のように実装しています。

f:id:monsho:20190901170849p:plain

これを角度を変えて8方向に対して処理しています。
コーナー検出の結果は以下のようになります。

f:id:monsho:20190901171205p:plain

左の入力画像に対して右の結果が求められます。
しかしながら、1種類では完全に検出できなかったので、実際には2種類の検出結果をそれぞれ [Blur HQ] + [Histogram Scan] で小さすぎるものや細くラインになってしまったものを除去しています。

コーナーを拡張

コーナーは検出できましたが、これだけではビス穴をつけられません。
ここからはコーナー部分の画像を拡張して、ビス穴を打てる程度の大きさを確保します。

拡大は単純な方法として [Distance] ノードを使用する方法がありますが、単純に適用してしまうと隣のアイランドに接触してしまったりします。
これではおかしな部分にビスが打たれてしまうので、拡大方法も少し考えないといけません。

今回適用したのは少しだけ拡大して元形状でマスクと言う手法を繰り返す方法です。
マスクは元の形状情報を利用します。形状からはみ出した部分を削除するためですね。
複数回繰り返すことで元形状の内部にのみコーナーマスクを広げるわけですが、広げすぎても問題がありますので回数は10回以内で選択できるようにしました。
このためのグラフ、CornerExpand グラフは以下のような実装となります。

f:id:monsho:20190901173123p:plain

終結

このようになりました。

f:id:monsho:20190901173556p:plainf:id:monsho:20190901173626p:plainf:id:monsho:20190901173651p:plain

四角、五角、六角ではパラメータさえきちんと調整すればうまくいきます。
しかし、このような形状の場合はうまくいきません。

f:id:monsho:20190901174227p:plain

このような形状の場合は丸の中心にビスを打ちたいと思うはずですが残念ながらうまくいきません。
このような場合は別途の手法を用いたほうがいいでしょうね。

というわけで、一定の成功はあったものの、最終的には失敗してると言えるような結果となりました。

Tile SamplerのVector Map Inputで正しい方向を向かない場合の対処法

第3回SubstanceDesignerゆるゆる会にて、Tile SamplerのVector Map Inputについての問題が提示されました。
ある形状のエッジ部分にエッジに垂直になるように形状を配置したいが、Tile SamplerのVector Map Inputだとたまにおかしな方向を向くというものでした。

Tile SamplerノードのVector Map Inputは、入力されたノーマルマップの勾配方向に向かって入力形状を回転する機能です。
これを利用すると、ある形状のワッペンのようなマテリアルを作成し、そのエッジ部分に縫い付けたような糸を想起されるような形状を作ることが出来ます。

では試してみましょう。

f:id:monsho:20190609010105p:plain

適当な形状を作り、これをワッペンの形状とします。
そのエッジを検出し、これをTile SamplerのMask Map Inputに入れます。
Tile Samplerの[Mask Map Threshold]を1.0にすれば与えられた形状の場所のみパターンが描画されます。

次にこのマスクにBlurを適用し、ノーマルマップを生成します。
これによりエッジに垂直な勾配が出来ますのでこれをVector Map Inputに接続します。
あとは[Vector Map Multiplier]のパラメータを1.0にすれば入力した細い線の形状がエッジに垂直に出てくるはずです。

その結果がこちら。

f:id:monsho:20190609011535p:plain

正しく出てる…ように見えて一部は正しく出ていません。
耳のあたりの一部の線がエッジに垂直ではなく、エッジに沿っているようになっているのが見受けられるかと思います。

このようになる理由はVector Map Inputに起因します。
Vector Map Inputの挙動は、入力したノーマルマップのRGの値から勾配の方向を調べ、アークタンジェントを使って回転角度を求めています。
この方法では正しく傾きが出ている部分はいいのですが、正しく出ていない部分、例えばノーマルの方向が上向きになっている部分では正しく勾配方向が求められないことになります。
そのような場合は向きがおかしくなってしまうわけで、これは計算上仕方がないです。

ではどうすればいいかというと、Mask Mapとして入力したものからノーマルを計算する必要は別にないわけです。
つまりこうすればいい。

f:id:monsho:20190609082200p:plain

Edge Detectする前のノードにEdge Detectをブレンドしてまずは膨らませます。
これでTile Samplerで配置される位置は全てカバーできます。

次にBevelで内側に破綻しない程度に大きく傾きを付けていきます。
これでエッジ部分には外向きの傾斜が出来ますので勾配方向はすべての場所においてエッジの外側に向きます。
これでほぼ完全に正しい方向に対応できるようになるはずです!

f:id:monsho:20190609082653p:plain

エッジに沿っていたものがなくなり、綺麗に並んでいるのがわかるでしょう。

この問題の対処方法は他にもいくつかノードの組み合わせがあるのですが、概ね”正しい方向を正しく向かせられるノーマルを作成する”という単純かつ正しい方法でほぼ全て解決できます。
逆に言えば、うまく行ってないのはノーマルが勾配方向を示すものとしては正しくなっていないからと言うことになります。
ただしパッと見ではその正しくなさがわかりにくいと思いますので、出来るだけ”正しくならざるを得ない”ようなノーマルをVector Map Inputに提供するようにすることが大事でしょう。

ハハッ

f:id:monsho:20190609085242p:plain