はじめに
Unityのシャドウマッピングについて、調べてみたものをまとめてみようと思います。
環境
Unity2020.2.0f1
Universal RP 10.2.2
シーンに光源を置いてみる
シーンに緑色のDirectional Lightを置くと、以下のようになります。
光が当たった領域は緑色になりますが、影の領域には色を与えません ( = 真っ黒になります)
シャドウマップ
Unityでは、「ある領域が影になっているかどうか」の判定にシャドウマップを利用します。
シャドウマップのベイク
点光源を例に見てみます。
点光源から、シーンのオブジェクトに最も近い点までの距離を求め、それをテクスチャ(シャドウマップ)に保存します。
テクスチャは0 ~ 1 までの値しか保存できないので、範囲[0, maxDistance] を [0, 1] へ変換したものをテクスチャに保存します。
影の最大距離(maxDistance) はURP の Pipelineアセットの Shadows の部分から設定できます。
シャドウマップを利用した影の判定
カメラで描画しようとしている点とライト間の距離D、シャドウマップに保存されている距離dを比較して、影かどうかを判定します。
ちなみに、MainLightのシャドウマッピングは Universal RP パッケージ内部の MainLightShadowCasterPass.cs にて実装されており、実装の内容を見ることができます。
FrameDebugger で シャドウマップを見てみる
以下のようなシーンを作成して、シャドウマップを見てみます。
DrawOpaqueObjects パスからシャドウマップを Ctrl + 左クリックすることで、シャドウマップを確認することができます。
シャドウマップ
今回は以下のようなシャドウマップが確認できました。
このシャドウマップは、カスケードシャドウマップになっています。
おまけ : シャドウマップの実装場所
Universal RP の 中を見ると、シャドウマップを利用している処理を見ることができます。
Shadows.hlsl
Shadows.hlsl の中身を見ると、シャドウマップをサンプリングしている関数があります。
half MainLightRealtimeShadow(float4 shadowCoord) { #if !defined(MAIN_LIGHT_CALCULATE_SHADOWS) return 1.0h; #endif ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData(); half4 shadowParams = GetMainLightShadowParams(); return SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), shadowCoord, shadowSamplingData, shadowParams, false); }
Lighting.hlsl
上記のMainLightRealtimeShadow関数は、 Lighting.hlsl にて利用されています。
Light GetMainLight(float4 shadowCoord) { Light light = GetMainLight(); light.shadowAttenuation = MainLightRealtimeShadow(shadowCoord); return light; }
上記のGetMainLight関数は URP の PBRシェーディングを行う関数 UniversalFragmentPBRの中で利用されています。(場所はLighting.hlslです)
Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, shadowMask); #if defined(_SCREEN_SPACE_OCCLUSION) AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(inputData.normalizedScreenSpaceUV); mainLight.color *= aoFactor.directAmbientOcclusion; surfaceData.occlusion = min(surfaceData.occlusion, aoFactor.indirectAmbientOcclusion); #endif MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI); half3 color = GlobalIllumination(brdfData, brdfDataClearCoat, surfaceData.clearCoatMask, inputData.bakedGI, surfaceData.occlusion, inputData.normalWS, inputData.viewDirectionWS); color += LightingPhysicallyBased(brdfData, brdfDataClearCoat, mainLight, inputData.normalWS, inputData.viewDirectionWS, surfaceData.clearCoatMask, specularHighlightsOff);