【Universal RP】ライティング計算の実装を読んでみた

# はじめに
URP向けのシェーダーを書くとき、ライト計算を自前で実装したくなることがあります。
今回は、URPのライティング計算はどこに実装されているのかを調べてみました。


ちなみに、URPのサンプルシーンのオブジェクトにはLit.shaderが使用されており、PBRのライティングが実装されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/e911e344-166d-3d65-58b9-13f87fe635e9.png)


## 環境
Universal RP 8.2.0
Unity 2020.1.2f1

## リアルタイム ライト
Lit.shaderのPBRリアルタイムライト計算は、**Lighting.hlsl** の ```UniversalFragmentPBR``` メソッドに実装されています。

```Lighting.hlsl
///////////////////////////////////////////////////////////////////////////////
//                      Fragment Functions                                   //
//       Used by ShaderGraph and others builtin renderers                    //
///////////////////////////////////////////////////////////////////////////////
half4 UniversalFragmentPBR(InputData inputData, half3 albedo, half metallic, half3 specular,
    half smoothness, half occlusion, half3 emission, half alpha)
{
    BRDFData brdfData;
    InitializeBRDFData(albedo, metallic, specular, smoothness, alpha, brdfData);
    
    Light mainLight = GetMainLight(inputData.shadowCoord);
    MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));

    half3 color = GlobalIllumination(brdfData, inputData.bakedGI, occlusion, inputData.normalWS, inputData.viewDirectionWS);
    color += LightingPhysicallyBased(brdfData, mainLight, inputData.normalWS, inputData.viewDirectionWS);

#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
        color += LightingPhysicallyBased(brdfData, light, inputData.normalWS, inputData.viewDirectionWS);
    }
#endif

#ifdef _ADDITIONAL_LIGHTS_VERTEX
    color += inputData.vertexLighting * brdfData.diffuse;
#endif

    color += emission;
    return half4(color, alpha);
}
```

# ライトの検証
ライトの挙動を検証するため、以下のようなシーンを用意してみました
・Directional Light を配置して、影を出す
、Point Light を１つ配置
・すべてのオブジェクトにはLit.shaderをアタッチ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/7345ec5c-bfea-76b0-945f-273782b46c1d.png)


# Main Light
Main Light は以下のように実装されています。(Lighting.hlsl)

```Lighting.hlsl
    Light mainLight = GetMainLight(inputData.shadowCoord);
    MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));

    half3 color = GlobalIllumination(brdfData, inputData.bakedGI, occlusion, inputData.normalWS, inputData.viewDirectionWS);
    color += LightingPhysicallyBased(brdfData, mainLight, inputData.normalWS, inputData.viewDirectionWS);
```


## 比較
```LightingPhysicallyBased()``` の呼び出しをコメントアウトして、画面がどのように変化するのかを見ていきます。

### 変更前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/7345ec5c-bfea-76b0-945f-273782b46c1d.png)

### 変更後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/c2532fe1-d5ab-cb95-3ec7-096ade8c6446.png)

DirectionalLightによる光が消え、画面全体が暗くなりました。

# Main Lightの落ち影を消す
**LitForwardPass.hlsl** を見ると、シャドウマップ用のテクスチャ座標(```shadowCoord```)の計算処理があります。


```LitForwardPass.hlsl
#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
    inputData.shadowCoord = input.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
    inputData.shadowCoord = TransformWorldToShadowCoord(inputData.positionWS);
#else
    inputData.shadowCoord = float4(0, 0, 0, 0);
#endif
```

この```shadowCoord``` は Main Light の影に利用されます。

```Lighting.hlsl
Light GetMainLight(float4 shadowCoord)
{
    Light light = GetMainLight();
    light.shadowAttenuation = MainLightRealtimeShadow(shadowCoord);
    return light;
}
```

## 比較
```inputData.shadowCoord``` の計算を削除したときの画面の変化を見ていきます。


## 変更前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/43b57aa9-a969-241b-d159-b54bb40fa9b0.png)

## 変更後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/5a0f1b1b-8a47-41c2-aa14-3ad15e813919.png)

落ち影が無くなりました。


# Additional Light
ポイントライトなどの追加ライトは以下のコードで実装されています。(Lighting.hlsl)
 
```Lighting.hlsl
#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS);
        color += LightingPhysicallyBased(brdfData, light, inputData.normalWS, inputData.viewDirectionWS);
    }
#endif
```

## 比較
```LightingPhysicallyBased()``` の呼び出しをコメントアウトしたとき、画面がどのように変化するのかを見ていきます。

### 変更前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/a8d23c60-9daf-bcc1-46ab-2f1609c7524a.png)

### 変更後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/6474ca5c-226d-a755-be17-91f74daed70e.png)

ポイントライトが照らされている部分が暗くなりました。

## Baked GI (ライトマップ)
ライトのベイク情報の取得は、**LitForwardPass.hlsl** に実装されています。
実装コードは以下になります。

```LitForwardPass.hlsl
inputData.bakedGI = SAMPLE_GI(input.lightmapUV, input.vertexSH, inputData.normalWS);
```


## 比較
```SAMPLE_GI()``` マクロの呼び出しを削除したときに画面の変化を見ていきます。


### 変更前
今回は、ライトをベイクしたシーンを使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/4b4e006f-cb5d-de40-2dac-c647238dee5f.png)

### 変更後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44288/4475f0ec-a858-14b3-a97c-98658b28ef0c.png)

SAMPLE_GI()の呼び出しを削除すると、ライトがベイクされていない状態と同じ見た目になります。


## 参考
Unity URP のライティング
https://note.com/npaka/n/n22986e11615e

Unity Learning Materials - ライティングのセッション
https://learning.unity3d.jp/tag/%E3%83%A9%E3%82%A4%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0/