だらけ者だらけ

だらけ者だらけの遊び場

CustomDepthを拡張して色も出力してみる。

この記事は UE4 Advent Calender 2015 その1 の16日目の記事にあたります。

前回の記事は@nano06126728さんの何か適当にだしてみますでした。


やったこと: カスタムデプス計算時にカラーもできるようにしました。

本来のマテリアルでは、PixelDepthOffsetが一番下に来ていると思いますが、そこにCustomColorという項目を追加し、好きな値を入れられるようにしました。

f:id:tempkinder:20151210225715p:plain

ここで出力したCustomColorはG-Bufferの拡張領域となりますので、ポストプロセスマテリアルのSceneTextureで参照することができます。

f:id:tempkinder:20151210225722p:plain


非対象者: 拡張したいエンジニアの方へ

具体的な拡張の話はしません。使い道と仕組みについて簡単に説明します。もしも具体的な拡張方法が気になる方は以下のコードに従ってください。 

if (エンジニアでしょうか?)
{
	return; //このブログを閉じて、PixelDepthOffsetの実装をまねてください
}


なぜ実装した?: ①日本で流行るノンフォト、CustomDepth/Stencilをマスクとして行っているものもある。

ご存知の通り、ノンフォトレンダリングが日本で流行っており、UE4でも様々な方が挑戦しております。エピックゲームズジャパンの下田さんも、UnrealFes2015でノンフォトの講演を行っています。
forums.unrealengine.com

ここで、らりほまさんの下記のブログを見てみると、CustomDepthによるエッジのマスクの記載があります。

D言語くん以外のオブジェクトにもクリース線と色の境界線が描画されてしまっているので、マスキングを行います。CustomDepth をマスクとして利用します。
UE4 にて特定の Actor に対して輪郭線・クリース線・色の境界線を描画する、あるいはD言語くんを真の姿にする方法 | rarilog

下記の画像のように陰や背景に不要にエッジがかかってしまうのを防ぐために、マスクが必要という訳です。らりほまさん、画像の転載許可ありがとうございます!!

http://rarihoma.xvs.jp/data/2015/02/22/1/14.png


なぜ実装した?: ②Customdept/Stencilhでは情報が足りない。

便利なCustomDepthですが、そのオブジェクトが出力したデプス値を保存する機能なのでので、ユーザが自由に変更することはできません。

f:id:tempkinder:20151210235145j:plain

そのため、UE4.9から、CustomStencilによって好きな値を1つ出力できるようになりました。

f:id:tempkinder:20151210235350j:plain

しかし、このステンシルも問題があります。このステンシル値は、メッシュのレンダリング項目でのみ設定できます。

f:id:tempkinder:20151210235550j:plain

つまり、メッシュ単位で任意の一つの値しか出力できません。1つのメッシュの中でも、キャラクタの目や眉毛にはエッジをかけてほしくないとか。。。鼻筋のエッジラインは非常に細くしたいとか。。。エッジをより詳細に調整したいとなると、ステンシルやデプス値では限界があります。そんなこんなで、デプスもステンシルも出力するならば、カラーも一緒に出力するようにすればいいじゃん。そして、その値を好きにマテリアルで調整できれば、マスクだけじゃなくて特殊な使い方をしたい場合など、幅広く貢献できるかなと思って作ってみました。

使用例

ごめんなさい。具体的にノンフォトレンダリングに適用したかったんですが、アセットが間に合いませんでした。。基本的には、好きなものを出力して好きに使えばよいだけです。一例としては、カスタムデプスやステンシルのマスキングでは制御できないようなマスクを作ることでしょうか。例えば下記のように、そのままのエッジ抽出アルゴリズムだと、目、口、鼻に過剰なエッジが出てしまいます。(実はこのエッジアルゴリズムだと眉毛もほんのり赤くなる。)

f:id:tempkinder:20151213213826p:plain

CustomColorでマスキングして省くことができます。

f:id:tempkinder:20151213213839p:plain


後はマテリアルIDなど、エッジの色や太さの制御など、やりたいことをご自由にというものです。このようにエッジを細かく制御して実際の製品で実現させたのが、俺屍2です。CEDECで具体的な実装について説明していますので、ご参考にしてください。ノンフォトレンダリングは、制御の難しさから、検証レベルまではいけるもののプロダクションレベルまで持ってくのが非常に難しい技術かと思います。これを携帯器PSVitaで実現させた素晴らしい成功例かと思います。
cedec.cesa.or.jp
www.inside-games.jp



注意点: CustomDepthには負荷があります。オブジェクトをもう一度投入してレンダリングします。

f:id:tempkinder:20151210231613j:plain

ProfileGPUを見るとG-Bufferを作成する"BasePass"というものとは別の場所にCustomDepthという項目があります。CustomDepthがOnのオブジェクトは、もう一度GPUに投入されデプスが計算されるため追加の負荷がかかります。(本当は、もう一度投入するなんて暴挙に出るのならばカラーも出してしまえと思って拡張をしたのが発端です。)

読んでいただきありがとうございます。

f:id:tempkinder:20140112120104j:plain

明日は、ntaro@年末に向けて準備中 (@tarotarokun) | Twitterさんの「標準のUE4だとWindowsのマルチタッチが使えないための対応方法を書きます」です。インプットデバイス関連には疎いので、勉強させていただきます!

Stationary Light の影について

この記事はUnreal Engine 4 (UE4) 其の弐 Advent Calendar 2015の16日の記事です。昨日はじゅる (@xxJulexx) | Twitterさんの映像クリエータ視点な使い方とか。でした。そもそもコンポジットをオフラインですることを前提に、BasePassと一部のポストエフェクトをそのまま出力できる機能があれば、UE4の映像制作がもっと捗りそうですね。

今回はライティングの細かい話をしようと思います。なんとなく設定している人が多い、ステーショナリーライトの仕組みです。カタカナと英語が乱立しますが、気が向いたら直させてください。

UE4のライティングは様々な組み合わせに応じて内部実装が変わります

f:id:tempkinder:20151215020601p:plain
高クオリティなライティングを出しつつも一定のパフォーマンスを出したい場合などでは、作成したライティング環境が内部でどんな計算をしているかを把握する必要があります。
今回は、不透明(Maskedも含む)オブジェクトに対して、Stationary Lightの影がどんな動作をするかを簡単に説明していきたいと思います。
話はStaticなMeshかMovableなMeshかで二分されます。



