【Unity】オンデマンドレンダリングで描画処理負荷を抑える

# はじめに
Unity 2019.3から**オンデマンドレンダリング（On-demand rendering）**なるものが追加されました。[^1]
[^1]: オンデマンドは「要求に応じて」の意味。
レンダリングのパフォーマンスには苦労することの多いモバイルアプリ開発などで特に有用そうな機能でしたのでご紹介します。

# どういうもの？
ゲームループ（MonoBehaviour.Updateとか）のフレームレートは通常通りに、レンダリングを一定間隔でスキップできる機能です。
![normal_rendering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/6a5fffda-cdd8-9d46-ef99-009c9906c95f.png)

![ondemand_rendering.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/abfecdc7-9d0c-6b56-ac87-ca689bc1b4db.png)


これの一番の使いどころは（特にモバイル端末での）**「レンダリング処理負荷低減」**でしょう。
例えばユーザー入力待機画面のような画面の動きが激しくない（＝見た目のフレームレートを落としても影響が小さい）場面なら、レンダリングを適度にスキップすることでクオリティそのままに端末の発熱や消費電力を減らすことが期待できます。

また、従来からあるフレームレート調節方法である[Application.targetFrameRate](https://docs.unity3d.com/jp/current/ScriptReference/Application-targetFrameRate.html)を変える方法よりも、**入力の応答性**という点でより良い選択になると考えられます。[^2]
[^2]: そもそもApplicationクラスの値は頻繁に変えることになんか抵抗があります…。
例として、入力がない間はフレームレートを落としておき、入力があったタイミングでフレームレートを上げて応答することを考えた際に下の違いがあります。

![target_frame_rate_interaction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/962af431-3a54-0023-3d54-fce345cd4c36.png)

![ondemand_rendering_interaction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/871882bb-930f-10e3-f858-01d8a4de5039.png)

あとは、**シミュレーションや機械学習**といったシチュエーションでも望まれていた機能ではないかと思います。
それらは実際は**レンダリングを必要としないことがほとんど**なので、この機能を活用してレンダリングにリソースを割かないようにすればより早く結果を得られると考えられます。

# 使用方法
制御はスクリプトから行います。
名前空間**UnityEngine.Rendering**の**[OnDemandRendering](https://docs.unity3d.com/ScriptReference/Rendering.OnDemandRendering.html)**というクラスに3つのStaticなAPIが用意されています。

## [OnDemandRendering.renderFrameInterval](https://docs.unity3d.com/ScriptReference/Rendering.OnDemandRendering-renderFrameInterval.html)
私たちが直接変更できる唯一のプロパティであり、これでレンダリングをおこなう間隔を設定します。
例えばこれに**3を代入すれば**、ゲームループの**3フレーム中に1回レンダリング**するようになります。（レンダリング更新頻度が1/3）

タッチ入力があるまで20[fps]にしておき、タッチがあったらそこから60[fps]にする例はこんな感じです。

```cs:OnDemandRenderingTest.cs
using UnityEngine;
using UnityEngine.Rendering;

public class OnDemandRenderingTest : MonoBehaviour
{
    private void Start()
    {
        QualitySettings.vSyncCount = 0;
        Application.targetFrameRate = 60;

        // 20 (= 60 / 3) fps
        OnDemandRendering.renderFrameInterval = 3;
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // 60 fps
            OnDemandRendering.renderFrameInterval = 1;
        }
    }
}

```

## [OnDemandRendering.effectiveFrameRate](https://docs.unity3d.com/ScriptReference/Rendering.OnDemandRendering-effectiveRenderFrameRate.html)
現在の設定から想定されるレンダリングフレームレート値です。
例えばゲームループを60fpsで動かしている端末なら、通常はレンダリングも60fpsですが、**renderFrameIntervalを3に設定しているときは20**（=60 / 3）が返ってきます。

## [OnDemandRendering.willCurrentFrameRender](https://docs.unity3d.com/ScriptReference/Rendering.OnDemandRendering-willCurrentFrameRender.html)
現在のゲームループフレームがレンダリング対象かを返します。

![will_current_frame_render.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483690/aaa0d74f-6f0f-e55a-6770-50c6b946852b.png)

これは何に使えるのか…ということですが、レンダリングにリソースを割かなければならないフレームかどうかが判断できるので、**「見た目のフレームレートを安定させつつ、高負荷な処理を裏で回す」**的なことに使えるっぽいです。
なかなか使う場面は限られそうですが、使い方のイメージとしてはこんな感じでしょうか。

```cs:OnDemandRenderingTest2.cs
using UnityEngine;
using UnityEngine.Rendering;

public class OnDemandRenderingTest2 : MonoBehaviour
{
    private void Update()
    {
        if (OnDemandRendering.willCurrentFrameRender)
        {
            // レンダリングするので重い処理はしない
        }
        else
        {
            // レンダリングしてないので重い処理を実行
        }
    }
}
```

# おわりに
今回は特に検証などはしていないですが、上手に使えば高い効果が得られそうです。
モバイルアプリ開発なんかでは積極的に取り入れていきたい機能だと感じました。

# 参考
- [Unity Blog オンデマンドレンダリングでモバイルのパフォーマンスが向上するしくみ](https://blogs.unity3d.com/jp/2020/02/07/how-on-demand-rendering-can-improve-mobile-performance/)