こんにちは。八ツ橋まろんです💡
UnityのTimeline と PostProcessingStack V2を上手く使うための補助スクリプトの話です
###・Unity の Timeline
UnityのTimeline使ってますか?私はめっちゃ使ってます✨
Unityを使ってミュージックビデオを作っているのですが、Timelineを使うとカメラワークが簡単に作れるし、アニメーションとカメラワークのタイミングを合わせるのがすごく楽になります✨
「脱法ロック/歌ってみた (八ツ橋まろん)」
https://www.youtube.com/watch?v=m2iq4w1YXSo
[GIF] ↓↓
GameObjectにAnimatorを付けて録画ボタンを押すだけで、ComponentのパラメータをTimeline上でいじるとキーフレームが打たれて自由に制御できるのはとっても便利です。[GIF]↓↓
###・Unity の Post Processing Stack V2
また、エモい絵を作るためにPostProcessingStack V2は必須です。有り/無し で見た目がこんなに変わります。[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]↓↓
[B] PPVのパラメータと連動するスクリプトを作ってスクリプトのパラメータをTimelineでいじることで間接的にPPVもいじれるようにする
こんな感じになります。PPVのVignetteのIntensityとスクリプトのパラメータが連動していますね。Timelineでスクリプト側のパラメータを制御→間接的にPPVも制御という風になっています。[GIF]↓↓
ということで、今回は[B]の、**「TimelineでPPVのパラメータをいじれるようにする補助スクリプトを作る」**という記事です。
早速ですが、完成したスクリプトがこちら。
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を指定します。[画像]↓↓
スクリプト内のパラメータは指定した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