Static Meshの影について

Stationary Lightはなんで5個以上重ねちゃいけない?

ステーショナリーライトの陰ですが、公式サイトではこのように述べられています。

ライトマス は Stationary light (固定ライト) に対し ディスタンス フィールド シャドウマップ を生成します。
...
ライトは、シャドウマップ テクスチャの異なるチャンネルへの割り当てが必要なため、 4 つまたはそれ以下のオーバーラップした Stationary light (固定ライト) のみが静的シャドウを表現することができます。
Unreal Engine | Stationary lights (固定ライト)

どれか一個のオブジェクトに5個以上のステーショナリーライトが重なると、そのライトの中の4個はステーショナリーライト用のシャドウマップでレンダリングできますが、残りのライトはシーン全体を動的にシャドウ計算することになります。5個以上ステーショナリーライトが重なると、UE4は自動的に、どのライトを動的シャドウ計算にするか決めます。下の図で×印がついているのが動的計算とみなされたライトです。残念ながらどのライトを仲間外れにするかを自分で指示できなそうです。
f:id:tempkinder:20151214010733p:plain

ここまではみなさんご存知かとお思います。ですがこれ、なんでなんでしょう??



ステーショナリーライトのシャドウマップを持つのはライトではなく、(スタティックな)メッシュです。

ステーショナリーライトのシャドウマップは、シーンに置かれた各スタティックメッシュが持ちます。スタティックライトがライトマップにベイクされるのと同様です。この消費データは、Statを見ると NumSM / TextureSMといった項目で確認できます。

f:id:tempkinder:20151214011958p:plain

NUM SMが5と書いてあるのは、重なっているライトもカウントされているからですが、TextureSMのサイズを見ると、4個のマップを持っているものとデータ量が変わらないのがわかるかと思います。実は、ステーショナリーライトは自身にライトのインデックス番号を1つ(0~3の値)持ち、そのインデックスに対応したシャドウマップに自身の影を書き込むのです。このインデックスはUE4が勝手に内部で決めます。
f:id:tempkinder:20151214020309p:plain
どのライトが仲間外れになるのか? 気になる方はULightComponent::ReassignStationaryLightChannels関数の中身を参考にすると良いと思います。


Stationary LightのShadow Mapは、G-Bufferを経由してライティング計算時に参照されます

f:id:tempkinder:20151214022049p:plain
上図のように、スタティックメッシュに焼かれたシャドウマップはG-Bufferに埋め込まれて、ライティング計算に送られます。ステーショナリーライトのための計算がG-Bufferの前後にあるのは非効率に見えるかもしれません。ですが、ランタイムでのデプス比較を用いたシャドウ計算がないため、高速かつ高精細な影がステーショナリーライトで実現できているのです。
これが、スタティックなメッシュがスタティックなメッシュによって受けるステーショナリーライトの影の計算です。つまり、動的オブジェクトが関係する場合、もちろん挙動は変わります。



Movable Meshの影について

Movable ObjectがStationary Lightに及ぼす影

次はMovableなメッシュがStationary Lightが及ぼす影についてです。上記でだらだら話していたシャドウマップのことはもう全く関係ありません。忘れてください。話を変えて、以下のようなポイントライト一個のシーンを考えます。周りに浮いているのはすべてMovableに設定されたオブジェクトです。(注: Directional LightはCascade Shadowが使わるので今回はポイントライトやスポットライトに限定します。後で説明します。)
f:id:tempkinder:20151215013519p:plain

このとき、Stationary Lightは、MovableなMesh毎にシャドウマップをつくります。実際にシャドウマップをキャプチャすると以下のようなテクスチャです。
f:id:tempkinder:20151215013811j:plain
左上に小さくオブジェクトが描画されているのがわかるでしょうか?巨大なシャドウマップ一枚をグリッド上に分割し、各オブジェクトが及ぼすシャドウマップを作っています。

その後、全てのMesh毎に、Shadowの影響をレンダリングをします。実際に一つのオブジェクトに対してライティングされた結果を見ると、以下のような黄色い部分が、1つのあるオブジェクトが関与する影部分の描画です。(描画範囲はShadow Frustumのステンシルテストで指定しています。)
f:id:tempkinder:20151215014324j:plain

つまり、Movable Meshに於けるStationary Lightの影のコストは以下になります。

Movable MeshにおけるStationary Lightの影のコスト =
ライトの影響下にあるMovable Meshの数 * 一つ一つのシャドウ生成及び描画コスト



Stationary LightのShadowは、Movable Mesh毎に計算される

(Directional LightはCascade Shadow MapやDF Shadowによりこの制限を受けません!Directional Lightの挙動はUE4 Docを参照してください。)

さて、先ほどのように、Stationary LightのShadowは、MovableなMeshに対して一個ずつShadow Mapが作成され、レンダリングも一個ずつ行われると言いました。その状況で例えば、ライトの影響範囲内に沢山Movable Meshをおいてみます。
f:id:tempkinder:20151215014837p:plain

これだけ置いた後のShadowMapは以下のように、ShadowMapで描画されるオブジェクトが増えているのがわかります。
f:id:tempkinder:20151215014934j:plain

そして、そのあとこの大量のオブジェクト1つ1つに対して影の影響を計算します。
f:id:tempkinder:20151215015420j:plain

実際にProfileGPUで見てみると、ShadowMap作成フェイズのShadowDepthsFromOpaqueProjectedで大量のメッシュ毎のシャドウマップが作成されているのがわかります。上図の赤いシャドウマップを作成しているフェイズですね。
f:id:tempkinder:20151215015524p:plain

そのあと、実際の影の影響を計算するフェイズのShadowProjectionOnOpaqueを見てみると、各メッシュに対して、実際のシャドウを計算しているのがわかります。
f:id:tempkinder:20151215015834p:plain

これがStationary Lightの影響範囲内にMovableなMeshを大量に置くと重たい理由です。

Stationary Lightは、Movable Mesh毎に別々に影の計算をする。別々に影の計算をするため、Movable Meshが増えれば増えるほどコストの増大が激しい。この一方、LightをMovableにすると、ShadowはMesh単位ではなく、ライト一個に対してまとめて計算されます。
なので、Stationary Lightの処理負荷はMovableなMeshの数に依存してしまうので、このように動的なオブジェクトに対する影響が多いライトはMovableにしたほうが影のコストが下がることがあるのです。

