Shockwave エフェクトを作る

ぽわ~んって感じの画面エフェクトを出したくてCamera filter packというアセットを買いました。

assetstore.unity.com

しかし、個人的に使いづらかったり、バグなのか使い方が悪かったのかアスペクト比が考慮されておらずエフェクトが細長くなってたりしたので自作しました。

ぽわ~んエフェクト
f:id:wkpn:20180316133548g:plain


まずはShader、

Shader "ShockWave"
{ 
    Properties 
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _TableTex ("DistortionTable", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            Cull Off ZWrite Off ZTest Always
            CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma target 3.0
#pragma glsl
#include "UnityCG.cginc"
            uniform sampler2D _MainTex;
            uniform sampler2D _TableTex;
            uniform float     _TimeOffset;
            uniform float     _PosX;
            uniform float     _PosY;
            uniform float     _ShineMag;
            uniform float     _DistortionMag;
            uniform float     _WidthRev;
            uniform float     _AspectRatio;
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            struct v2f
            {
                float2 texcoord  : TEXCOORD0;
                float4 vertex   : SV_POSITION;
                float4 color    : COLOR;
            };
            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.vertex = UnityObjectToClipPos(IN.vertex);
                OUT.texcoord = IN.texcoord;
                OUT.color = IN.color;
                return OUT;
            }
            half4 _MainTex_ST;
            float4 frag(v2f i) : COLOR
            {
                float2 org_uv = UnityStereoScreenSpaceUVAdjust(i.texcoord, _MainTex_ST);
                float2 adjusted_uv = org_uv.xy;//距離を算出するため、アスペクト比を考慮したuv
                float2 wave_center = float2( _PosX, _PosY );

                //アスペクト比の考慮
                adjusted_uv.x *= _AspectRatio;
                wave_center.x *= _AspectRatio;

                float2 distortion_dir = adjusted_uv - wave_center;//歪み方向。より外側のテクスチャをpickする
                float dist = distance(adjusted_uv, wave_center);
                distortion_dir /= dist;//正規化
                float offset = dist * _WidthRev - _TimeOffset;

                float distortion = tex2D(_TableTex, float2( offset, 0.5 ) ).r;
                org_uv += distortion_dir * distortion * _DistortionMag;//歪ませる
                float4 color = tex2D(_MainTex, org_uv.xy) + _ShineMag * float4( distortion, distortion, distortion, 0.0 );//歪んだ元画像に光をプラス

                return color; 
            }
            ENDCG
        }
    }
}

指定した位置からの距離に応じて、画面を歪ませつつちょっと色を飛ばしています。
距離に応じてどの程度歪ませるかは _TableTex のテクスチャで指定します。

私は、こんな感じのテクスチャを使いました。
f:id:wkpn:20180316132823p:plain

次にC#スクリプト

using UnityEngine;
using System.Collections;
public class ShockWaveEffect : MonoBehaviour {
    [SerializeField]
    [Range(0.0f, 1.0f)]
    private float timer = 0.5f;

    [Range(-1.5f, 1.5f)]
    public float posX = 0.5f;

    [Range(-1.5f, 1.5f)]
    public float posY = 0.5f;

    [Range(0f, 10f)]
    public float duration = 1f;

    [SerializeField]
    [Range(0f, 2f)]
    protected float shine = 0.5f;

    [SerializeField]
    [Range(0f, 1f)]
    protected float distortion = 0.1f;

    [SerializeField]
    [Range(0f, 10f)]
    protected float width = 0.5f;

    [SerializeField]
    protected Material material;

    void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
    {
        material.SetFloat("_TimeOffset", ( 4.0f * timer - 2.0f ) ); // -2 〜 2 まで動けば画面端で切れることもなかろう
        material.SetFloat("_PosX", posX);
        material.SetFloat("_PosY", posY);
        material.SetFloat("_ShineMag", shine);
        material.SetFloat("_DistortionMag", distortion);
        material.SetFloat("_WidthRev", 1.0f / width);
        material.SetFloat("_AspectRatio", sourceTexture.width/(float)sourceTexture.height );
        Graphics.Blit(sourceTexture, destTexture, material);
    }
    void Update ()
    {
        timer += Time.deltaTime * ( 1.0f / duration );
        if ( timer > 1.0f ){
            enabled=false;
        }
    }

    public void StartEffect(Vector3 world_pos)
    {
        var camera = GetComponent<Camera>();
        var screen_pos = camera.WorldToScreenPoint(world_pos);
        enabled=true;
        timer=0.0f;
        posX = ( screen_pos.x )/camera.pixelWidth;
        posY = ( screen_pos.y )/camera.pixelHeight;
    }

    [ContextMenu("startEffectTest")]
    private void startEffectTest()
    {
        StartEffect(Vector3.zero);
    }
}

使い方は、

  • c#スクリプトをカメラに引っ付ける
  • 先のシェーダーを元にMaterialを作る
  • MaterialのTableTexにテクスチャを当てる
  • カメラにひっつけたスクリプトにMaterialをassignする

あとは、StartEffect( ポジション )を呼べばぽわ~んっていうエフェクトが発生します。