rn.log

備忘録など

【Unity】シャドウマッピングについて

環境

Unity2020.2.0f1
Universal RP 10.2.2

シーンに光源を置いてみる

シーンに緑色のDirectional Lightを置くと、以下のようになります。

f:id:r-ngtm:20210126050256p:plain:w480
緑のDirectional Lightを配置

光が当たった領域は緑色になりますが、影の領域には色を与えません ( = 真っ黒になります)

シャドウマップ

Unityでは、「ある領域が影になっているかどうか」の判定にシャドウマップを利用します。

f:id:r-ngtm:20210126072742p:plain:w640
シャドウマップの深度と描画点の深度を比較

シャドウマップのベイク

点光源を例に見てみます。
点光源から、シーンのオブジェクトに最も近い点までの距離を求め、それをテクスチャ(シャドウマップ)に保存します。
テクスチャは0 ~ 1 までの値しか保存できないので、範囲[0, maxDistance] を [0, 1] へ変換したものをテクスチャに保存します。

f:id:r-ngtm:20210126072044p:plain:w640
光源から見て最も近いオブジェクトまでの距離をシャドウマップへ保存

影の最大距離(maxDistance) はURP の Pipelineアセットの Shadows の部分から設定できます。

f:id:r-ngtm:20210126072552p:plain:w320
影のmaxDistance

シャドウマップを利用した影の判定

カメラで描画しようとしている点とライト間の距離D、シャドウマップに保存されている距離dを比較して、影かどうかを判定します。

f:id:r-ngtm:20210126072742p:plain:w640
シャドウマップの深度と描画点の深度を比較


ちなみに、MainLightのシャドウマッピングは Universal RP パッケージ内部の MainLightShadowCasterPass.cs にて実装されており、実装の内容を見ることができます。

FrameDebugger で シャドウマップを見てみる

以下のようなシーンを作成して、シャドウマップを見てみます。

f:id:r-ngtm:20210126080022p:plain
仮のシーン

DrawOpaqueObjects パスからシャドウマップを Ctrl + 左クリックすることで、シャドウマップを確認することができます。

f:id:r-ngtm:20210126075939p:plain
DrawOpaqueObjects

シャドウマップ

今回は以下のようなシャドウマップが確認できました。

f:id:r-ngtm:20210126080150p:plain
シャドウマップ

このシャドウマップは、カスケードシャドウマップになっています。

f:id:r-ngtm:20210126081532p:plain
シャドウマップの詳細
f:id:r-ngtm:20210126081731p:plain
カスケードの最大距離

おまけ : シャドウマップの実装場所

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);