Help us understand the problem. What is going on with this article?

UE4のUnlitでトゥーンレンダリングをキメる!

More than 1 year has passed since last update.

こちらは Unreal Engine 4 (UE4) Advent Calendar 2018 17日目の記事です。

UE4でVRMのマテリアルを作っていて思った。
「Unityでは可愛く描画できてるのに… UE4のコレジャナイ感はなんなの?」

UE4のUnlitでトゥーンレンダリングをキメる!

Unlitでマテリアルを組むと、想定よりも色味が薄く出力されます。
トゥーンレンダリングに限らず、UE4のNPRは大概この問題にぶち当たるように思います。

この原因を探り、根本的な対処をすることが今回の目的です。

先に結果

左:コレジャナイ。Unlitでテクスチャを貼ったもの。
右:キマったもの。FilmToneMapInverse利用。
シーンはデフォルトマップです。

https://3d.nicovideo.jp/alicia/
image.png
https://vroid.pixiv.net/
image.png

これらを踏まえて、ベースをLITにしてライトを適用、PostProcessで調整したもの

https://vroid.pixiv.net/
image.png

想定に近い色が出るようになりました。PostProcessも素直に反映されています。
なおキャッチー(モデラーさんに感謝)なスクショはこれだけです。
後は地味で分かりづらい内容が続きます。。

検証にはUE4.20.3 WindowsでSDRモニタを利用しています。
おそらくHDRモニタでは結果が異なります(私は未確認です)
色空間やモニタについては以下のEpicさんの資料を合わせて読むと 理解が深まると思います。
https://www.slideshare.net/EpicGamesJapan/ue4-v11

一部 理解が浅く、解説がふわふわした箇所があります。気になる方はご自分の手でご確認ください。
間違いあったらすみません。ツッコミをお願いします。

方針:テクスチャの値をそのまま出す

大方針は「テクスチャの値や手入力値」と「出力値」を一致させることです。
また汎用的に利用したいので、既存の描画機能と共存できるもので、エンジン改造なし、マテリアルで完結するものを探ります。

コレジャナイの原因:FilmicTonemapper

先に話してしまいますが、原因はこいつです。
https://docs.unrealengine.com/en-us/Engine/Rendering/PostProcessEffects/ColorGrading
UE4.15でしれっと有効化されたこの機能、バージョンアップで苦しんだ方も多いのではないでしょうか。私は苦しかったです

それなりな見栄えを求める場合、トゥーンレンダリング向けの決定打はなさそうです。
正直なところ改造して真面目に実装するか、PostProcessで頑張る方が早そうな感じはします… が今回はマテリアルで頑張ります。
先に私見をまとめておきます。アイデア次第では、全ての場面で破綻しない実装方法がある… かもしれません。

対処方法 入力と出力が一致する PostProcess使える 背景に影響しない モバイルで利用できる 補足・懸念
なにもしない ×
r.TonemapperFilm=0 将来的に廃止されるらしい?
自前のTonemap
またはShowFlag.Tonemapper=0
×
Lit+PostProcess × × ForwardShadingで利用できない
カラーグレーディングやLUTで調整 手間がかかる
FilmToneMapInverseを利用 正しい処理か不明

◎:問題なし ○:厳密には違うが許容範囲  △:許容できるか微妙  ×:全然ダメ
説明は省きますが、MToonの機能を阻害しないことが前提です。
以前にまとめた記事があるので、ご興味ある方はどうぞ。

順を追って検証していきます。

検証

検証1 入力値をそのまま出す

vector3で(入力=出力)を目指す

Unlitでこんなマテリアルを組んでみます。

image.png
image.png

出力されてほしいのは、
255 * 0.25 = 63.75
255 * 0.50 = 127.5
255 * 0.75 = 191.25
ですが、それより大きな値(158,191,209)が出ました。
これはTonemapの影響です。ひとまず無効化しましょう。
このようなPostProcessMaterialを作成しTonemapを置き換えます。

image.png
image.png

(64,127,191)で、ほぼ一致しました。
おそらくですが、「Replacing the Tonemapper」では、Tonemapに加えてその後段のDisplayGammaまで置き換わるようです。