確認のため、試しに、ライトをMovableにしてみます。
f:id:tempkinder:20151215133423p:plain

そうしてプロファイリングすると、影描画がWholeSceneという一つにまとめられて、合計の描画コストも下がっているのがわかります。シーン全体で一つのシャドウマップを作成し一回でレンダリングするMovableのライトの方が、MovableなMesh毎に影を描画するStationary Lightの影よりも処理負荷が軽くなる良い例かと思います。
f:id:tempkinder:20151215134845j:plain



Directiona Lightの影について

f:id:tempkinder:20151215132442p:plain
。説明の通り、ライトの影響下にあるMovableなMeshはそのまま影生成のコストにつながります。今までの説明はPoint Lightで行いましたが、Directional Lightは基本的にシーン全体に影響を及ぼすのでこれでは問題です。そのためもあり、Directional Lightは基本的に、影描画をCascade Shadow Mapで行うようにしています。ですので、上記のシーンをプロファイルするとカスケードシャドウによってシーンが分割(Split)されて影描画されているのがわかります。
f:id:tempkinder:20151215132721j:plain

ですので、普段使いしている際Directional LightでMovableなメッシュによるコストだけが気になることは少ないと思います。一応Cascade Shadowの外のオブジェクトをポイントライトと同じ手法によって影描画するオプションがあります。Cascade Shadow Mapの設定のInset Shadows For Movable Objectsです。
f:id:tempkinder:20151215132839j:plain

こちらを設定し、Dynamic Shadow Distance Stationary Lightの距離を小さくして全てのオブジェクトをカスケードの外に出してみます。すると下記のように、オブジェクト毎に影描画用のフラスタムが作成されることが見て取れるかと思います。
f:id:tempkinder:20151215133221p:plain

プロファイルしても各オブジェクトのシャドウコストが発生しているのがわかります。
f:id:tempkinder:20151215133354j:plain



読んでいただきありがとうございます。

f:id:tempkinder:20140112120016j:plain

まとめると、Stationary Lightが作る影は。。。

  • StaticなMeshの場合、そのシャドウマップは(ライトマップと同様に)事前に計算され、各Meshが持ちます。レンダリング時はその値を読み込むだけで影の値がわかるので処理負荷が軽いです。
  • MovableなMeshの場合、そのシャドウはMesh毎に動的に計算されます。沢山のMovableなMeshが影響を受ける場合、Movable Lightにしたほうが処理が軽いことがあります。

明日は、ntaro@年末に向けて準備中 (@tarotarokun) | Twitterさんの「C++を使ったSocketを使った通信周りの書き方を解説するよ」です。
通信周りの情報がどんどん充実してきてて非常にありがたいです!楽しみです!

パーティクルにLight追加したらGPU負荷が跳ね上がった話

発端: パーティクル置いたら"Tiled" Deferredが走るようになった。


何もないシーン。 f:id:tempkinder:20141226170124j:plain

プロファイルしてみる。ライティングは1.0msぐらい。

(Startar Contetnsの)P_Sparksをおいてみた。

プロファイルしてみる。ライティングは4.03ms!!!!

TiledDeferredという処理が走るようになっている。

どうして? パーティクルにライトモジュールがあるからです。

ということが起きました。実は、パーティクルのライトが一個でもあると、裏でこの"Tiled Deferred"が走ります。

この処理、非常に大雑把に言うと、ライトが影響を与える画像の範囲を前もって調べ、不要なピクセルへの描画処理を省く手法です。 ライトがシーンに沢山ある場合は非常に高速になるのですが。。。ライトが高々数個の場合は逆で、この”前もって調べる”という部分のコストが一定量かかってしまい、それらが処理負荷を増大させてしまいます。 仕組みについて詳しく説明しません。案の定いつもどおり要するに率直におおかたの予想通り平たくところはばからずに正直に言ってしまえば、もんしょさんがこちらについて詳しく書いておりますので、そちらを参考にしてください。

DirectXの話 第125回 - もんしょの巣穴

余談ですが、UE4の基本設定では、普通のポイントライトを80個以上置くとTiledDeferredが走ります。(もんしょさん、ありがとうございます。) 逆を言うと、それぐらいライトの数が増えないとTiledDeferredによる高速化の恩恵を得られないということです。なのに、パーティクルのライトは一個でもあると、このタイル化処理が走ってしまうのです。

では、パーティクルライトを使う&Tiled Deferredを走らせない という方法は取れないか?少し調べてました。.iniの設定等ではできなそうです。

ありました!.iniファイルに

r.TiledDeferredShading=0

と記載するか、下図の様に、Blueprintで直接コンソールコマンドを打ち込むことで、普通のディファードレンダリングにすることができます。(一括での変更のみ。このパーティクルライトだけTiledで。。。とかはできません。)

パーティクルライトは非常に綺麗なので使いたい人は多いと思います。 このパーティクルのライティングについてはtoaru_designerさんの以下のスライドが非常にわかりやすく説明していますので、こちらを参考にしてください! ただし、パーティクルライトの数が少ない場合は、上記のコマンドでTiledDeferredを走らせない方が良いでしょう。

まとめ

問題
パーティクルのライトモジュールを置くとTiledDeferredというものが走ってGPUコストが上がる。ライトが沢山ある場合は高速な機能だが、ライトが少ない場合は逆に負荷を高めてしまうのでOffにしたほうが良い。

TiledDeferredをOffにする方法
・”r.TiledDeferredShading=0”をDefaultEngine.iniに追加
・もしくはexecute console commandで”r.TiledDeferredShading 0”と打ち込む
備考
これはSM5が動くPC版等での設定です。試していませんが、モバイル版ではこのTiledDeferredの処理は走りません。試してませんが。試してませんけど。誰か試して。


原因: なんでTiledDeferredが走るの?

本件の問題となるコードも載せておきます。(UE4.6で検証) FDeferredShadingSceneRenderer::RenderLights()(RenderLights.cpp)内の以下コードです。

if (NumSortedLightsTiledDeferred > 0 || SimpleLights.InstanceData.Num() > 0) 
{
    // Update the range that needs to be processed by standard deferred to exclude the lights done with tiled
    StandardDeferredStart = NumSortedLightsTiledDeferred;
    RenderTiledDeferredLighting(RHICmdList, SortedLights, NumSortedLightsTiledDeferred, SimpleLights);
}               }

