Custom Image Effects in Unity

I have been playing with image effects for the first time in Unity and feeling a bit like a kid in a toy store, trying to hold myself back from throwing in all sorts of crazy effects. The first thing I’m using it for is to blur out and darken the background when a UI panel is being displayed. The blurring part is easy: it’s already one of Unity’s built-in effects. But there is nothing out of the box that darkens the screen. Given this is probably the simplest possible image effect you can do, it was a great candidate for learning:

First, I created the effect MonoBehaviour. Here is what it looks like:


[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class Darkener : MonoBehaviour
{
  public float Amount;
  Material _material;

  void Start()
  {
    _material = Resources.Load<Material>("Materials\\Darken");
  }

  void OnRenderImage(RenderTexture src, RenderTexture dest)
  {
    if (Amount < 0.0001f)
    {
      Graphics.Blit(src, dest);
      return;
    }

    _material.SetFloat("Amount", Amount);
    Graphics.Blit(src, dest, _material);
  }
}

The magic method is OnRenderImage which is called every frame and contains the render texture of the main camera (UI panels use different cameras), and a destination where we output the modified result. The Amount value allows me to change the amount of darkness which I use to fade-in/fade-out the darkness when a UI panel opens. If it’s 0 (or close to it) we simply write the original data to the destination render texture. Otherwise we apply the Darken material. Here is how the shader for this material works:


Shader "Custom/Darken"
{
  Properties
  {
    _MainTex("Base (RGB)", 2D) = "white" {}
    Amount("Amount", Range(0, 1)) = 0
  }

  SubShader
  {
    Pass
    {
    CGPROGRAM

    #pragma vertex vert_img
    #pragma fragment frag

    #include "UnityCG.cginc"

    uniform sampler2D _MainTex;
    uniform float Amount;

    float4 frag(v2f_img i) : COLOR
    {
      float4 c = tex2D(_MainTex, i.uv);
      c = c * (1 - Amount);

      return c;
    }
    ENDCG
    }
  }
}

 

The shader gets the render texture (_MainTex) and simply makes it darker based on the Amount property we set earlier. Nothing fancy.

Now all we need is a Coroutine somewhere to use this effect to lighten/darken the image over several frames. I actually put these on a separate MonoBehaviour:


IEnumerator DoDarken()
{
  Darkener.enabled = true;

  while (Darkener.Amount < 0.3f)
  {
    var darkenAmount = 0.3f*Time.deltaTime;
    Darkener.Amount = Mathf.Min(0.3f, Darkener.Amount + darkenAmount);
    yield return null;
  }
}

IEnumerator DoLighten()
{
  Darkener.enabled = true;

  while (Darkener.Amount > 0)
  {
    var lightenAmount = 0.3f*_gameTime.DeltaTime;
    Darkener.Amount = Mathf.Max(0, Darkener.Amount - lightenAmount);
    yield return null;
  }

  Darkener.enabled = false;
}

I actually also apply the blur image effect so we end up with something like this:

darken-background

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s