[UE4]Distance Field Shadowが落とせない対象と、何か落とす方法

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

# はじめに
メッシュへの距離情報からShadowを落とすDistance Field Shadowについての話になります。
ゲームだと遠方の影をCascade ShadowからDistance Field Shadowにして負荷軽減したり、精度を上げたりするような時に使ってるかと思います。

ただ、このDistance Field Shadowでは落とせないメッシュがあったりします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/76519b84-015c-87fc-6740-ad78f09a095d.png)
その場合、Cascade ShadowとDistance Field Shadowの境目で影が切れてしまい格好悪い感じになってしまいます。
なんとかしたい。

- 参考資料
    - ディスタンス フィールド シャドウを使用する - 公式ドキュメント
        - https://docs.unrealengine.com/4.27/ja/BuildingWorlds/LightingAndShadows/MeshDistanceFields/HowTo/DFHT_1/
    - [UE4] Shadow Rendering(影描画)機能入門 - Qiita EGJ-Yutaro_Sawada(Epic Games Japan)
        - https://qiita.com/EGJ-Yutaro_Sawada/items/2a8a1467ebaaa9795fe0#distance-field-shadow

# まとめ
（いつも長いと言われるので先に）
- Distance Field Shadow で落とせるのは**StaticMesh**のみ。
- ステーショナリーライトのInset Shadow For Movable Objectsで**ムーバブルなMesh**の影を落とせる
- メッシュ設定のInset Shadowでメッシュ個別に影を落とせる
    - ムーバブルなライトなら**どのメッシュ**からも影を落とせる

|処理方法|StaticMesh<br>スタティック|StaticMesh<br>ムーバブル|SkeletalMesh|SplineMesh<br>スタティック|SplineMesh<br>ムーバブル|
|:--|:--:|:--:|:--:|:--:|:--:|
|Distance Field Shadow|○|○|×|×|×|
|[Inset Shadow For Movable Objects](#-ステーショナリーライト：Inset-Shadow-For-Movable-Objects)<br>(ステーショナリーライトのみ)|×|○|○|×|○|
|[メッシュのInset Shadow](#-メッシュのDynamic-Inset-Shadow)<br>+ステーショナリーライト|×|○|○|×|○|
|[メッシュのInset Shadow](#-メッシュのDynamic-Inset-Shadow)<br>+ムーバブルライト|○|○|○|○|○|

スタティックなSplineMeshが厳しい。背景配置で多様するだろうし……
メモリが許せば全部StaticMesh化するのが良いのだろうけど、流石に。
何か出来ないかと試した結果で [#Distance Fieldだけを生成したい](#Distance-Fieldだけを生成したい) だけど、SplineMeshに汎用近似メッシュ合わせるのはすこし厳しいか……

# Distance Field Shadowで落とせない影
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/b9f1e9a3-7cdb-e497-f18a-6b4803da6a28.png)
Distance Field Shadowですが、SkeletalMeshとSplineMeshは影を落とせません。
と言いますのも、SkeletalMeshとSplineMeshはDistance Field Shadow描画の元となるDistance Fieldが生成できません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/fac6982d-999d-a587-ce92-1bea50ef284f.png)
こちらがDistance Field。SkeletalMeshとSplineMeshからは生成できません。
Distance Field自体が変形できない為、変形するメッシュには追従できず反映されません。

とはいえ何も出てないのは格好悪いのでなんとかしたい……

# ステーショナリーライト：Inset Shadow For Movable Objects
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/200e66ac-10e0-19b5-3c79-a2045bc4f908.png)
<font color="OrangeRed">**ステーショナリーなライトでのみ使用可能です。**</font>
この機能は、「ムーバブルなオブジェクトはInset Shadowを利用して影を落とす」形になります。
Inset Shadowとは？となりますが、これはオブジェクト個別にShadow Mapを生成して影を落とす事になります。
詳しくは下記Pre Shadow項目に詳しく書かれています。参照してください。
[[UE4] Shadow Rendering(影描画)機能入門-PreShadow項目](https://qiita.com/EGJ-Yutaro_Sawada/items/2a8a1467ebaaa9795fe0#pre-shadow)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/a2223148-51af-a6a8-88bd-1de39757c787.png)
ライトの可動性がステーショナリーな場合は、Inset Shadow For Movable Objectsがデフォルトで有効になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/826eb86b-a436-63ab-edfc-50c17053779c.png)
今回の場合、ムーバブルなオブジェクトがこのように一つずつShadow Map生成されています。

**その分、ムーバブルなオブジェクトが増えれば負荷が増えることが想定されます。**
逆にデフォルトでONになっているので、遠景のムーバブルは影要らない！の時はOFFにするいい思います。

# メッシュのDynamic Inset Shadow
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/476f48cd-7876-dea5-cbb7-44ede104fdf1.png)
前述の[Inset Shadow For Movable Objects](#-ステーショナリーライト：Inset-Shadow-For-Movable-Objects)のメッシュ側のフラグになります。
[Inset Shadow For Movable Objects](#-ステーショナリーライト：Inset-Shadow-For-Movable-Objects)はライト側の設定で、ONにすると全てのムーバブルなメッシュに対して処理を行うのですが、こちらはメッシュ側の設定なので、このメッシュだけInset Shadowを使う！等が出来ます。
また、違いとして下記のようになっているようです。

- **ステーショナリーなライト**の場合は、**ムーバブルなメッシュのみ**Inset Shadowが使用可能
- **ムーバブルなライト**の場合は、**スタティックなメッシュでもムーバブルなメッシュでも**Inset Shadowが使用可能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/eb2e31e6-d3ce-4b31-e33b-c1fccd2c2042.png)
設定はMeshComponentの設定に有ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/6953641c-001f-9e4a-27dc-7dbcf0d6e19e.png)
こちらも一つずつShadow Mapを生成しますので、**多数をONにすると負荷が増える事が想定されます。**
目立つメッシュや、カットシーン中はONにするとかでしょうか。

