[UE4] DynamicなShadowを何とか軽くしたい

検証UE4: 4.27.1
まちがってたらごめんなさい。


# はじめに
DynamicなShadowは、
Movableなライト。
Stationaryなライト＋Movableなメッシュ。（Stationaryなメッシュも？）
等でShadowをCastしたい時に出ます。
負荷として積み重なり重くなる傾向にあるので、極力使わない方が良いのですが、それはそれとして時には使用せざるを得ない時が有ります。
（車のヘッドライトとか、手に持つ松明とか）


## 参考資料
こちらはShadowについて詳しい記事を書かれています。
是非読んでみてください。

- [UE4] Shadow Rendering(影描画)機能入門 - Qiita
    - https://qiita.com/EGJ-Yutaro_Sawada/items/2a8a1467ebaaa9795fe0
- Stationary Light の影について - だらけ者だらけ
    - https://darakemonodarake.hatenablog.jp/entry/2015/12/16/UE4/Stationary

# 解像度を下げる
Stationaryなライト、もしくはMovableなライトの場合、ShadowMapにレンダリングします。
単純にシャドウマップへのレンダリング解像度が下がればその分描画負荷が減ります。

個別にはライトの詳細欄で「Shadow Resolution Scale」を調整する事で解像度を減らしたり、解像度を増やす事が出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/0713219e-2521-d843-244f-b2f3783667dc.png)

## コンソールコマンドで一律で操作する
個別に操作するのが面倒な時は下記コンソールコマンドで、各ライトの種類に一律スケールできます。
（例えばカットシーンとかで描画負荷が高く、CastShadowのコストを削りたい時。もしくは逆に、カットシーンだけ高品質にしたい場合とか）

- r.Shadow.TexelsPerPixel
    - StationaryライトのCastShadowの解像度スケール（対Stationary/Movableメッシュ）
- r.Shadow.TexelsPerPixelPointlight
- r.Shadow.TexelsPerPixelSpotlight
- r.Shadow.TexelsPerPixelRectlight
    - こちら3つはMovableライトのCastShadowの解像度スケール

コマンドの内容的には、上げると使用する解像度が大きくなり綺麗に負荷高、下げると使用する解像度が小さくなり低品質に負荷安。

MovableなSpotLightで、"r.Shadow.TexelsPerPixelSpotlight 2.4" の時 1016x1016
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/6af07ec6-15b2-ac22-9181-61b59eaa1c41.png)
"r.Shadow.TexelsPerPixelSpotlight 0.5" にすると 248x248に。影のくっきり感も減少。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/807733e8-ff30-e9d4-6c4c-85c3702246d1.png)


# WorldPositionOffsetを使うメッシュの注意
Movableなライトを使用したCastShadowでは、Static/Stationaryなメッシュは１度レンダリングするとキャッシュされて
次のフレーム以降は個別にレンダリングが走らないようになっています。

↓Staticな柱、Stationaryな球は1つ目のCopyCachedShadowMapバッチでキャッシュから再利用されています。
Movableなピラミッド、MannequinだけがShadowDepthのレンダリングが走っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/47b84b84-eff4-cf66-f93d-c44306cdb3e5.png)

動かないメッシュは１度レンダリングすれば使い回して大丈夫、との形です。
が、 **WorldPositionOffsetを使用したマテリアルはStaticでもキャッシュに乗りません。**

↓Staticな木のメッシュですが
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/e8f3ce59-4b88-f09c-a32d-f310669a88cc.png)
キャッシュには無く、毎回レンダリングされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/45af0fd9-cbed-ac84-0c77-212fa88ebe26.png)

WorldPositionOffsetは頂点を動かす機能ですので、Movableと同じ扱いを受けるようになります。
森の中で車のヘッドライトとか、持って歩く松明とか大変な事になりますね！

## MeshComponent内に一つでもあれば……
例えば、スタティックメッシュ内にマテリアルが複数ある状態で、
その中の **一つだけWorld Position Offsetを使用したマテリアルがある** 場合。
下記は「M_UseWPO」マテリアルだけWorldPositionOffsetを使っており、残りは「WorldGridMaterial」なのでWPOを未使用です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/98f7a202-1456-f8f9-d6cd-9448ba488dd6.png)
この場合、 **該当のMeshComponent全てキャッシュに乗らなくなります！** 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/aab20e4e-f38c-6d59-6596-4dd6d95faee4.png)
このような形で使用する際は、WPOを使うマテリアル部分だけ別メッシュにした方が良いかもしれません。


## 対策
r.Shadow.CacheWPOPrimitives
のコンソールコマンドがあります。
これは、WorldPositionOffset を使用したマテリアルでもキャッシュに乗せるコマンドになります。
（デフォルト0で乗せない設定。1にするとキャッシュに乗ります）
ただし、1にすると **CastShadow側のメッシュのWorldPositionOffsetが無効化されます。**

キャッシュに乗せる代わりに、WPOを無効化してStaticな扱いにする形です。
↓途中で1に切り替えました。WPOで動いている葉ですが、CastShadow側だけ止まります。
![a.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/63607423-1ea8-bd20-188a-ea42c5d1086a.gif)

その代わり、無事キャッシュに乗って毎フレームレンダリングが走る事が無くなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/adc0d4f0-cf49-cc50-ab8b-73496a0b0e23.png)

動的にON/OFFを切り替えれますが、そこそこのヒッチが発生する可能性があります。



# ほか
- 参考URL先にもあります通り下記コマンドとかでしょうか
    - "r.Shadow.PreshadowsForceLowestDetailLevel" を使う
    - "r.ForceLODShadow" を使う（4.27だと実装コード変更入ってて、エンジン改造無しでいけそう？）
        - [UE4] Shadow Rendering(影描画)機能入門 - Qiita
            - https://qiita.com/EGJ-Yutaro_Sawada/items/2a8a1467ebaaa9795fe0
- Shadowに限らずですが、ライトパラメーターの「Attenuation Radius」も極力小さく……できれば……
- ライトの影響受けなくて良いならLightChannelでCastShadowする対象絞れるかなぁ
    - メインのディレクショナルライトをChannel 1、MovableなPointLightをChannel 2にして、影響受けたい（DynamicにShadowを落としたい）メッシュだけchannel 2もONにする
    - と思ったけど、使い所あんまり無さそうなので備考欄行き。カットシーンの描画負荷対策で、見た目重要じゃないオブジェクト削るとかならまだ有り？

（WPO気をつけた方がいいですよ、的な事書きたかっただけの記事です）