SimpleLightsが一個でもシーンにあると自動的にTiledDeferredが走ります。 このSimpleLightsがどういう過程で生成されるのか詳しく見ていませんが、パーティクルのLightモジュールは、SimpleLightsとして生成されるようです。

この部分を読むとモバイルなどではタイルベースの処理は走らないようになっている様ですが、試してはおりません。

読んでいただきありがとうございます。

f:id:tempkinder:20140112115953j:plain

なんか見つけたらちょいちょい書いてきます。

【UE4】被写界深度半透明物体共生戦略手法零式(Type-0)

※UE4フォーラムに本記事のスレッドを立ち上げております。
半透明描画とDOF(被写界深度)を両立させる方法について(2014 Advent Calender)



わかりにくいタイトルでごめんなさい。つまり、

半透明描画にDOF(被写界深度)を適切にかける手法を、色々試してみました。
というのが、今回の記事内容です。ソース拡張や独自ポストプロセスは入っていません。エディタ内でできることの調査です。
(被写界深度は文字が硬い感じがするので、英語のDepth Of Fieldを略した"DOF"と以後表現します。レンダリング界隈の人々はDOF(ドフ)と呼んでいるみたいです。)

前もって自分が試した手法の結論を言っておきます。

半透明描画にDOFの効果を与えるために2つ程手法を紹介します。が、本手法は多大な手間がかかりますし、色々制約があります。実用的ではありません。

自分なりの検証結果をまとめときますので、議論の入り口になれば幸いです。
そんな意味での零式(Type-0)です。。
ということで本日のアジェンダです。

アジェンダ


  1. 発端: 半透明とDOFの相性の悪さの例
  2. 原因: なんで半透明のDOFはうまくいかない?
  3. 解決案1: DOFに対応した半透明描画を作成する
  4. 解決案2: DOFをかけたい描画にデプスを与える
  5. まとめ: なぜ実用的じゃないか

発端: 半透明とDOFの相性の悪さ


まずは、DOF(被写界震度)と半透明描画がうまく行かないシーンを見てみます。
非常にシンプルで恐縮ですが、空間にパーティクルを配置しただけの以下のシーンを考えます。

DOF(被写界震度)OFF

これにDOFかけたシーンがこちらです。

DOF(被写界震度) ON

わかるでしょうか?地面の切れる地平線にそって、DOFの効果に差が出て、境界線がでています。

また、半透明物体を別のバッファに描画する"Separate Translucency"という機能がマテリアルにあります。


マテリアルのSseparate Translucency設定箇所


この機能はProjectSettingで、そもそもSeparate Translucencyを使うかを設定できます。

プロジェクトのSseparate Translucency設定箇所


これらをOnにしてる人も多いと思いますが。。。
"Separate Translucency"が設定された半透明マテリアルにはDOFがかかりません。
これを設定したのが以下の画像です。

被写界震度ON & マテリアルのSeparate TranslucencyをON


一見良さそうに見えますが、右の遠くにある煙はDOFが入っていません。繰り返しになりますが、Separate TranslucencyがONのマテリアルをアサインされた物体には、DOFがかからないのです。
このように、半透明描画とDOFは非常に相性が悪いです。
ひどく言えば、バグのような描画結果になってしまいます。
半透明描画は、実は様々なエフェクトと併合性がありません。
これらの検証はs k (@monsho1977) | Twitterさんが以下の記事で丁寧にまとめています。ので、参考にしてみてください。
もんしょの巣穴blog [UE4] 半透明マテリアル

 

原因: なんで半透明のDOFはうまくいかない?


ではなんで半透明描画がうまくいかないのか?答えは単純で、
半透明描画がDepthを書き込まないから
ということに集約できます。

前章の画像のDepthを見てみます。

デプスバッファ(Scene Depth)


半透明の煙のデプスは全く書き込まれていません。
UE4の半透明物体(≒マテリアルがTranslucentに設定されたもの)はデプスに描画されません。
しかし、DOF(被写界深度)はデプスバッファを見て遠近を判断するので、半透明物体の正確な位置を考慮することができないのです。そのため、先に見せたようなエフェクトの不具合が発生してしまうのです。一連のレンダリング流れをまとめたのが下図です。

半透明とDOFの描画の流れ(Separate Translucencyを使わない場合)


また先にも述べた通り、UE4には、マテリアル設定にSeparate Translucencyという機能があります。そのSeparate TranslucencyをONにした際の挙動が以下です。Separate Translucencyが設定されたオブジェクトは別のバッファに書き出され、DOFの後に合成されます。半透明描画がやたら真っ赤なのは、単にこの画像がアルファを考慮していない画像なだけでアルファを考慮すると合成画像のように馴染みます。

半透明とDOFの描画の流れ(Separate Translucencyを使う場合)

DOFの後に合成するので、そりゃあ半透明にはDOFがかからないわけです。

解決案


では、どのような解決策があるか?今回、検証した以下2つの手法を晒します。

  1. 半透明の描画時に自力でぼかす
  2. DOFがかかるように遠くの物体にはデプスを書く

両方共Separate TranslucencyがOnになっていることを想定しています。

炎は複数のEmitterで作られており設定が大変なので、ここからはEmitterが一個のシンプルな煙で検証します。



検証用の煙

煙が赤いのはわかりやすくするためです。


解決案1: 半透明の描画時に自力でぼかす(マテリアルのDephtOfFieldFunction)


この解決案は、半透明のマテリアルを改変し、自作のDOF効果をぶち込んでやろうという方針のものです。

自作のDOF効果でパーティクルをぼかす


そのために、マテリアルのDepthOfFieldFunctionという機能を使います
Unreal Engine | Utility 表現式

マテリアルのDepthOfFieldFunctionノード


これの効果を見てみます。例えば、以下のマテリアルをスフィアに設定します。

DepthOfFieldFunctionテストマテリアル


DepthOfFieldFunctionはピントがあっている場合は0の値を、ボケている場合は1の値を返してくれます。しかもポストプロセスのDOF設定と自動で対応付けしているので、ピントがあう位置では赤に、ピントが合わないぼやけた位置では緑色になります。(下図)


DepthOfFieldFunctionテストシーン