余談ですが、今回の色の確認にはWindows標準のペイントブラシのスポイト機能を利用します。
UE4のスポイトは、LinearColorに変換した値が格納されます。
試しにエディタで上記の色を取得すると、

image.png

(0.0512, 0.2122, 0.5209)という異なる値が出ました。

UE4のsRGBからLinearへの変換は、以下のようなコードを利用していますので、、

GammaCorrectionCommon.ush
half3 sRGBToLinear( half3 Color ) 
{
    Color = max(6.10352e-5, Color); // minimum positive non-denormal (fixes black problem on DX11 AMD and NV)
    return Color > 0.04045 ? pow( Color * (1.0 / 1.055) + 0.0521327, 2.4 ) : Color * (1.0 / 12.92);
}

これをcol=(0.25, 0.5, 0.75)にあてはめると、、

R=0.25 G=0.5 B=0.75
スポイトした値 0.0512 0.2122 0.5209
sRGBToLinear変換した値 0.0508 0.2140 0.5225

まだ差があって気になりますが… 本題ではないので深追いしません…

テクスチャ値で(入力=出力)を目指す

このTonemapでUnlitのテクスチャを出力してみます

マテリアル
image.png
利用したテクスチャ
image.png
レンダリング結果
image.png

値が小さく(絵的には暗く)なりました。
これはテクスチャの値がsRGBからLinearColorへ変換されているからです。
これを無効化するため、テクスチャのsRGBチェックを外します。同時にサンプラはLinearColorにします。

image.png
image.png
image.png

元テクスチャと完全に一致しました。

同じ値は出ましたが、このままでは全体的に暗く、HDRを活かした絵が出ません。続きます。

検証2 TonemapなしDisplayGamma有り を再現する

コンソールコマンドより ShowFlag.Tonemapper=0 を入力すると、Tonemapが無効化されます。
手始めにPostProcessでこの見た目を再現してみます。

中身は簡単で、ざっくりと、
OutColor = pow(InColor, 1/2.2);
とすれば完成です。

image.png
image.png

それっぽい見た目になりました。

Tonemapなしで vector3の(入力=出力)を目指す

DisplayGammaを打ち消すために、pow(x, 2.2)を追加します。

image.png
image.png

(64,127,191) で元と一致しました。

Tonemapなしで テクスチャ値の(入力=出力)を目指す

先程のPostProcessで テクスチャ(sRGBチェック入り)をUnlitで出してみます。

image.png
レンダリング結果とテクスチャを交互に表示したもの
01.gif

だいたい合っていますが、厳密な数値では差(256段階で1~2程度)が出ています。
これはおそらく、sRGBToLinear(col)とpow(col,2.2)の差です。この2つは近似に利用されるくらい似たようなカーブを描きます。

厳密に値を合わせるならば、テクスチャのsRGBチェックを外して、自前でpow(col,2.2)を追加すればOKです。

image.png

元テクスチャと完全一致します。

これでHDRを活かしつつ、値をそのまま出すことができました。大抵はこの手法による対処で問題ないと思います
※ただし、PostProcessは効きません。
※PostProcessを使う場合は、差異を許容してr.TonemapperFilm=0で上記設定を利用するか、r.TonemapperFilm=0自体を打ち消すマテリアルを作成する必要があります。

それではいよいよ、UE4のデフォルト設定と馴染ませやすい方法を探ってみます。続きます。

検証3 FilmicTonemapper上で(入力=出力)を目指す

本題です。
テクスチャの値をFilmicTonemapperで出力してみましょう。
初期状態に戻すため コンソールコマンドで r.TonemapperFilm=1、ShowFlag.Tonemapper=1 として、FilmicTonemapperを有効化します。

image.png
image.png
レンダリング結果とテクスチャを並べたもの
image.png

矢印で示した帯はテクスチャ画像です。
だいぶ差があります。特に目立つのは、左下の白や左上の肌、青系全般も気になります。
UE4の素のUnlitで白色や肌色がクッキリ出ないのは この影響でしょう。

対策1:FilmicTonemapperのパラメータで頑張る

