LoginSignup
8
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-12

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

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

・Unity の Timeline

UnityのTimeline使ってますか?私はめっちゃ使ってます✨

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

「脱法ロック/歌ってみた (八ツ橋まろん)」
https://www.youtube.com/watch?v=m2iq4w1YXSo
[GIF] ↓↓
Qiita4.gif

GameObjectにAnimatorを付けて録画ボタンを押すだけで、ComponentのパラメータをTimeline上でいじるとキーフレームが打たれて自由に制御できるのはとっても便利です。[GIF]↓↓
Qiita3.gif

・Unity の Post Processing Stack V2

また、エモい絵を作るためにPostProcessingStack V2は必須です。有り/無し で見た目がこんなに変わります。[PNG]↓↓
図2.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

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

こんな感じになります。PPVのVignetteのIntensityとスクリプトのパラメータが連動していますね。Timelineでスクリプト側のパラメータを制御→間接的にPPVも制御という風になっています。[GIF]↓↓
Qiita6.gif

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

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

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

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

[スクリプト解説]

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

[おわりに]

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

あと、これらを使いこなしたミュージックビデオ作ったので見てください✨

[脱法ロック/歌ってみた 八ツ橋まろん]

それではまたねっ💕

八ツ橋まろん

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

8
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3