このように、DepthOfFieldFunctionを使うことで、マテリアル内でDOFへの遷移が可能となるのです。後は、この値に基づいてマテリアルを構築するのみです。単純にぼかしテクスチャを用意して、オリジナルと遷移するようにします。

結果の画像が以下です。左側がオリジナルの煙で、近寄っても遠のいてもぼけません。右側はDepthOfFieldFunctionを用いた煙で、近寄るとボケているのがわかるかと思います。


このように、半透明物体に対して、独自にDoFの設定を行う事が可能です。今回厳密に調査していませんが、まとめるとこんな感じです。

  • 長所
    1. 各エフェクトに対してカスタマイズしたDoFの効果を作成することができる。
  • 短所
    1. シェーダ負荷があがる。
    2. 自身で一つ一つ調整しないといけない。




解決案2: DOFがかかる(ぼける)物体にはデプスを書く(Particle LOD)


こちらは簡単に紹介だけ。
もう一つの手法も、Separate TranslucencyがOnのときにのみ成り立つ手法です。「どうにかして既存のポストプロセスのDOFをパーティクルにかけられないか?しかし、エンジンの拡張などはしたくない。。。」という要求を成り立たせるために、”ぼける距離にあるパーティクルのマテリアルは、(デプスを書き込む)Maskedに設定する。”という手法を考えました。距離に応じてパーティクルの挙動を変える。。。つまりLOD(Level Of Detail)を使ったパーティクル制御です。

LOD Particleの細かな設定方法は公式に載っていますので、こちらを参考にしてください。
Unreal Engine | パーティクル システムの詳細度 (LOD)

ボケる範囲のマテリアルをBlendModeをMaskedに変更します。(下図)

左:ピント合うLODのマテリアル。右:ぼやけるLODのマテリアル


確認すると、Maskedに設定したパーティクルは確かにDepthを書き込むことができています。

右のLODがかかったパーティクルのデプスが書き込まれる。


この状態でぼかしたのが、下図、右のパーティクルです。この手法だと、前手法のようにマテリアルを拡張しないため、処理負荷は増大しないという利点があります。が。

LOD&Maksedマテリアルを用いたパーティクルのブラー


まあまあな感じもしますが、実は全然ダメです。Gif画像にしてみました。


LODの切り替え


LODの問題として、切り替わりが滑らかにならない問題があります。また、(DepthOfFieldFunctionのように)ポストプロセスのDOFと連動してくれたりはしません。なので、LODが切り替わる距離をDOFのぼやける距離と合う様に自分で設定しなければいけません。上記の煙もちゃんと設定すればもっとうまくいくかもしれませんが、そもそも、半透明に板のデプスを与えるということも無理やりすぎるので、ここで断念しました。

まとめ: なぜ実用的じゃないか


半透明+DOFを実現できないか、簡単ですが自分の試行錯誤を説明させていただきました。がしかし、がしかしだ。これの最大の欠点は、処理負荷や調整よりも修正項目の多さです。やることが非常に多い。今回の検証では煙一個、つまりエミッター一個だけの修正ですみました。ですが、例えばStarterContentsの爆発と炎のパーティクルを見てみます。


爆発のParticleはエミッターが5個


炎のParticleはエミッターが6個


このように一つのパーティクルにも複数のエミッターがあります。DOFのためにこんなに沢山の修正してられっか!というのが正直なところです。
ということで、ここまで説明してきて身も蓋もないのですが、実用的じゃない例を紹介させていただきました。
誰かがより良い手法を教えてくれるのをお待ちしております。

まとめ
問題点
半透明物体はDepthを描画しないため、Depthを用いるポストエフェクトのDOFと非常に相性が悪い。
解決策の検証
半透明描画自身にDOF機能を持たせるか、デプスを書き込むことによってポストエフェクトのDOFがかかるようにしないか?この2点を軸に考えた。どの案も、処理負荷の増大、自身での細かなパラメータ設定と一長一短がある。
最大の欠点
説明した手法では、一つのパーティクルにDOF効果を与えるためだけでも、使われる全種類のマテリアル/ParticleLODモデルを拡張しなければならない。




読んで頂ありがとうございます。


2014 UE4 Advent Calender. ウサギからウサギへバトンを託します。
次回は関西で勢力的に活動なさってる栗坂こなべさんの「UE4でOculus Riftコンテンツを作る入門記事(を予定)」です。自分もOculus+UE4で色々試してるので、非常に楽しみです!



【UE4】GPUコストの高いHZBとはどんな機能?

※本記事へのフォーラムを立ち上げました。色んなご意見がありましたら、是非こちらのフォーラムでご指摘や議論をしていければと思います。

HZBの仕組みと負荷の高さについてまとめました(2014 advent calender )



発端: HZBはOffにしたほうが良い?

Oculus開発などのために、最近ではUE4でパフォーマンスを出す方法がよく議題にあがります。
そんな時によく

"HZBをオフにしてみては?"

という意見を聞きます。


例えば、EpicGames下田さんのこの記事では。。。

Oculus ConnectでのNickのプレゼンテーションでShowdownデモの最適化でHZBはOFFにしたと説明していましたが「r.HZBOcclusion 0」とコンソールコマンドを打つ事でOFFにできます。

http://qiita.com/junyash/items/3ba225d8a2f86046cd23


またUE4の公式でも、最適化のアドバイスの部分で。。。
Unreal Engine | GPU プロファイリング

オクルージョン カリング:HZB オクルージョンの負荷は常に高いですが、オブジェクト当たりの負荷は小さめです。r.HZBOcclusion を切り替えて、オンにしなくても大丈夫が確認します。

https://docs.unrealengine.com/latest/JPN/Engine/Performance/GPU/index.html

このように、最適化の第一手として、
"HZBをOn/Offにして負荷を計測し、高速な方を選ぶ。"
というのが通例になっています。
が、"HZBという機能は何をしてんの?"という日本語資料は、現時点ではあまりないようです。
今回はこのHZBについて簡単にまとめてみたいと思います。
作業効率が倍になったり、新しい表現ができるようになわるわけではないです。スッキリさせたい人向けです。
検証不十分のため誤りがあるかもしれませんが、よろしくお願いします。

アジェンダ: UE4のHZB(Hierarchical Z-Buffer)について

  1. HZBとはどんなデータか?
  2. HZBオクルージョンカリングの仕組み
  3. HZBはどうして負荷が高い?
  4. HZBが有効なシーンは?
  5. 終わり
  6. 備考. ScreenSpaceReflectionのBuildHZBは?