結論だけ言ってしまえば、この方法では調整は厳しそうです。
そもそも入力値1をそのまま出力1とするためには、slopeの値を1以上にするか、他のパラメータ(DisplayGammaやExposure、マテリアル自体)をいじる必要がありそうです。
なんとなく近似できる組み合わせはありそうなのですが…

下図は初期パラメータのグラフで、入力1に対して、出力は約0.75 となっています。
つまり真っ白を想定して1を入力しても、実際はx0.75された灰色が出力されます。

image.png

UE4ToneMapper:https://www.desmos.com/calculator/h8rbdpawxj
こちらを参考にさせていただきました:https://technorgb.blogspot.com/2018/11/ue4.html?spref=tw

対策2:FilmToneMapInverse を利用する

なんとか出来ないかシェーダを眺めていると、FilmToneMapInverse という関数を見つけました。
※ちゃんと読めていません。関数名から察するに、FilmicTonemapperの逆変換をしてくれるのでしょう… が、パラメータが固定されています。特定の条件下での簡易的な逆変換のように見えます。

藁にもすがる思いで組み込みます

家弓メソッドです
image.png
image.png
image.png

そこそこ合いました。が、この差を許容できるかは微妙なラインですね…

検証4 LitのBaseColorを利用して(入力=出力)を目指す

ここまでの検証は「Unlitのマテリアル内でTonemapを打ち消す手法」に注力してきました。
今回はアプローチを変えて、値をLitのBaseColorに格納し、それをPostProcessで拾う手法を試してみます。

マテリアル
image.png
ポストプロセスマテリアル
image.png
レンダリング結果
image.png

値が小さく(絵的には暗く)なりました。
検証1でやった通り、画像のsRGBチェックを外せば一致します。
毎回sRGBチェックを外すのも手間なので、今回はLinear2srgbを利用してマテリアルで対処してみます。
中身はGammaCorrectionCommon.ush の LinearToSrgbBranchingChannelにあります。

image.png
image.png

テクスチャと色があいました。
当然ながら、シーン全体がBaseColorで描画されてしまっています。空はUnlitなのでBaseColorを参照すると真っ黒になっています。
対処する場合は、CustomDepth等を利用したマスク処理が必要でしょう。
また、TemporalAAを利用している場合はジラ付きが発生します。
FXAAを利用しましょう。(MSAAが利用できない理由は後述します)

マスクを利用すると、、

image.png

対象のモデルのみテクスチャ値が出力され、
他はFilmicTonemapperが効いている絵が出ました。

ただしこれはGBufferを利用しているためForwardShadingでは利用できません(=MSAAも利用できない)
また対象のモデルにはPostProcessが効きません。

検証おまけ UE4とUnityで同じ値を出力する

以下は素のUE4とUnityそれぞれで、Unlitのマテリアルに(0.25,0.5,0.75)を入力した結果です

image.png
image.png

同じ入力値でもUE4の方が大きな値に(絵的には明るく)なっています。
「カラーを入力してグレイスケールのテクスチャと乗算する」ような場合、同じ数値をそのまま使うと結果がずれます。

見た目を素のUnityに寄せるならば、UE4側の入力にsRGBToLinear(col)が必要です。
(環境や設定によって結果は異なることがあります)

まとめ

これであなたのUnlitもトゥーンレンダリングがキマったでしょうか。

ただ解説途中でも触れたように、対処方法によっては既存のPostProcessが使えなかったり、他モデルと並べて表示すると破綻する場合があります。
ゲームであれば、エフェクトや半透明との相性、描画負荷の問題もあるでしょう。
デバイスによってはForwardShadingで破綻ないか確認する必要もあります(モバイルやスタンドアロンのVRデバイス)

十分吟味の上、適した手法を探ってみてください。

宣伝:冒頭のスクショはVRM4Uを利用しました

冒頭のスクショはVRM4U(VRMローダプラグイン)を利用したものです。
もし興味あれば下記リンクからお試しください。近々VRoid Hubと連携可能になる… 予定です。
https://github.com/ruyo/VRM4U/

以上です。

ruyo
bandainamcostudios
バンダイナムコスタジオは、家庭用ゲームソフト、モバイルコンテンツ、の企画・開発・運営、ゲームに関する技術研究・開発を行っている会社です。
https://www.bandainamcostudios.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away