【Unity】PostProcessingStack V2のパラメータをTimelineで動的に制御したい

こんにちは。八ツ橋まろんです💡

UnityのTimeline と PostProcessingStack V2を上手く使うための補助スクリプトの話です

###・Unity の Timeline
UnityのTimeline使ってますか？私はめっちゃ使ってます✨

Unityを使ってミュージックビデオを作っているのですが、Timelineを使うとカメラワークが簡単に作れるし、アニメーションとカメラワークのタイミングを合わせるのがすごく楽になります✨

「脱法ロック/歌ってみた (八ツ橋まろん)」
https://www.youtube.com/watch?v=m2iq4w1YXSo
[GIF] ↓↓
![Qiita4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478685/22acc8b6-b4bd-a15f-2248-92a6f7484352.gif)


**GameObjectにAnimatorを付けて録画ボタンを押すだけで、ComponentのパラメータをTimeline上でいじるとキーフレームが打たれて自由に制御できる**のはとっても便利です。[GIF]↓↓
![Qiita3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478685/48a56120-e149-29b6-b554-d8ae6249fbbd.gif)


###・Unity の Post Processing Stack V2
また、エモい絵を作るためにPostProcessingStack V2は必須です。有り/無し で見た目がこんなに変わります。[PNG]↓↓
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478685/78ba6848-adab-902f-5bc7-d55962ee081a.png)



導入、使い方は他の記事にお任せしますが、使うときはPostProcessingVolume Component(PPV)をGameObjectにアタッチします。

###・Timeline × Post Processing Stack V2 ??
さて、Timelineを使えばComponentのパラメータをキーフレームとして打って自由に制御できると書きましたが、PPVに関しては特殊で、コイツは**パラメータをいじってもキーフレームが打てません！！**

ナンテコッタ！！これではPPVの動的な変化ができないっ！！でも、いじりたい場面があるんじゃ！！


ということで調べたんですが、
[A] Cinemachineを導入することでPPVの制御モドキができる模様。
[B] 普通のスクリプトならTimeline上でキーフレームが打てるということで、どうにかスクリプトを自作する。

以上2通りの策を見出しました。

**[A] CinemachineのVirtual Camera 2つに違うパラメータのCinemachinePostProcessing(Virtual Camera用のPPV)を割り当てて、Timeline上で混ぜることで線形補完する**

※Cinemachineの導入は他の記事参照。

2つのVirtual CameraをTimeline上で混ぜると両者を線形補完してくれるので、これを使うことで、混ざってる間は動的に変化します。これで事足りることも多い。[GIF]↓↓
![Qiita5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478685/0101cba3-d1a7-79d8-d833-2d3c45ae4328.gif)


**[B] PPVのパラメータと連動するスクリプトを作ってスクリプトのパラメータをTimelineでいじることで間接的にPPVもいじれるようにする**

こんな感じになります。PPVのVignetteのIntensityとスクリプトのパラメータが連動していますね。Timelineでスクリプト側のパラメータを制御→間接的にPPVも制御という風になっています。[GIF]↓↓
![Qiita6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478685/8deacf6d-dd04-d629-bd8a-f41f0e6f646b.gif)


ということで、今回は[B]の、**「TimelineでPPVのパラメータをいじれるようにする補助スクリプトを作る」**という記事です。

早速ですが、完成したスクリプトがこちら。

