[PostEffect]ワイプエフェクトのImageEffect


環境

  • Unity5.5
  • DOTween

(これワイプエフェクトっていうんだろうか・・??)

Shader側

Shader側のPropertyには

  • 円の中心となるスクリーン座標 : (_HoleCenterX, _HoleCenterY)
  • 円の半径 : _Radius

を定義します。

fragmentShaderの中で指定された円の中心とピクセルとの距離を計算します。
距離が_Radius未満の場合はそのままの色のままとし、_Radisu以上の場合は黒く塗りつぶします。

WipeEffect.shader
Shader "Custom/WipeEffect"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _HoleCenterX ("HoleCenterX", float) = 0.5
        _HoleCenterY ("HoleCenterY", float) = 0.5
        _Radius ("Radius", float) = 0.5
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            float _HoleCenterX;
            float _HoleCenterY;
            float _Radius;

            fixed4 frag (v2f i) : SV_Target
            {
                float2 pos = i.uv * _ScreenParams.xy;

                if( distance(pos.xy, fixed2(_HoleCenterX, _HoleCenterY)) < _Radius ){
                    discard;
                }
                return fixed4(0, 0, 0, 1);
            }
            ENDCG
        }
    }
}

 Script側

Script側では円の中心となるスクリーン座標をshader側に渡しています。
今回は指定されたGameObjectを中心にワイプエフェクトをかけたいので、WorldToScreenPoint"関数でmyCameraから見たGameObjectのスクリーン座標を取得してshader側に渡しています。

CircleEffect.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class CircleEffect : MonoBehaviour
{
    [SerializeField]
    private Camera myCamera;
    private Material material;

    /// <summary>
    /// シーン上の覗き穴Effect対象のGameObjectの配列
    /// </summary>
    [SerializeField]
    private GameObject[] cubeArray;

    /// <summary>
    /// 処理中フラグ
    /// </summary>
    private bool isProcess;

    /// <summary>
    /// 最終的な半径の大きさ
    /// </summary>
    [SerializeField, Range (0.1f, 1.0f)]
    private float destinationRadius;

    /// <summary>
    /// 半径の一時変数
    /// </summary>
    private float _radius;

    GameObject currentTarget;
    int index = 0;

    void Start ()
    {
        material = new Material (Shader.Find ("Custom/CircleFadeOut"));
    }

    void OnRenderImage (RenderTexture source, RenderTexture destination)
    {
        if (isProcess) {
            UpdateMaterial ();
            Graphics.Blit (source, destination, material);
        }
    }

    void UpdateMaterial ()
    {
        Vector3 screenPoint = myCamera.WorldToScreenPoint (currentTarget.transform.position);

        float currentRadius = Screen.height * _radius;
        material.SetFloat ("_HoleCenterX", screenPoint.x);
        material.SetFloat ("_HoleCenterY", screenPoint.y);
        material.SetFloat ("_Radius", currentRadius);
    }

    /// <summary>
    /// 指定されたGameObjectのスクリーン上の座標の周りを黒塗りにする
    /// </summary>
    /// <param name="target">Target.</param>
    void StartTargetWipeEffect (GameObject target)
    {
        isProcess = true;
        _radius = 2.0f;
        currentTarget = target;
        DOTween.To (r => _radius = r, _radius, destinationRadius, 1.0f)
            .OnComplete (() => {
                Debug.Log("WipeEffect Process End!");
        });
    }

    void Update ()
    {
        if (Input.GetKeyDown (KeyCode.A)) {
            StartTargetWipeEffect (cubeArray[index%cubeArray.Length]);
            index++;
        }
    }
}