Skip to content
This repository has been archived by the owner on Nov 30, 2020. It is now read-only.

Manipulating the Stack

Thomas edited this page Jun 14, 2018 · 4 revisions

Quick Volumes

While working on a game you'll often need to push effect overrides on the stack for time-based events or temporary states. You could dynamically create a global volume on the scene, create a profile, create a few overrides, put them into the profile and assign the profile to the volume but that's not very practical.

We provide a QuickVolume method to quickly spawn new volumes in the scene:

public PostProcessVolume QuickVolume(int layer, float priority, params PostProcessEffectSettings[] settings)

First two parameters are self-explanatory. The last parameter takes an array or a list of effects you want to override in this volume.

Instancing a new effect is fairly straightforward. For instance, to create a Vignette effect and override its enabled & intensity fields:

var vignette = ScriptableObject.CreateInstance<Vignette>();
vignette.enabled.Override(true);
vignette.intensity.Override(1f);

Now let's look at a slightly more complex effect. We want to create a pulsating vignette effect entirely from script:

using UnityEngine;
using UnityEngine.Rendering.PostProcessing;

public class VignettePulse : MonoBehaviour
{
    PostProcessVolume m_Volume;
    Vignette m_Vignette;

    void Start()
    {
        m_Vignette = ScriptableObject.CreateInstance<Vignette>();
        m_Vignette.enabled.Override(true);
        m_Vignette.intensity.Override(1f);

        m_Volume = PostProcessManager.instance.QuickVolume(gameObject.layer, 100f, m_Vignette);
    }

    void Update()
    {
        m_Vignette.intensity.value = Mathf.Sin(Time.realtimeSinceStartup);
    }

    void OnDestroy()
    {
        RuntimeUtilities.DestroyVolume(m_Volume, true, true);
    }
}

This code creates a new vignette and assign it to a newly spawned volume with a priority of 100. Then, on every frame, it changes the vignette intensity using a sinus curve.

Important: Don't forget to destroy the volume and the attached profile when you don't need them anymore!

Fading Volumes

Distance-based volume blending is great for most level design use-cases, but once in a while you'll want to trigger a fade in and/or out effect based on a gameplay event. You could do it manually in an Update method as described in the previous section or you could use a tweening library to do all the hard work for you. A few of these are available for Unity for free, like DOTween, iTween or LeanTween.

Let's use DOTween for this example. We won't go into details about it (it already has a good documentation) but this should get you started:

using UnityEngine;
using UnityEngine.Rendering.PostProcessing;
using DG.Tweening;

public class VignettePulse : MonoBehaviour
{
    void Start()
    {
        var vignette = ScriptableObject.CreateInstance<Vignette>();
        vignette.enabled.Override(true);
        vignette.intensity.Override(1f);

        var volume = PostProcessManager.instance.QuickVolume(gameObject.layer, 100f, vignette);
        volume.weight = 0f;

        DOTween.Sequence()
            .Append(DOTween.To(() => volume.weight, x => volume.weight = x, 1f, 1f))
            .AppendInterval(1f)
            .Append(DOTween.To(() => volume.weight, x => volume.weight = x, 0f, 1f))
            .OnComplete(() =>
            {
                RuntimeUtilities.DestroyVolume(volume, true, true);
                Destroy(this);
            });
    }
}

In this example, like the previous one, we spawn a quick volume with a vignette. We set its weightproperty to 0 as we don't want it to have any contribution just yet.

Then we use the sequencing feature of DOTween to chain a set of tweening events: fade in, pause for a second, fade out and finally destroy the volume and the component itself once it's done.

And that's it. Of course you can also tween individual effect properties instead of the volume as a whole, it's up to you.

Profile Editing

You can also manually edit an existing profile on a volume. It's very similar to how material scripting works in Unity. There are two ways of doing that: either by modifying the shared profile directly or by requesting a clone of the shared profile that will only be used for this volume.

Each method comes with a a few advantages and downsides:

  • Shared profile editing:
    • Changes will be applied to all volumes using the same profile
    • Modifies the actual asset and won't be reset when you exit play mode
    • Field name: sharedProfile
  • Owned profile editing:
    • Changes will only be applied to the specified volume
    • Resets when you exit play mode
    • It is your responsibility to destroy the profile when you don't need it anymore
    • Field name: profile

The PostProcessProfile class has a few utility methods to help you manage assigned effects. Notable ones include:

  • T AddSettings<T>(): creates, adds and returns a new effect or type T to the profile. Will throw an exception if it already exists.
  • PostProcessEffectSettings AddSettings(PostProcessEffectSettings effect): adds and returns an effect you created yourself to the profile.
  • void RemoveSettings<T>(): removes an effect from the profile. Will throw an exception if it doesn't exist.
  • bool TryGetSettings<T>(out T outSetting): gets an effect from the profile, returns true if one was found, false otherwise.

You'll find more methods by browsing the /PostProcessing/Runtime/PostProcessProfile.cs source file.

Important: Don't forget to destroy any manually created profiles or effects.

Additional notes

If you need to instantiate PostProcessLayer at runtime you'll need to make sure resources are properly bound to it. After the component has been added, don't forget to call Init() on it with a reference to the PostProcessResources file as a parameter.

var postProcessLayer = gameObject.AddComponent<PostProcessLayer>();
postProcessLayer.Init(resources);