【Unity】シェーダーで波紋エフェクト

# デモ
板
https://www.youtube.com/watch?v=DbGPtjECUaI
ユニティちゃん版
https://www.youtube.com/watch?v=vFsKSkm3w-A

# 概要
波動方程式を利用した古典的な波紋シミュレーションです。
変位をテクスチャに書き込み、シェーダーを用いてそれを更新していきます。
それっぽく見えればよいのでけっこう適当にやっています。
サンプルコードを添付していますがシーンの構築はセルフサービスでお願いします。

# 実装

### シェーダー
frag 関数がキモで他はおまじないのようなものです。
波動方程式を差分に置き換えたり変数を簡略化しています。
伝播速度を原理に沿わない方法で反映させていますが、
見た目的にだいたい大丈夫そうだったのでこれでよしとしています（大きな値や１未満の値を入れると破綻すると思われます）。

```glsl
Shader "Custom/WaveUpdate2" {
Properties {
 _prev_1 ("", 2D) = "white" {}
 _prev_2 ("", 2D) = "white" {}
 _draw ("", 2D) = "black" {}
 _width ("", Float) = 1024
 _height ("", Float) = 1024
 _speed("", Float) = 10
}
 
SubShader {
 
ZTest Always
Cull Off
ZWrite Off
Fog { Mode Off } //Rendering settings
 
 Pass{
  CGPROGRAM
  #pragma vertex vert
  #pragma fragment frag
  #include "UnityCG.cginc" 
  //we include "UnityCG.cginc" to use the appdata_img struct
    
  struct v2f {
   float4 pos : POSITION;
   half2 uv : TEXCOORD0;
  };
   
  //Our Vertex Shader 
  v2f vert (appdata_img v){
   v2f o;
   o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
   o.uv = MultiplyUV (UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
   return o; 
  }
    
  sampler2D _prev_1; //Reference in Pass is necessary to let us use this variable in shaders
  sampler2D _prev_2;
  sampler2D _draw;
  
  fixed _width;
  fixed _height;
  fixed _speed;
    
  //Our Fragment Shader
  fixed4 frag (v2f i) : COLOR{

   fixed strideX = _speed/_width;
   fixed strideY = _speed/_height;

   fixed4 retval = tex2D(_prev_1, i.uv)*2 - tex2D(_prev_2, i.uv)
	+ (
	  tex2D(_prev_1, half2(i.uv.x+strideX, i.uv.y))
	  +tex2D(_prev_1, half2(i.uv.x-strideX, i.uv.y))
	  +tex2D(_prev_1, half2(i.uv.x, i.uv.y+strideY))
	  +tex2D(_prev_1, half2(i.uv.x, i.uv.y-strideY))
	  -tex2D(_prev_1, i.uv)*4
	) * 0.5;
     
	fixed4 halfvec = fixed4(0.5,0.5,0.5,0);
	retval = halfvec + (retval - halfvec)*0.985;
	retval += tex2D(_draw, i.uv);

   fixed4 col = retval;// fixed4(avg, avg, avg, 1);
     
   return col;
  }
  ENDCG
 }
} 
 FallBack "Diffuse"
}
```

### コントロール用コンポーネント
計算には過去２ステップの履歴を必要とします。
出力先と合わせて３枚のRenderTextureを用います。
これらをループさせて使用することで波の状態を順次アップデートします。

myMaterialに上のシェーダーを用いたマテリアル、
targetRendererには出来上がったテクスチャを適用する対象を突っ込みます。

inputTex には波を外力で動かすためのテクスチャを入れます。
何かしら変化するタイプのテクスチャを入れるのがいいと思います（変化しないとつまらないので）。
黒い背景に白いオブジェクトを描画したシーンキャプチャを用いるのが分かりやすいです。
アプリケーションに組み込む際は、これの作り方が工夫のしどころです。

```glsl
using UnityEngine;
using System.Collections;

public class WaveControl : MonoBehaviour {

    RenderTexture[] _waveBuf = new RenderTexture[3];
    public Material myMaterial;
    int _currentTargetIdx = 0;
    public Renderer targetRenderer;
    public Texture inputTex;


	// Use this for initialization
	void Start () {
        myMaterial = new Material(myMaterial);
        for (int i = 0; i < 3; ++i)
        {
            _waveBuf[i] = new RenderTexture(1024, 1024, 24);
            _waveBuf[i].wrapMode = TextureWrapMode.Clamp;
            _waveBuf[i].Create();
        }
        myMaterial.SetTexture("_draw", inputTex);
	}
	
	// Update is called once per frame
	void Update () 
    {
        int prevIdx1 = (_currentTargetIdx - 1 + 3) % 3;
        int prevIdx2 = (_currentTargetIdx - 2 + 3) % 3;
        myMaterial.SetTexture("_prev_1", _waveBuf[prevIdx1]);
        myMaterial.SetTexture("_prev_2", _waveBuf[prevIdx2]);

        Graphics.Blit(_waveBuf[prevIdx1], _waveBuf[_currentTargetIdx], myMaterial);

         targetRenderer.material.mainTexture = _waveBuf[_currentTargetIdx];

        _currentTargetIdx = (_currentTargetIdx + 1) % 3;
	}
}

```

以上です。