【Unity】現在描画に利用しているカメラの取得方法

# はじめに
現在の描画に利用しているカメラを取得する際に `Camera.current` というプロパティから取得できるはずが、取得しても以下の様に`null` になってしまっていたので原因を調べてみました。

```cs
void Start()
{
    var currentCamera = Camera.current;
    Debug.Log(currentCamera);
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/85a09e68-1ec7-6643-2d91-2e6d44fc7b0f.png)

# Built-in Render Pipelineの場合の取得方法

公式ドキュメントの記載の通り、`OnRenderImage、OnPreRender、OnPostRender`  以外では取得できないようです。

> この関数は、 MonoBehaviour.OnRenderImage、MonoBehaviour.OnPreRender、MonoBehaviour.OnPostRenderのイベントのいずれかを実装する場合にのみ使用します 。

https://docs.unity3d.com/ja/2023.2/ScriptReference/Camera-current.html

BRPの場合は公式に従って以下の様にそれぞれのイベント

```cs
    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        Debug.Log($"OnRenderImage : {Camera.current}");
    }
    
    private void OnPreRender()
    {
        Debug.Log($"OnPreRender : {Camera.current}");
    }
    
    private void OnPostRender()
    {
        Debug.Log($"OnPostRender : {Camera.current}");
    }
```

また、これらのイベントはCameraコンポーネントと同一のゲームオブジェクトにアタッチする必要があるので現在のレンダリング中のカメラを取得する用途には制約が多い印象です。
そこで、`OnRenderObject`イベントで取得することでCameraをアタッチしているGameObject外でもCamera.currentを取得できるようです。

```cs
    private void OnRenderObject()
    {
        Debug.Log($"OnRenderObject : {Camera.current}");
    }
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/a8d65c08-d388-a4a7-6670-925eb71104fd.png)


# URPの場合の取得方法
URPでは`OnRenderImage、OnPreRender、OnPostRender`に加えて`OnPostRender`これら全てが機能しないため、公式ドキュメントに記載されている方法ではCamera.currentを取得することができません。

以下ログの様にイベント自体が呼ばれていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/01941189-05d5-b079-83dd-5fc906a01684.png)

そこで描画タイミングで発生するイベント`RenderPipelineManager.beginCameraRendering`を購読することで最新の描画に利用したカメラを取得する方法でCamera.currentっぽい値を取得してみました。

Scene構成は以下の形で、MainCameraがpriorityを0、SubCameraがpriorityを-1に設定してMainCameraの優先度の方が高くなるように設定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/13119221-5c47-12b4-0bc8-c1ba9cf7f637.png)

```cs
public class CameraCheck : MonoBehaviour
{
    // 直近で描画に利用したカメラ
    Camera _currentCamera;
    private void Start()
    {
        // カメラ描画イベントを購読する
        RenderPipelineManager.beginCameraRendering += WriteLogMessage;
    }
    private void OnDisable()
    {
        // カメラ描画イベントを解除する
        RenderPipelineManager.beginCameraRendering -= WriteLogMessage;
    }

    void WriteLogMessage(ScriptableRenderContext context, Camera camera)
    {
        Debug.Log($"Beginning rendering the camera: {camera.name}");
        
        // 最新の描画用カメラとして登録する
        _currentCamera = camera;
    }

    private void Update()
    {
        // 最後に描画したカメラを表示する
        Debug.Log("_currentCamera : " + _currentCamera);
    }
}

```

この様にすることで以下のログのように優先度を高く設定した`MainCamera`が最新のカメラとして取得できるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1336886/4107f9b4-4097-7d5d-b498-8d77d0efd9e7.png)


本記事について、もっと簡単に実現できる方法があればコメントを頂けると幸いです。

# 余談
ちなみに最後のActionを使う方法ですが`UniRx`や`R3`で実装するとこの様になります。
取得したいフレームだけイベント購読する。みたいな方法で使えば便利かも知れません。

```cs
    private void Start()
    {
        Observable.FromEvent<Action<ScriptableRenderContext, Camera>, Tuple<ScriptableRenderContext, Camera>>
        (
            h => (x, y) => h(Tuple.Create(x, y)),
            handler => RenderPipelineManager.beginCameraRendering += handler,
            handler => RenderPipelineManager.beginCameraRendering -= handler
        ).Subscribe(arg => WriteLogMessage(arg.Item1, arg.Item2));
    }
```

ActionをStreamに変換することができる`Observable.FromEvent`ですが、今回の様に引数が複数になるとタプルに変換して戻すような処理が入り一気に複雑な記述になってしまうため、可読性が悪いためActionのまま実装した方が良い気がしました。。。