```c#:PostProcessController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing; //PostProcessingを扱うのに、この行が必要です。

/// <summary>
/// パラメータを追加したい場合は
/// ①変数宣言
/// ②OnEnable関数内を追加(必要あれば)
/// ③Update関数内を追加
/// ④ApplyParams関数内を追加
/// </summary>


// 再生中じゃなくてもスクリプトを適用する
[ExecuteInEditMode]


// TimelineでパラメータをいじるにはAnimatorが付いている必要があるので、
// このスクリプトを付けると自動でAnimatorがアタッチされるように以下を追記します。
[RequireComponent(typeof(Animator))]


public class PostProcessController : MonoBehaviour
{
    [SerializeField]
    PostProcessProfile postProcessProfile;
    PostProcessProfile postProcessProfileStored; //PostProcessProfleの取得・破棄の監視用
    ///////////////////////////////////
    /// PostProcessVolumeの要素たち
    ///////////////////////////////////

    //*********************
    // AmbientOcclusion
    //*********************
    AmbientOcclusion ambientOcclusion;

    //*********************
    // AutoExposure
    //*********************
    AutoExposure autoExposure;

    //*********************
    // Bloom
    //*********************
    Bloom bloom;

    //Intensity
    [SerializeField, Range(0, 10)]
    public float bloomIntensity;
    float bloomIntensityStored;

    //*********************
    // ChromaticAberration
    //*********************
    ChromaticAberration chromaticAberration;

    //*********************
    // ColorGrading
    //*********************
    ColorGrading colorGrading;


    //*********************
    // DepthOfField
    //*********************
    DepthOfField depthOfField;

    //FocusDistance
    [SerializeField, Range(0, 10)]
    public float depthOfFieldFocusDistance;
    float depthOfFieldFocusDistanceStored;

    //*********************
    // Grain
    //*********************
    Grain grain;

    //*********************
    // LensDistortion
    //*********************
    LensDistortion lensDistortion;

    //*********************
    // MotionBlur
    //*********************
    MotionBlur motionBlur;

    //*********************
    // ScreenSpaceReflections
    //*********************
    ScreenSpaceReflections screenSpaceReflections;

    //*********************
    // Vignette
    //*********************
    Vignette vignette;


    //Intensity
    [SerializeField, Range(0, 1)]
    public float vignetteIntensity;
    float vignetteIntensityStored;




// void OnValidate()は「インスペクター上で値が変化した時に処理する関数」です。
// PostProsessingProfileを選択した時に、その要素を取得するのに使っています。
// (BloomやDepth of Field, Vignetteなど、計11種類の要素を取得している。実際はこの3種類しか使ってない。他は拡張したい時用)
// また、PostProsessingProfileを外した時に、取得した要素を破棄するようにもしています。
// (破棄しないと、インスペクター上にはPostProsessingProfileがないのにパラメータ変更ができてしまう。)


    void OnValidate()
    {
        if (postProcessProfile == null&& postProcessProfileStored == null)
            return;
        if (postProcessProfileStored == postProcessProfile)
            return;
        if (postProcessProfile == null&& postProcessProfileStored != null)
        {
            ambientOcclusion = null;
            autoExposure = null;
            bloom = null;
            chromaticAberration = null;
            colorGrading = null;
            depthOfField = null;
            grain = null;
            lensDistortion = null;
            motionBlur = null;
            screenSpaceReflections = null;
            vignette = null;
            return;
        }
        // postProcessProfile.settings から要素を探して参照する
        foreach (PostProcessEffectSettings item in postProcessProfile.settings)
        {
            if (item as AmbientOcclusion)
            {
                ambientOcclusion = item as AmbientOcclusion;
            }
            if (item as AutoExposure)
            {
                autoExposure = item as AutoExposure;
            }
            if (item as Bloom)
            {
                bloom = item as Bloom;
            }
            if (item as ChromaticAberration)
            {
                chromaticAberration = item as ChromaticAberration;
            }
            if (item as ColorGrading)
            {
                colorGrading = item as ColorGrading;
            }
            if (item as DepthOfField)
            {
                depthOfField = item as DepthOfField;
            }
            if (item as Grain)
            {
                grain = item as Grain;
            }
            if (item as LensDistortion)
            {
                lensDistortion = item as LensDistortion;
            }
            if (item as MotionBlur)
            {
                motionBlur = item as MotionBlur;
            }
            if (item as ScreenSpaceReflections)
            {
                screenSpaceReflections = item as ScreenSpaceReflections;
            }
            if (item as Vignette)
            {
                vignette = item as Vignette;
            }
        }
    }

    /// <summary>
    /// Updateではこのスクリプトのprivate変数の値[C]をComp〇〇Paramメソッドで変更し、
    /// 最後にPostProcessVolumeの変数の値[A]、このスクリプトのpublic変数の値[B]を[C]に統一する。
    ///           
    /// </summary>
    void Update()
    {
        if (ambientOcclusion) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (autoExposure) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (bloom) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
            bloomIntensityStored = CompFloatParam(bloom.intensity.value, bloomIntensity, bloomIntensityStored);
        }
        if (chromaticAberration) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (colorGrading) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (depthOfField) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
            depthOfFieldFocusDistanceStored = CompFloatParam(depthOfField.focusDistance.value, depthOfFieldFocusDistance, depthOfFieldFocusDistanceStored);
        }
        if (grain) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (lensDistortion) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (motionBlur) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (screenSpaceReflections) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
        }
        if (vignette) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
            vignetteIntensityStored = CompFloatParam(vignette.intensity.value, vignetteIntensity, vignetteIntensityStored);
        }
        ApplyParams();
        postProcessProfileStored = postProcessProfile;
    }

    /// <summary>
    /// PostProcessVolumeの変数の値[A]、このスクリプトのpublic変数の値[B]、このスクリプトのprivate変数の値[C]を比べて、正しい値に統一する。具体的には、
    /// 
    /// スクリプトアタッチ時 (A ≠ B = C)                                    ： 既に設定してあるPostProcessVolumeの値に合わせたいので[A]を優先
    /// マウスでPostProcessVolumeの値を変えたとき (A ≠ B = C)               ： 変更したPostProcessVolumeの値に合わせたいので[A]を優先
    /// マウスやTimelineでこのスクリプトのpublic値を変えたとき (B ≠ A = C)  : 変更したpublic値に合わせたいので[B]を優先
    ///           
    /// Cという保存用のprivate変数があることで、AとBどちらが変化したのかを判別できている。
    /// </summary>

    bool CompBoolParam(bool A, bool B, bool C) 
    {
        if (A != C)
        {
            return A;
        }
        else if (B != C)
        {
            return B;
        }
        else
        {
            return C;
        }
    }
    float CompFloatParam(float A, float B, float C) 
    {
        if (A != C)
        {
            return A;
        }
        else if (B != C)
        {
            return B;
        }
        else
        {
            return C;
        }
    }
    Vector3 CompVectorParam(Vector3 A, Vector3 B, Vector3 C) 
    {
        if (A != C)
        {
            return A;
        }
        else if (B != C)
        {
            return B;
        }
        else
        {
            return C;
        }
    }
    void ApplyParams() // 全ての値をprivate変数と同じにする
    {
        if (ambientOcclusion) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {

            // AmbientOcclusion

        }
        if (autoExposure) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {

            // AutoExposure

        }
        if (bloom) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
            // Bloom

            bloomIntensity = bloomIntensityStored;
            bloom.intensity.value = bloomIntensityStored;

        }
        if (chromaticAberration) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {

            // ChromaticAberration

        }
        if (colorGrading) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
            // ColorGrading

        }
        if (depthOfField) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
            // DepthOfField

            depthOfFieldFocusDistance = depthOfFieldFocusDistanceStored;
            depthOfField.focusDistance.value = depthOfFieldFocusDistanceStored;

        }
        if (grain) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {

            // Grain

        }
        if (lensDistortion) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {

            // LensDistortion

        }
        if (motionBlur) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {

            // MotionBlur

        }
        if (screenSpaceReflections) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {

            // ScreenSpaceReflections

        }
        if (vignette) // 該当するパラメータがPostProcessVolumeに存在する場合のみ処理をする
        {
            // Vignette

            vignetteIntensity = vignetteIntensityStored;
            vignette.intensity.value = vignetteIntensityStored;
        }

    }

}
```

