◇ はじめに
Shadow(影)の描画は絵に非常にインパクトを与え、影の有無で写実感に大きな影響を与えます。
この記事ではUE4で使用できるShadowの表現について、どのような方法が用意されているのか紹介していきます。
・なぜShadow?
3DCGを表現する上で相互作用が起きる描画は処理負荷が高く、正にShadow表現は、あるオブジェクトからオブジェクトへ影の影響を与えるものとなっているため処理負荷が高くなる問題です。厳密にシミュレーションを行おうとすると非常に時間がかかるため、さまざまなトリックやテクニックを使用し、それらしく表現を行う方法が必要になってきます。
UE4ではShadow表現を実現するための様々な方法を用意されており、今回すべてを網羅することはできませんが主に使用する事が多い手法について紹介してきます。
◇ 事前知識
・リアルタイムのShadow描画の基礎
まず最初に基本的なリアルタイムのShadow描画方法について触れておきます。最近はRaytracing等も入ってきているので一概には言えませんが、基本的には、ライトから見たときのオブジェクトの遮蔽情報を深度情報(Depth)としてShadowMapと呼ばれるテクスチャに焼き込み、そのShadowMapを参照してShadow描画を行う手法が多いです。
ShadowMapと比較し、そこに光源との距離に差があったら影部分判定し、
そこに光源との距離に差がない場合は、影部分ではないと判定します。
以上ざっくりとしたShadow描画の説明となります。
・UE4のライトについて
Shadow表現を見ていくためにはライトについて知ることが重要です。詳細を知りたい場合は公式ドキュメントを確認することをオススメしますが、ここでも簡単に紹介します。UE4ではライトに対する設定として、大きく3つの状態があります。状態によりいくつかの制限がありますが、その設定により最適な処理を行います。
状態 | |
---|---|
Static(静的) | 実行時位置など動かすことができず、またIntensityなども変更できない |
Stationary(固定) | 実行時位置など動かすことはできないが、一部パラメーターを変更できる |
Movable(動的) | 実行時に動かすことができる |
ライトの可動性 | Unreal Engine Documentation
https://docs.unrealengine.com/ja/BuildingWorlds/LightingAndShadows/LightMobility/index.html
・UE4のShadowの概要
UE4では、ライトを配置しCast Shadowオプションを有効にすると影が表示されますが、この時、ライトの種類や設定などで様々な手法のShadowに切り替えられています。
大まかに紹介すると、StaticライトによるStatic Shadowはテクスチャに焼き付け(Bake)し、動く影表現はできませんが比較的ゲームランタイム中の処理負荷は低く表現できます。
StationaryライトをもとにしたStationary Shadowは、ある制限化を元に高速に動的なShadowを表現します。
MovableライトによるDynamic Shadowは、それぞれにShadow Mapを作ることになるので、アニメーションしたキャラクターなどに対して動く影を作成することができますが、処理負荷的には高くなります。
まずはこれらのShadow描画機能についてそれぞれ紹介していき、後半は組み合わせて使用する少し特殊なShadowを紹介します。
◇ 各Shadowの説明
・Static Shadow
まずはStatic Shadowについて紹介します。
Static ShadowはStatic LightとStatic Meshを対象として予め事前計算(Build)を行いLightmapにライティング情報を格納したもので表現する方法です。動かない対象が対象なため、ライティング情報とともにテクスチャに焼き付けてしまうイメージです。
描画パスとしてはLightingなどではなく、Basepassで描かれることになります。
以下はキャラクター頭上にMeshを配置したレベルで、ビルド後実行した状態となります。
BassPass描画時にSceneColorに描画が行われます。
LightmassでのLightbuildに関しての詳細な挙動は以下が参考になるので、詳しく知りたい場合はこちらを参照してください。
Lightmassの仕組み ~Lightmap編~ (Epic Games Japan: 篠山範明)
https://www.slideshare.net/EpicGamesJapan/lightmass-lightmap-epic-games-japan
・Stationary Shadow
Stationary Lightを用いた際のShadowは大きく分けて2つあり、動くものと動かないもので別々の方法で描画します。
1つ目に、動かないものを対象にStationary Lightをライトビルドすると生成されるShadow Mapを利用したShadowがあります。
(ライトビルドすると生成されるShadow MapはWorld Settingsから実際のテクスチャを確認できます。Static LightのLightmapとは違うので要注意です。)
これはShadow Mapという名前で事前計算データとして保存されるものですが、内容としてはLightmap UVとして展開されたテクスチャにShadowのFactorが保存されています。
注意点として、Shadow Mapという名前ですが、事前知識として紹介したライトからの深度情報が入っているのではなく、どのぐらい影かといったFactorが入っています。
この値はRGBAの各要素にそれぞれマスクのような形で保存され、BasePass時に参照しGBufferEに書き込まれます。
そしてライティング時にこのバッファを使い、マスクのような形で描画する処理が動かないものに対してのShadowになります。
・Pre Shadow
次にStationary Lightを用いた際の動くものに対してのShadowですが、これはPre Shadowと呼ばれています。
Pre Shadowは動くモノ、Skeletal Meshごとにランタイム上でShadow Mapを生成します。
以下の例だとShadowDepthsパスでLightsourceの方向からキャラクターのDepth情報が保存されているのが分かるかと思います。
(1枚のテクスチャ上でアトラス的に書き込みを行っています。)
そのためStationary lightを置いた時に動くものがあると、それだけShadow map生成の負荷が上がる場合があることに注意が必要です。
これら生成したShadowMapをInputとして、LightingのパスであるLight->ShadowedLightsの
ShadowProjectionOnOpaqueパスでShadowの描画を行います。
処理負荷軽減方法の1つとしてPreShadow描画対象モデルのLODを一番低いモノにするオプションがあります。
全体にかかってしまうため注意が必要ですが、LODが適切に設定されていればPreShadow描画時の負荷を削減することができます。
r.Shadow.PreshadowsForceLowestDetailLevel 1
更にStationary Lightについての詳しい検証は、以下ブログ記事が非常に参考になります。
Stationary Light の影について - だらけ者だらけ
https://darakemonodarake.hatenablog.jp/entry/2015/12/16/UE4/Stationary
・Dynamic Shadow
MovableライトのShadowとしてDynamic Shadowがあります。これは処理負荷は高いですが、動的なShadow描画を実現できます。
Dynamic Shadowに関してはライトの種類によって挙動が少し変わってくるので、まずはPointライトから見ていきます。
・Pointライトの場合
Pointライトでは、ShadowDepthsパスでCubemap上に深度情報を書き込みShadowMapを作ります。
影響範囲に入っているオブジェクトを光源を視点として深度情報をShadowMapに書き込みます。
デフォルトだとr.Shadow.CacheWholeSceneShadows 1というオプションが設定されており、動かないモノ(Movable)以外に関しては
以前のShadowMap(キャッシュ)を使い、動く対象物のみだけを書き込みます。
※r.Shadow.CacheWholeSceneShadows 0を設定するとライトが影響を与えるすべてのモノが投入されます。
・Spotライトの場合
Spotライトでは、1枚のテクスチャとしてShadowMapが生成されます。
こちらもキャッシュが有効になっているため、このLevelではキャッシュがコピーとして呼び出されたあと、
Movableが設定されているBox1つのみが描画されています。
・Directionalライトの場合
Directional Lightの場合は、Cascade Shadow Mapと呼ばれる手法が使われ少し特殊な挙動となります。
Directional Lightはどこまでも影響範囲があるため、視点からの距離で分割してShadowMapを作り、複数枚に分けることで広範囲かつ動的なShadowMapを作成します。
設定方法としてはDirectionalライトのCascade Shadow Map設定にあります。
Dynamic Shadow Distance MovableLightがどこまでを対象範囲にするかの視点からの距離で、Num Dynamic Shadow Cascadesが何枚でShadowMapを作成するかです。
設定の関連付けとして、例えば枚数を1枚にして、遠くまで対応しようとすると解像度が足りず影の描画がぼやけていきます。
そのため、この次の項目で紹介するようなDistance Field Shadowなどと組み合わせて利用することが多いです。
枚数を増やすことによって、領域が分割されきれいな表示にすることも可能ですが、いたずらに枚数を増やしたりすると、それだけメモリ量も増えるので注意が必要です。
中身としては、枚数設定した区切り(Split)の枚数分ShadowMapを生成していきます。
ShadowMap生成後の処理
これらのShadowMapを生成したあと、LightingパスであるLights->ShadowedLight->ShadowProjectionInOpaqueパスで
ShadowMapの投影を行い影判定して描画を行います。
◇ その他のShadowについて
ここからは、少し特殊なShadowについて紹介していきます。
。Capsule Shadow
Capsule Shadow(カプセルシャドウ)は主にキャラクターなどで使用するShadow表現方法の1つです。
特徴としては、UE4の物理アセット(カプセル状で近似させたもの)を設定し、その情報をもとに通常のShadowMapでは難しいソフトなShadowを描画します。
(Compute Shaderでカプセルに対してRaymaching的なアプローチで実現しています。)
ShadowMaps | Capsule Shadow |
---|---|
設定方法としては、2つの設定が必要です。
まずアセットの詳細の[Shadow Physics Asset]に対処の物理アセットを設定します。
次にCapsule Direct ShadowまたはCapsule Indirect Shadowオプションを有効化します。
(Capsule Direct Shadowは動的ライティングに対して、Capsule Indirect Shadowは事前計算情報をもとに処理を行います。)
デフォルトだと最終的な描画解像度の半分の解像度で処理を行っており、クオリティを上げたい場合は、
r.CapsuleShadowsFullResolutionを1と設定することでフル解像度で計算されるようになります。
その他、詳しい設定や詳細については以下公式ドキュメントが参考になります。
カプセル シャドウ | Unreal Engine Documentation
https://docs.unrealengine.com/ja/BuildingWorlds/LightingAndShadows/CapsuleShadows/Overview/index.html
・Distance Field Shadow
Distance Field Shadowは、ライトがMovableまたはStationaryの時に使用できるShadowの描画です。手法としてはCapsule Shadowに近く、こちらはカプセルではなくMeshにDistance Fieldと呼ばれる事前計算情報を保存しておき描画を行います。事前に情報を用意する必要があるためキャラクター等SkeletalMeshのアニメーションの影は表現はできません。
Distance Field分のメモリは必要になりますが、綺麗なShadowを遠くまで表現できるため、例えばカメラの近くでは動的なCascade Shadow Mapを使い、遠景のみDistance Field Shadowを使うといったケースがあります。
Distance Fieldは正確にはSigned Distance Field(SDF)を使用しており、これは最も近いサーフェスまでの距離をボリュームテクスチャに格納しています。この情報を元に効率的にRaytrachingを行います。
使用するためにはまず、Project設定からDistance Fieldの有効化が必要です。
設定を有効後Editorを再起動するとDistance Fieldが作成されMeshに保存されます。
(Mesh DistanceFieldsからデータの可視化が可能です。)
次に各種ライトを配置し、DetailsからDistance Field Shadowを有効化します。
以上で設定は完了です。パラメーターを変更することで以下のように遠景はDistance Field Shadowでソフトシャドウ、近景は動く影の作ることができるCascade Shadowといった組み合わせで表現することができます。
その他詳しい設定はドキュメントを参考にしてください。
ディスタンス フィールド シャドウを使用する
https://docs.unrealengine.com/ja/BuildingWorlds/LightingAndShadows/MeshDistanceFields/HowTo/DFHT_1/index.html
ディスタンス フィールド ソフト シャドウ | Unreal Engine Documentation https://docs.unrealengine.com/ja/BuildingWorlds/LightingAndShadows/RayTracedDistanceFieldShadowing/index.html
・Contact Shadows
このContact Shadowsは、Pointライト、Spotライトなど各種ライトの設定の以下項目から設定できる手法です。
デフォルトでオフになっていますが、Contact Shadow Lengthを0以上に設定することで有効化可能です。
特徴としては他のShadow描画と違い、スクリーンスペースで描画するピクセルから逆にライトの方向に探索を行い、Shadowの対象か確認します。
この方法を使用することで、より輪郭までのShadowがくっきりと描画されるようになります。
(たとえば首元や関節など、Self Shadowとなりそうな部分)
弱点としては、追加の処理負荷となること、スクリーンスペースなので近づいた場合などに消えてしまうといった問題があります。
・Preview Shadows
StaticライトやStationaryライトが含まれるレベルで、ライト情報のBuildが行われていないとき、代わりにプレビュー表示としてPreview Shadowsが表示されます。
これはあくまでプレビューなため、実際のBuild設定等により実際の描画と品質が異なる可能性もあり注意が必要です。
上記をビルドしたものが以下となります。StationaryでビルドしたのでShadowMapが作られますが、今回はDefault Cubeを変形させたものを配置しているのでメッシュの分割が少なくBuildしたShadowMapにあまり好ましくないため以下の結果となっています。
以上までが各種Shadow描画方法についての紹介となります。
ここからはいくつかのTipsを紹介します。
◇ Tips
・r.ForceLODShadowを指定してShadow描画のためのMeshを軽量化
r.ForceLODShadowコマンドを設定することでShadowDepthを生成する場合のメッシュを指定のLODに変更することができます。
残念ながら一括指定となってはしまいますが、改造をおこなってモデル別に変更されているケースも有るようです。
4.25~4.26現在不具合が起きていて、このコマンドが使えない状態となってしまっているのですが、
以下のように修正することで動作することが可能です。(既に修正含めて報告済みなので何か進展があれば追記します。)
.\Engine\Source\Runtime\Renderer\Private\ShadowSetup.cpp
・SceneCapture2dのCascade Shadow Mapの解像度を下げる
つい先日、@EGJ-Takashi_Suzuki さんがTwitterに載せていたエンジン改造方法の紹介です。
SceneCaptureのレンダリングの場合Cascade Shadow Map(CSM)は大本のテクスチャサイズで作られてしまうのですが、以下で紹介されている修正を入れることで、解像度を変更することができます。
♰RIP♰
— Takashi.Suzuki (@wankotank) December 15, 2020
シーンキャプチャーレンダリング中のシャドウマップ解像度を(マジックナンバーで)落とす修正。
バグとは関係なかったので供養。 pic.twitter.com/RqyxfXW5gL
小さい画面で使用するケースが多いかと思いますので、フル解像度は必要ない場合もあるかと思います。
その場合解像度を下げることで負荷やメモリ面で処理が稼げる可能性があります。
・UE4のDeferred renderingとForward renderingによるShadowの違い。
レンダリングの設定をDefferardからForwardに切り替えると、Shadowの描画が若干変わることがあります。
その理由の1つとして、Defferardの場合はG-Bufferを参照し法線を用いて補完を行っているのですが、
Forward Renderingの場合は、都合上Depth(深度)のみを利用しています。
具体的には以下のように、ShadowProjectionOnOpaqueパスで違いが起きており、以下シェーダーコードのような調整を行っております。
そのためForwardに切り替えた際は影のバンディング(階調がでる)などが発生していないか注意が必要です。
(Bias等の細かい微調整になるかと思います。)