土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

#unity ShadowmaskモードとDistance Shadowmaskモードの違いについて

はじめに

 現在、技術書典6に向けてシェーダー本の第3巻を執筆中です。当初の予定では、第3巻は「ライティング&GI(大域照明)編」と題して、Unityライティングシステムの解説と、エディタ上の各パラメータをシェーダーコード上で使用する方法についてまとめる予定でした。
 しかし、いざ初めてみると、「エディタ上の各パラメータ」を網羅するだけでページが足りなくなる事が判明し(!)、急遽今回は「ライティングシステム解説編」として、シェーダーコードの実装は第4巻に送ることになりました。もう少し減る予定ですが、現在解説する予定のパラメータは200個近く(!)あります。
 なので、多分今回はサンプルコードがなく「シェーダープログラミングの教科書」と言えない気もするのですが、Unityライティングシステムについて世界でもっとも詳しい本になる予定なので、どうぞよろしくお願いします。
 今回は、執筆中Distance Shadowmaskについて調べた際の自分用メモ的な物です。

Distance Shadowmaskモードとは何か。

 Distance Shadowmaskモードとは、UnityのGIのモードの一つです。以下の設定で有効になります(これはプロジェクトのデフォルト設定でもあります)

Lighingウィンドウ>Mixed Lighting
・Baked Global Illuminationをオン
・Lighting ModeをShadowmaskに設定
Project Settings>Quality>Shadows
・Shadowmask ModeをDistance Shadowmaskに設定

 Distance Shadowmaskモードでは、シャドウマスクの使用に加えて、Shadow Distance距離内ではシャドウマップを使用します。とはいえ、この辺の関係が非常にわかりにくいので、「シャドウマスクの使用に加えて、Shadow Distance距離内ではシャドウマップを使用します」というのがどういう挙動を示すのかについて、順に見ていきます。

リアルタイムライト

 まず、一番シンプルな、ベイクなしのRealtimeモードのライトのみの絵を見てみます。
f:id:t_tutiya:20190216143726p:plain
 一番上の白いオブジェクトと一番下の平面はStatic、間にある赤いオブジェクトは非Staticです。ディレクショナルライト(Realtimeモード)が上から当たっていて、それぞれのオブジェクトの影が下のオブジェクトに投影されているのが分かります。
 ちなみになんですが、上記の画像はRealtime GIが有効になっています(ライトがRealtimeなので、Baked GIの有効無効はここでは関係ありません)。Realtime GIも無効にすると下図のようになります。
f:id:t_tutiya:20190218230132p:plain
 空中のオブジェクトの側面部や、プレーンに投影されたが真っ黒になっているのが分かります。Realtime GIをオフにすると間接光が生成されなくなるため、ディレクショナルライトに面を向けていないサーフェイスはこのように黒く描画されます。

Bake

 次に、ライトをBakedにしてライトマップをベイクします。分かりやすさのために、ライトマップの解像度をかなり下げています(シャドウマスクテクスチャの解像度はライトマップと同じになります)。
f:id:t_tutiya:20190218225511p:plain
 見て分かる通り、白いオブジェクトが投影する影がぼんやりしています。これは、staticオブジェクトの影がシャドウマスクテクスチャにベイクされるようになったためです(改めて補足:説明の為に解像度を低くしているので極端にぼんやりしたシャドウになっていますが、適切な解像度に設定すれば自然なシャドウがベイクされます)。
 Bakedのライトの場合、非Staticオブジェクトにはライトが当たりません。そのため、赤いオブジェクトは真っ暗になっています。リアルタイムシャドウも投影されないので、シャドウは一個だけです。

Shadowmask(非Distance)

 次に、ライトをMixedにして、ライトマップをベイクします。シャドウマスクのモードは非Distanceです。
f:id:t_tutiya:20190216143753p:plain
 赤いオブジェクトが投影するシャドウが描画されるようになりました。こちらは非staticのためシャドウマップ経由で影を描画しています。シャドウマップは通常の解像度を設定しているので、極端に解像度を落としたライトマップにベイクされた中央のシャドウと比べて輪郭がはっきりしています。
 気になるのは白いオブジェクトから赤いオブジェクトに投影していた影が消えている点です。大きな変化なので違和感があるのですが、これは考えてみると当然でして、そもそもなぜライトマップに影をベイクするのかと言えば、それによってリアルタイムに影を演算するCPUコストを削減したかったからでした。その為、staticオブジェクトから非staticオブジェクトにはリアルタイムシャドウが投影されません。

余談1

 この状態でライトを反映させる方法として、ライトプローブを使用できます。ライトプローブを配置すると、以下のように影にいる時は暗く見えます。
f:id:t_tutiya:20190216143813p:plain
f:id:t_tutiya:20190216143848p:plain
 ライトプローブではメッシュ全体に同じ補間値が設定されてしまうので、これだと違和感がある場合は以下のようにLPPV(Light Probe Proxy Volume)を使用します。
f:id:t_tutiya:20190216143845p:plain
 メッシュの一部に影が落ちているのがわかるでしょうか?

Shadowmask(Distance Shadowmask)

 ではいよいよDistance Shadowmaskモードでベイクしてみます。
f:id:t_tutiya:20190216143842p:plain
 赤いオブジェクトに投影される影も、平面に投影される影も綺麗に描画されています。Distance Shadowmaskモードでは、カメラからShadow Distanceで設定された距離内のオブジェクトについてはシャドウマップを使ったリアルタイムシャドウが処理されるので、このようになります。
 次の図は、Shadow Distanceを小さくした物です。
f:id:t_tutiya:20190216143838p:plain
 リアルタイムライトのシャドウは、カメラからの距離がShadow Distanceに近づくと弱くなり、Shadow Distanceに到達した時点で見えなくなります。しかし、StaticオブジェクトからStaticオブジェクトに投影されているシャドウは、途中からシャドウマスクに書き込まれたシャドウに切り替わり、影が描画されます。Shadow Distanceより遠くのオブジェクトについてはリアルタイムシャドウが処理されないので、処理コストは抑えられているわけです。

余談2

 Shadow Distance付近で発生するシャドウのフェードですが、どうもこれ、フェードの有無をオンオフしたり、フェード強度を設定したりする方法はないようです。マジかー(白目)。

まとめ

 ShadowmaskとDistance Shadowmaskのどちらを使うべきかは状況によるので一概に言えません。後者の方が処理コストが高いのは間違いないですが、違和感のない描画を実現します。