# Distance Fieldだけを生成したい
頑張ったけどいい感じには出来なかった。
人型には難しい。負荷的にはちょっとだけエンジン弄る必要ありそう。
メモとして残しておきます。

Inset Shadowを使わず、Distance Field Shadowの仕組みを流用してそのまま影が出せないか、という所でDistance Fieldのみを生成する方法が無いか試してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/5c9dcf59-15c0-212d-f4c5-181c6d6edb23.png)
特に車とか乗り物はタイヤを動かすためだけにSkeletalMeshにしている事があり、その為にDistance Field Shadowが使えず勿体ない感じがします

今回はSkeletalMeshComponentの位置に合わせて、近似的なStaticMeshを使用して代用する方法を試してみました。
![b.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/ffc08239-ded2-ffc4-88ec-884f2a25c05e.gif)
結果はこちら。カメラが離れた時がDistance Fieldです。
（ちょっと近似的なメッシュを作らず、横着してCubeを使用しました。きっちり形合わせたい場合は車の形そのままのStaticMeshを用いれば良いかなと……）

やった手順
+ DistanceFieldだけ生成したいStaticMeshを配置。今回はCubeで代用
    + ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/6059b892-3628-7f04-ae10-ea120a6d53c9.png)
+ MeshComponentのオプション設定
    + コリジョンをOFF
        + ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/526dc4c4-1427-2386-e8d1-08c3d9e54c19.png)
    + ライティング周り
        + ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/22ece39c-fd34-9c9c-008d-59c744ada3a2.png)
    + レンダリング周りで通常のViewには影響が出ないように各種OFFに
        + ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/ea2a7744-2dd9-395f-d942-105efc2cf220.png)
    + ここまでの設定で、通常のViewには影響を与えず、Shadowのみ落とす表示が出来ます
+ 現状だとカメラが近い所のCascade Shadow時にも描画されてしまうのでマテリアルを差し替えます
    + Maskedマテリアルで、Shadow Pass SwitchでShadowパス（Cascade Shadowとか）のみ0にして表示消します
        + ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/36dabad3-6ec5-9ca0-2215-102b2d734db2.png)
    + 作ったマテリアルで置き換えます
        + ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/9a969629-30b4-5077-7d77-37f0de97346a.png)
+ これで、見た目上はDistance Fieldにのみ影響を与えるメッシュ配置になります。

問題として、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/88899def-2017-1872-178d-d5d4848d6d29.png)
モーションには追従しないので、人形状等は難しいかも。
（やるとしたらCapsule Shadowみたいに各ボーンにメッシュくっつけて追従させるとかしか……）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58518/821cc31e-480f-ca88-1d0c-ded89f02892e.png)
DistanceField用Meshが元メッシュの形状からはみ出ると、元のメッシュにも影を落としてしまうので難しい。



<font color="OrangeRed">※また、この方法だと画面には表示はされないもののCascade Shadow範囲内に入った際に、このメッシュもCascade描画が走ってしまう問題がある。<br>低コストマテリアルとはいえ負荷に。Inset Shadowとどっちが負荷的に良い？</font>
この辺りも回避するにはエンジン改造するしかないかも。以下エンジンコード側の話メモ。
CastsDynamicShadowがONでないとDistanceFieldに積まれない。
けどCastsDynamicShadowがONになるとCascade Shadowにも積まれてしまう。
何かDistance Fieldのみ積む用フラグを追加して、FDistanceFieldSceneData::AddPrimitive 辺りでCastsDynamicShadow無視して積めばいけるかも。
未検証。