検証バージョンはUE4.6、またDX11の範囲のみ絞っております。
(基本的なアルゴリズムは同じなので問題無さそうです。)
(HZBとは本来はデータ形式ですが、UE4ではHZBを用いたオクルージョンカリングを"HZB"と言っているので、今回はHZB=HZBオクルージョンカリングとして説明させていただきます。)


1. HZBとはどんなデータか?

まず、HZBとはHierarchical Z-Bufferの略です。名前の通り、階層化されたZ-Bufferです。そういうと特殊なものに聞こえますが、つまりただのテクスチャのMipMapです。
UE4では、1フレーム前のZバッファからこのHZBというテクスチャを作成します。
(デフォルトは、一番下が512*256ピクセルで、8段です。)
1フレーム前を使って平気なのかと言うと、30fps/60fpsでは1フレーム前のデプスも現在のフレームのデプスもだいたい同じだろうという前提のもとで行っており、問題になるようなシビアなシーンは殆どないと考えられています。
f:id:tempkinder:20141216131535p:plain

ただしこのMipmapの作成は一工夫されています。UE4のHZBは、近隣のデプス値の中から、"一番遠いデプス値"を選んでMipMapを作成していきます。(下図)
f:id:tempkinder:20141216131536p:plain

このHZBを使って、後にオクルージョンカリングを行います。


2. HZBオクルージョンカリングの仕組み

前の節で作成したHZBを使って、オクルージョンカリングを行います。
オクルージョンカリングとは、前景のオブジェクトによって完全に見えないオブジェクトを前もって探しておき、それらをレンダリング計算から除外して計算コストを下げる手法です。
不透明な壁の裏に500万ポリゴンのキャラクタがいても、映らない事を判定して前もって省いておくので、(GPUの)レンダリングコストを減らしてくれます。

ではどうやって、あるオブジェクトが見えるか見えないかを判定するか?非常に単純な3工程なので以下で簡単に説明します。

2-1. オブジェクトのバウンディングボックスがスクリーンのどれくらいの範囲に描画されるかを調べます。

f:id:tempkinder:20141216131532j:plain

2-2. 1で調べた範囲のZ値を、前もって作って置いたHZBから読み込みます。これは、”バウンディングボックスの描画範囲で最遠のデプス値”となります。

このとき、バウンディングボックスを内包できるMipのレベルを調べて、そこからから読み込みます。
f:id:tempkinder:20141216131533j:plain


(備考)もしも半端なところにボックスがある場合、4近傍の周りのデプスを取って来て、それらの最遠のデプス値を使ったりもしてます。
f:id:tempkinder:20141216145945j:plain

2-3. "(HZBから読み込んだ)その範囲の一番遠いデプス" よりも "バウンディングボックスが完全に後ろにある場合" オブジェクトは絶対に映らないので、描画されない用フラグをつける。

f:id:tempkinder:20141216133004j:plain


まとめると以下の流れです。

HZBオクルージョン判定の流れ(SubmitHZB(のTestHZB))

  1. オブジェクトのバウンディングボックスがスクリーンのどれくらいの範囲に描画されるかを調べます。
  2. その範囲の最遠のZ値を、前もって作って置いたHZBから読み込みます。
  3. "(HZBから読み込んだ)その範囲の一番遠いデプス" よりも "バウンディングボックスが完全に後ろにある場合" オブジェクトは絶対に映らないので、描画されない用フラグをつける。

このようにして、どんな高ポリゴンのオブジェクトだろうと、バウンディングボックスとHZBテクスチャの比較のみで済むので、非常に低負荷にオクルージョン判定ができます。*1



じゃあ、なんで重たいんだよ?



3. HZBはどうして負荷が高い?~GPU⇒CPUへのデータ転送の重たさ。~

上で説明したとおり、HZBでやることは2つ。

  1. HZBを作る。(BuildHZB)
  2. HZBを使って、見えないオブジェクトを算出する。(SubmitHZB)

UE4内でこの2つの作業はBuildHZB, SubmitHZBと呼ばれます。

実際にProfileGPUの中身を見てみます。
f:id:tempkinder:20141216103619j:plain

この中のHZBの中を見てみると、確かにBuildHZB、SubmitHZBがあります。
f:id:tempkinder:20141216103526p:plain
こう見ると、HZBを作成しているBuildHZBは処理負荷が全くかかっていません。というか今回の場合、HZBの負荷の1%にもなっていません!
ミップマップを作っているだけなので、そこまで重たくなることは確かになさそうです。

処理負荷の99%は、SubmitHZBです。ここでSubmitHZBの中を見てみると。。
f:id:tempkinder:20141216104459p:plain
TestHZBというのが0.02出てきました。これは実はSubmitHZBの”見えないオブジェクトを算出する”というメインの部分です。やはりこちらも軽いのです。バウンディングボックスとテクスチャ(HZB)の値を比較しているだけですから。(ですので、大量にオブジェクトがあったとしても、この部分の負荷が跳ね上がりはしないと考えられます。)
ということでHZBのアルゴリズムの外で、98%以上の負荷がかかっています。
では何をやっているかというと。。

HZBのメインのボトルネックは、オクルージョンの判定結果をGPUからCPU(のメモリ)に転送しているからです。

というのが答えになります。

HZBの作成も、オクルージョンのテストも、どちらもGPUで行われています。ということは、CPU側に”どのオブジェクトが見えて、どのオブジェクトは見えない?”という結果を返してあげなければいけません。

図のように、オクルージョンの判定(TestHZB)もGPUで行われるため、GPU側はオクルージョンの判定結果をCPUに戻してあげないといけません。これが非常に重たい原因です。しかも、転送コストは一定なので、ユーザがアセットの作り方やレベルの作成方法などで削減できない部分です。


4. HZBが有効なシーンは?~Oculusには無理じゃね?~

うーん。。。。色々と調査していたのですが、具体的に判明しておりません。
実際、HZBによって沢山のオブジェクトがカリングされるようなシーン。。。があるのか微妙な気がします。(調査不足なのは否めませんが。)