####[使い方]
PostProcessingProfileを指定します。[画像]↓↓
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478685/e67585a6-7a29-ad45-21ec-425ae6ad2f0e.png)

スクリプト内のパラメータは指定したPostProcessingProfileと同期します(Editor上でも同期します)
TimelineでAnimationTrackを使ってScriptのパラメータをいじれば間接的にPostProcessProfileを操作できます。

####[スクリプト解説]
ほぼスクリプト内に書いてあります。スクリプトのパラメータとPostProsessProfileのパラメータを毎フレーム同じにします。
スクリプトを変更しても、PostProsessProfileを変更してもどちらでも大丈夫になるように、〇〇Storedと書いてある変数を使って変化を監視しています。毎フレームパラメータの変化がないかを監視し、変化があった場合に「スクリプト側をいじったのか」「PostProsessProfile側をいじったのか」を判定し、どちらかに変数を合わせます。

####[おわりに]
他のパラメータも動的に変更したい場合は、このスクリプトを改造してください。ぜひ、TimelineとPostProcessingStack V2を使いこなして下さいねっ🌟

あと、これらを使いこなしたミュージックビデオ作ったので見てください✨
####[脱法ロック/歌ってみた 八ツ橋まろん]
https://www.youtube.com/watch?v=m2iq4w1YXSo


それではまたねっ💕


八ツ橋まろん

Twitter
https://twitter.com/Maron_Vtuber
YouTube
https://www.youtube.com/channel/UCiIbLpncjzahHsp8cokG56g