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

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

#unity Mixed LightingのSubtractiveモードでのリアルタイムシャドウ色の手動調整について解説する

 シェーダー本4がようやく脱稿しました(宣伝エントリは後で作ります)。


 ブログは独自ドメイン取ったりしたものの、最適化他の作業が後回しになってたりして(httpドメインが残ってるとか)宿題がたまってる(ツイートボタンを一時的に消したの4ヶ月ぶりに思い出した)んですが、ぼちぼち更新を再開したいと思っています。

記事の位置づけ

 今回はMixed LightingのSubtractiveモードについて。毎回Subtractiveモードの挙動が良くわからなくなるので、ここで機能とサンプルをまとめておきます。
 リアルタイムGI(Enlighten)が実装されたUnityでは、Subtractiveモードは互換性のために遺しているのだと勝手に思っていたのですが、そのEnlightenが廃止され(!)、その上URPではShadowMaskモードも非対応(!!)なので、Subtractiveモードがまた意味を持つようになりました*1
 なお、言及しているのは全てURPでの挙動になります。とはいえ、Unity組み込みレンダーパイプライン(≒Standardシェーダー)でもほぼ同じ処理になっていると思われます。

Subtractiveモードとはなにか

f:id:t_tutiya:20201004190036p:plain
 Subtractiveモードは、Unityで大域照明(GI)を使用する時に、Lighting Modeで選択できるモードの一つです*2
 Subtractiveモードでは、静的オブジェクトに対し「①ライトマップからサンプリングしたライトカラー値」を反映するのとは別に、「②メインライトによって動的オブジェクトからキャストされるシャドウ」も同時に反映します*3。通常、動的オブジェクトがキャストするシャドウは、静的オブジェクトには描画されないのですが、Subtractiveモードでは(メインライトに限り)それが出来るわけです。
 ところで、この「②動的オブジェクトからキャストされるシャドウ」というのは(何らかのカラー値ではなく)減衰率でして、通常は元となるライトカラー値に乗算してシャドウのかかり具合を示します。しかし、「①ライトマップからサンプリングしたライトカラー値」には、シャドウの効果が既に反映されている(場合がある)ため、それに減衰率を乗算すると、シャドウが2重に反映されてしまい不自然な描画になってしまいます。そのため、Litシェーダーではこの現象を抑制するブレンド処理が内部的に行われています。

Subtractiveモードではリアルタイムシャドウ色の手動調整が必要

 ここからが本題なのですが、先述したブレンド処理はURPが全自動で計算してくれるわけでありません。これは、ライトマップに記録された各ピクセルについて「シャドウによる減衰が発生しているのか否か」という判断と、「発生していた場合のRGB各チャンネルのシャドウ減衰率」を計算することが、システムには困難なためです。シェーダーコード内である程度の推測による自動化されているものの、手動による微調整が必要になります。
 そのため、Subtractiveモードを使用する場合、ユーザーはLightingウィンドウのRealtime Shadow Colorプロパティを更新し、手作業で調整する必要があります。
 以下、その手動調整作業についてのサンプルを上げておきます。

Subtractiveモードでのシャドウ手動調整サンプルA

サンプルA-1 白色光での補正前

f:id:t_tutiya:20200816180644p:plain
 サンプルシーンは「地面(静的)」「壁(静的)」「球体(動的)」から構成されています(ベースカラーはすべて(255,255,255))。シーンに配置されているライトはディレクショナルライト(Mixed)のみです(ライトカラーは(255,255,255))。ライトマップはベイク済みで、壁から地面に落ちたシャドウはライトマップからサンプリングした物、球体から地面に落ちたシャドウはリアルタイムに計算された物です。
 見て分かる通り、2つのシャドウが重なった部分がおかしな事になっています。ここでRealtime Shadow Colorは(0,0,0)に設定しています。

サンプルA-2 白色光での補正後

f:id:t_tutiya:20200816180649p:plain
 Realtime Shadow Colorを(111,129,158)に設定しました。シャドウが馴染んで境界線が見えなくなりました(完全に消えたわけではなく、アップにすると境界がわかります)。
 余談ですが、Realtime Shadow Colorの値を手作業で決定するのは(少なくとも土屋は)大分難しいのですが、シーンウィンドウで地面に落ちたシャドウ部分の色をスポイトで拾うと、適切な色が簡単に取得できて効果的でした。

Subtractiveモードでのシャドウ手動調整サンプルB

 次のサンプルでは、ライトカラーが変わる場合にRealtime Shadow Colorの再調整が必要であることを示します。

サンプルB-1 ライトの色変更&再ベイク

f:id:t_tutiya:20200816180659p:plain
 まずディレクショナルライトのライトカラー値を(0,128,128)に変更して、ライトマップをベイクしなおしました。

サンプルB-2 ライトの色に合わせた補正

f:id:t_tutiya:20200816180654p:plain
 この状態で、Realtime Shadow Colorを(93,119,153)に設定し、影を馴染ませました(これもスポイトで色を決めています)。

サンプルB-3 白色光に変更&再ベイク

f:id:t_tutiya:20200816180702p:plain
 さらにこの状態でディレクショナルライトのカラーを(255,255,255)に戻して再度ライトマップをベイクしました。また描画が不自然になっているのがわかります。
 このように、Realtime Shadow Colorの調整は、ベイクされたライトの色に合わせて行う必要があります。

まとめ

 ライトマップとメインライトシャドウをブレンドする計算が(シェーダーコードを読めるとはいえ)微妙にブラックボックス化していることもあり、Subtractiveモードはとっつきが悪いかもしれません。とはいえ、静的オブジェクトにリアルタイムシャドウが投影されるのは演出効果としても非常に強力なので、使いこなして行きたい所です*4

*1:URP ver7.3.1現在。リアルタイムGIとShadowMaskモードは、どちらも将来的に対応予定とのことです。

*2:"subtractive"は「引く」の意味。後述するシャドウの2重減衰から、不要な効果を取り除く意味かと思われます。

*3:メインライトのModeがMixedに設定している場合。

*4:URPがリアルタイムGIに対応するまで。