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

  • 40
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

デモ


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

概要

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

実装

シェーダー

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

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"
}

コントロール用コンポーネント

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

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

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

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;
    }
}

以上です。