というのも、この"カリング"という工程はHZBオクルージョンだけじゃなく、視野の外のオブジェクトを除外するフラスタムカリング、スタティックなオブジェクトの見える領域を前もって計算するPrecomputed Visibilityなど色々あります。深く検証はしていませんが、HZBよりも前に走る上記2つのカリングが非常によく効き、HZBまで至るオブジェクトはそんなにないのでは??と考えています。

動的に動く壁によって隠れる大量のキャラクタ。。。なのかとも考えましたが、ちょっと調べきれておりません。海外Forumでも有効な意見は発見できなかったので、みな効き目を模索しているところでしょうか。何れにせよ、動的なオブジェクトが大量に出てくる物量の多いシーンでこそHZBは効いてくると思われます。

しかし、断言できそうな事もあります。Oculusで75FPS(はたまた今後は90FPS)を出そうとすると、CPU/GPU共にかなりシビアに負荷を制御しなければいけません。
90FPS出そうとするならば、CPUの制限からも、そんなに大量のオブジェクトを投入できそうもありません。結果、HZBが効果的に効くシーンを、そもそも作れない可能性が高いです。
ということで、やはり最初の提案に至ります。

"HZBをオフにしてみては?(特にOculusするなら)"



5. おわり

読んで頂きありがとうございます。
f:id:tempkinder:20130423003103j:plain
HZBについて簡単にまとめてみました。
”名前は聞くけど、何してんの?”と思っていた方々が少しでもすっきりできたら幸いです。
ソースを読んで、面白いことなども色々発見できたので、それはまた後日改めてまとめようと思います!


次回はとげとげさん@火曜西く25a (@checkela) | Twitterさんの"UE4でミクさんに可愛く踊ってもらう方法(予定)"です!華のある投稿になりそうなので、いまから楽しみです!
Unreal Engine 4 (UE4) Advent Calendar 2014 - Qiita





備考: SSR(ScreenSpaceReflection)のBuildHZBとは?

r.HZBOcclusionのOn/Offに関係なく、SSR(ScreenSpaceReflection)をOnにすると、BuildHZBがあります。

実はSSRもHZBを使います。もしもHZBオクルージョンで先にHZBを作成していた場合、SSRで呼ばれるBuildHZBは何もしません。もしもHZBオクルージョンをオフにしてた場合、SSRを計算するタイミングでBuildHZBによりHZBが作成されます。いずれにせよ、BuildHZBはHZBを作成する作業なので、負荷はほとんどありません。

”あれ?HZBOcclusionをOffにしたはずなのにBuildHZBとかいうのがプロファイルに出てくる。。”と考えてた方は気にしないで大丈夫と思います。
SSRの内部で、高速かつ粗くRaycastを行うためにHZB必要としています。
ですが、HZBオクルージョンのようにCPUに結果を戻したりしないというか、そもそもSubmitHZBの作業はSSRでは全く走らないので問題ないということです。
それとは関係なく、SSRも重いですけど。。。


参考資料



https://answers.unrealengine.com/questions/24425/whats-submit-hzb-its-consuming-high-gpu-resous.html/
深度バッファのレンジと精度を最大化する

*1:正規化デバイス座標系への変換やどのMipレベルを選ぶかなどは勿論ありますが、高負荷とはいえません。

エイドが最高!! 第20回 星の郷八ヶ岳野辺山高原100kmウルトラマラソン

快晴の野辺山高原!!
f:id:tempkinder:20140527011919j:plain
f:id:tempkinder:20140527010514j:plain
ロードはあまり好きではないのですが、楽しそうだったので参加してみました。


大会概要

  • URL : 神奈川県・山梨県東部トレイルラン連絡協議会オフィシャルサイト:第6回 東丹沢宮ヶ瀬トレイルレース]
  • 大会名: 第20回 星の郷八ヶ岳野辺山高原100kmウルトラマラソン
  • 日時 : 2014/5/18(日)
  • 場所 : 長野県南佐久郡野辺山高原一帯(レタスで有名な川上村)
  • 出走時間: 5:00
  • 距離: 100k
  • 制限時間: 14時間
  • 景観: 再考標高は1900m. 山頂にはいかないですが広大で見晴らしの良い景色の中を走れます。ロードとしては贅沢なのでは?
  • 地面: 20km程トレイルと言われますが、ただの砂利道なので登山道ではありません!他はコンクリートでくだりも多いので、膝を気を付けたいところ。
  • 前泊?: 都心からは必須です。朝5時スタートですし。
  • 風呂?: あります。ですが時間制限と共に営業が終わるので、入りたければは制限時間より一時間以上前にゴールしないといけません。(僕は無理だった。)
  • 備考: 完走率6割~7割と厳しいレース。途中に温泉が三か所あってはいっていい。入ってる人もいたみたいですが、自分みたいなへっぽこは入ったらまず間に合わないので黙々と走る。そして、なんといってもエイドが最高。

雑感

個人的なファンランおすすめ度は5点満点中5点。
はじめてのロードのウルトラでしたが、きついきつい!
完走率の低さは、アップダウンの多さに対する時間制限の厳しさでしょうか?
時間制限20分前にゴールした自分でも、順位は半分よりちょっと上くらいでした。

がしかし、

  • 天気に恵まれ、景色にめぐまれ。
  • 5km毎にエイドがあるという親切設計。
  • どのエイドにもバナナと梅干とヴァームウォーターがあるという。
  • 大きなエイドでは、お蕎麦やお汁粉やおにぎりなどを出してくれる。
  • 地元の人たちの自作エイドも沢山あって、手作りの野沢菜とか出してくれて幸せ。

と、最高の景色でもりもり食べれて飲んでと非常に幸せでした!!

コースは確かにアップダウンが激しいし、そもそも100kmだから長いけど、出れてよかった。また出たい。と思える良いレースでした。

ありがとうございました!

撮影記録(エイド以外)

朝5時。気温は0度の極寒の中スタート。
f:id:tempkinder:20140527005917j:plain
さむいっ。がこのあと20度まであがるので薄着で。
自分は防寒具とジェルをいれる小さなポシェット一個で挑みました。


スタート!寒い!
f:id:tempkinder:20140527010128j:plain


途中で踏切。これはみんな笑ってました。
f:id:tempkinder:20140527010156j:plain

トレイルと呼ばれる普通の砂利道。
f:id:tempkinder:20140527010300j:plain

コース最高地点。景観はまあまあ。
f:id:tempkinder:20140527010335j:plain
レース最初のほうなので、あまりみんな立ち止まらない。

