6
3

More than 3 years have passed since last update.

【Unity】Timelineを拡張してポストプロセスのカスタムトラックを作成する

Posted at

概要

Timelineでポストプロセス専用のカスタムトラックを作成する実装例をご紹介します。
TimelinePostProcessing.gif

環境

  • Unity2019.4.1f1
  • UniversalRP 7.3.1

Timelineでカスタムトラックを作成して連携

TimelineのクリップごとにProfileを指定します。
2020-09-13_19h59_15.png

ポストエフェクトのかかり具合はWeight Curveで調整するようにしました。
WeightCurveが1ならよくかかる、0ならかかりません。
2020-09-13_19h59_46.png

コード

4つのC#ファイルを作成します。

クラス 説明
PostProcessBehaviour.cs クリップごとの振る舞いを表します
PostProcessMixerBehaviour.cs トラック内で複数のクリップがある場合の振る舞いを表します。inputWeightが正のものが現在のクリップです
PostProcessTrack.cs トラックを表します
PostProcessClip.cs クリップを表します。クリップのパラメータが変更されるたびにCreatePlayableが呼ばれます。
PostProcessBehaviour.cs
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Rendering;
using System;

[Serializable]
public class PostProcessBehaviour : PlayableBehaviour
{
    [HideInInspector]
    public Volume volume;
    public VolumeProfile profile;
    public int layer;
    public AnimationCurve weightCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);

    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {
        if (profile != null)
        {
            QuickVolume();
        }
    }

    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        if (volume != null)
        {
            GameObject.DestroyImmediate(volume.gameObject);
        }
    }

    void QuickVolume()
    {
        if (volume == null)
        {
            volume = new GameObject().AddComponent<Volume>();
            volume.gameObject.layer = layer;
            volume.gameObject.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
            volume.gameObject.name = $"PostProcessBehaviourVolume [Profile {profile.name}]";
            volume.weight = 0;
            volume.priority = 1;
            volume.isGlobal = true;
            volume.profile = profile;
        }
    }

    public void ChangeWeight(float time)
    {
        if (volume == null) { return; }
        volume.weight = weightCurve.Evaluate(time);
    }
}
PostProcessMixerBehaviour.cs
using System;
using UnityEngine;
using UnityEngine.Playables;

[Serializable]
public class PostProcessMixerBehaviour : PlayableBehaviour
{
    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        int inputCount = playable.GetInputCount();
        for (int i = 0; i < inputCount; i++)
        {
            var playableInput = (ScriptPlayable<PostProcessBehaviour>)playable.GetInput(i);
            PostProcessBehaviour input = playableInput.GetBehaviour();
            float inputWeight = playable.GetInputWeight(i);
            if (Mathf.Approximately(inputWeight, 0f))
            {
                continue;
            }
            float normalizedTime = (float)(playableInput.GetTime() / playableInput.GetDuration());
            input.ChangeWeight(normalizedTime);
        }
    }
}
PostProcessTack.cs
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

[TrackColor(0.98f, 0.27f, 0.42f)]
[TrackClipType(typeof(PostProcessClip))]
public class PostProcessTrack : TrackAsset
{
    public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    {
        var scriptPlayable = ScriptPlayable<PostProcessMixerBehaviour>.Create(graph, inputCount);
        return scriptPlayable;
    }
}

PostProcessClip.cs
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif

[Serializable]
public class PostProcessClip : PlayableAsset, ITimelineClipAsset
{
    public PostProcessBehaviour template = new PostProcessBehaviour();

    public ClipCaps clipCaps
    {
        get { return ClipCaps.Extrapolation | ClipCaps.Blending; }
    }

    public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
    {
        var playable = ScriptPlayable<PostProcessBehaviour>.Create(graph, template);
        PostProcessBehaviour clone = playable.GetBehaviour();
        return playable;
    }
}

#if UNITY_EDITOR

[CustomEditor(typeof(PostProcessClip))]
public class PostProcessClipEditor : Editor
{
    PostProcessClip postProcessClip;
    Editor profileEditor;
    SerializedProperty profileProperty;
    SerializedProperty curveProperty;

    void OnEnable()
    {
        postProcessClip = target as PostProcessClip;
        profileEditor = Editor.CreateEditor(postProcessClip.template.profile);
        profileProperty = serializedObject.FindProperty("template.profile");
        curveProperty = serializedObject.FindProperty("template.weightCurve");
    }

    void OnDisable()
    {
        DestroyImmediate(profileEditor);
    }

    public override void OnInspectorGUI()
    {
        postProcessClip.template.layer = EditorGUILayout.LayerField("Layer", postProcessClip.template.layer);
        serializedObject.Update();
        EditorGUILayout.PropertyField(profileProperty);
        EditorGUILayout.PropertyField(curveProperty);
        serializedObject.ApplyModifiedProperties();

        profileEditor?.OnInspectorGUI();
    }
}
#endif

プロジェクトデータ

以前の記事と同じ場所にサンプルプロジェクトデータをGitHubアップしましたので
ご自由にお使いください。

参考サイト様

まとめ

  • 以前の記事ではコンポーネントを作ってアニメーターと連携して パラメータをいじってました。
  • 今回Timelineを拡張してポストプロセス用のカスタムトラックを作って設定できるようにしました。
6
3
0

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
6
3