30kmぐらい?でトレイルと呼ばれる砂利道終了
f:id:tempkinder:20140527010556j:plain
物足りない。。。

42kmゴール!だがウルトラではまだ半分にも満たしていない。。
f:id:tempkinder:20140527011038j:plain

ちなみに42km地点など数か所に自分の荷物を置いておくことができます。
f:id:tempkinder:20140527011008j:plain
便利!自分はめんどくさかったのでここでは休むだけ。


その後下りつつ受けて58km地点
f:id:tempkinder:20140527011405j:plain
このとき、くだりに左ひざをやられ、一気にスピードが出なくなる。

リタイアしようか悩む。が、よちよち走る。
f:id:tempkinder:20140527011502j:plain

リタイアしようか悩む。が、よちよち走る。
f:id:tempkinder:20140527011603j:plain


ここからののぼりがきついと言われていますが、
みんなとぼとぼ歩いていました。ファンランの人たちはのぼりは歩くみたいです。
僕はもちろん歩きます。


10時間30分かけて79kmまで上る。あとは下るだけ!
f:id:tempkinder:20140527011842j:plain
でも下りたくないんだよね。。。膝がもうだめだめで。
登る方が全然楽。
くだりは激痛。


しかし絶景。よちよち歩き走る。
f:id:tempkinder:20140527011919j:plain
f:id:tempkinder:20140527011954j:plain


ラスト10kmで左ひざが限界となり、ひきずりながら1時間45分...
f:id:tempkinder:20140527012030j:plain


13時間40分でゴール!制限時間ぎりぎり20分前!
f:id:tempkinder:20140527012105j:plain


足は痛かったけど、親切で素晴らしい大会でした。
また出たいっ。


ちなみに出場特典はバッグとTシャツ。
f:id:tempkinder:20140527005605j:plain
完走するとメダルがもらえます。

撮影記録(エイド!)

殆ど全てのエイドにある梅干しなど
f:id:tempkinder:20140527010225j:plain


水分もビタミンもエネルギーも取れて超美味しい
f:id:tempkinder:20140527010439j:plain
f:id:tempkinder:20140527010514j:plain


温かい飲み物も沢山!松竹のお吸い物!
f:id:tempkinder:20140527010626j:plain
f:id:tempkinder:20140527010831j:plain


たまにはおにぎりやアンパンも。長いレースだからちゃんと食べないとガス欠に。
f:id:tempkinder:20140527010726j:plain
f:id:tempkinder:20140527010653j:plain


三杯おかわりした、おしるこ!
f:id:tempkinder:20140527010900j:plain


50km地点ではお蕎麦を目の前で茹でてふるまってくれました!
f:id:tempkinder:20140527011140j:plain
f:id:tempkinder:20140527011109j:plain
f:id:tempkinder:20140527011307j:plain
f:id:tempkinder:20140527011335j:plain


ということで、13時間のレースを乗り切れたのは
こんなに沢山の美味しいエイドがあったからです。
ありがとーございました!!


読んで頂きありがとうございます。

f:id:tempkinder:20140112115948j:plain
走ってるの?食べてるの?

(今回はロードだったのでアクションカムはなしです。)

2014年: 第6回東丹沢宮ケ瀬トレイルレース(32km)

f:id:tempkinder:20140524091420j:plain
ハセツネ30kmから一週間。第6回東丹沢宮ケ瀬トレイルレースにも参加してまりましたー。

大会概要

  • URL : 神奈川県・山梨県東部トレイルラン連絡協議会オフィシャルサイト:第6回 東丹沢宮ヶ瀬トレイルレース]
  • 大会名: 第6回東丹沢宮ケ瀬トレイルレース
  • 日時 : 2014/4/20(日)
  • 場所 : 宮ヶ瀬湖畔ー早戸川林道周回コース(横浜線橋本駅からトレイルバス運行)
  • 出走時間: 8:00
  • 距離: 32k
  • 制限時間: 7時間
  • 景観: 標高は高くなく絶景ではない。ですが新緑の気持ちよさはあるはず!
  • 地面: 当日は雪の残るぐっちゃぐちゃなドロの中を走りました。でも、晴れればすごい気持ち良いトレイルなんじゃ。。なかろうか。
  • 前泊?: 都心からならば、朝一の電車で参加可能。
  • 風呂?: なし
  • 備考: 季節のわりに極寒でした。今年は山頂は雪が残っており、地面も泥まみれで危険でした。天候にすごい左右されそうなトレイルです。でもロードも多いですが。

雑感

個人的な初心者ファンランおすすめ度は5点満点中2点。
コースとしては全然きつくありません!
ちょっとロードが長いので、トレイル好きには物足りないかもしれませんが。。
時間制限も厳しくないのでゆっくり走ってればゴールに行けます。

がしかし。今年は地面がぐちゃぐちゃで、下りは結構危険でした。
そして、コース最高地点も山頂というか開けた場所じゃないので、あまり達成感なく。。。
トレイル初心者には向かないかなと。。
ただ、晴れたら。。。と予想するとトレイルはかなり気持ち良いはずです。
ロードとのバランスも、ロードからトレイル始めた人にはお勧めかも。
なんにせよ、日帰りできるしね。

でもやっぱり、お風呂がないのはいやかなー。
仮設テントで着替えてさようならなので、ちょっと切ない気もします!

撮影記録

はじめは10kmほど?ゆっくりロードを登ります。
f:id:tempkinder:20140524091422j:plain
楽しさはあんまりないかも。。登るのみ。


10m地点でエイド!ハセツネ30ではないからね。うれしい
f:id:tempkinder:20140524091423j:plain


トレイルに入って登り出すと。。あれ?雪?お?
f:id:tempkinder:20140524091424j:plain


山頂も特に景色はよくなくっていうか見えねえよ。
f:id:tempkinder:20140524091425j:plain
もくもくと下るのです。


ロードに戻れば綺麗で静かな湖を走れます。
f:id:tempkinder:20140524091426j:plain
晴れてほしかったなぁ。

アクションカムで撮ってきたダイジェスト。

トレイル記録: 第6回東丹沢宮ケ瀬トレイルレース



読んで頂きありがとうございます。

f:id:tempkinder:20140112115953j:plain
もうちょっと見応えのある写真撮れよ。