水面シェーダーの水面にオブジェクトを浮かせる

Unityの
StandardAssets>Environment>Water
には、水面が波打つ素敵なシェーダーがあります。

ただ、水面の高さをシェーダー側で処理しているため、水面のオブジェクトと同じ高さにオブジェクトを配置しただけでは、オブジェクトが水面に潜ったり浮き上がりすぎたりしてしまいます。
![wave0.gif](https://qiita-image-store.s3.amazonaws.com/0/59911/2c9bcf54-0ec8-6e2f-174d-a2033d1dc246.gif)

そこで、オブジェクトも水面と同じ計算をすることで水面に浮かせるようにしてみます。

まず、水面で使用されているシェーダー"FX/SimpleWater4"を見てみます。

```FX/SimpleWater4
    :
		Gerstner (
			offsets, nrml, v.vertex.xyz, vtxForAni,						// offsets, nrml will be written
			_GAmplitude,												// amplitude
			_GFrequency,												// frequency
			_GSteepness,												// steepness
			_GSpeed,													// speed
			_GDirectionAB,												// direction # 1, 2
			_GDirectionCD												// direction # 3, 4
		);
    :

```
ここで頂点オフセット、法線を変更しているようです。
この関数は
`#include "WaterInclude.cginc"`
からincludeされているようなので、そちらをみてみます。

```
	half3 GerstnerOffset4 (half2 xzVtx, half4 steepness, half4 amp, half4 freq, half4 speed, half4 dirAB, half4 dirCD) 
	{
		half3 offsets;
		
		half4 AB = steepness.xxyy * amp.xxyy * dirAB.xyzw;
		half4 CD = steepness.zzww * amp.zzww * dirCD.xyzw;
		
		half4 dotABCD = freq.xyzw * half4(dot(dirAB.xy, xzVtx), dot(dirAB.zw, xzVtx), dot(dirCD.xy, xzVtx), dot(dirCD.zw, xzVtx));
		half4 TIME = _Time.yyyy * speed;
		
		half4 COS = cos (dotABCD + TIME);
		half4 SIN = sin (dotABCD + TIME);
		
		offsets.x = dot(COS, half4(AB.xz, CD.xz));
		offsets.z = dot(COS, half4(AB.yw, CD.yw));
		offsets.y = dot(SIN, amp);

		return offsets;			
	}	
```

頂点オフセットの計算はこのようになっているので、これをc#に置き換えてみます。

```waterHeight.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class waterHeight : MonoBehaviour {
	[SerializeField]
	MeshRenderer targetMr;
	Material mat;
	// Time (t = time since current level load) values from Unity
	// float4 _Time; // (t/20, t, t*2, t*3)

	// Use this for initialization
	void Start () {
		mat = targetMr.sharedMaterial;
	}
	
	// Update is called once per frame
	void Update () {

		Vector2 xzVtx = new Vector2 (transform.position.x, transform.position.z);
		Vector4 steepness = mat.GetVector ("_GSteepness");
		Vector4 amp = mat.GetVector ("_GAmplitude");
		Vector4 freq = mat.GetVector ("_GFrequency");
		Vector4 speed = mat.GetVector ("_GSpeed");
		Vector4 dirAB = mat.GetVector ("_GDirectionAB");
		Vector4 dirCD = mat.GetVector ("_GDirectionCD");
		Vector3 ofs = GerstnerOffset4(xzVtx, steepness, amp, freq, speed, dirAB, dirCD);
		transform.GetChild (0).localPosition = ofs*0.5f;
	}

	Vector3 GerstnerOffset4 (Vector2 xzVtx, Vector4 steepness, Vector4 amp, Vector4 freq, Vector4 speed, Vector4 dirAB, Vector4 dirCD) 
	{
		float t = Time.timeSinceLevelLoad;
		Vector4 _Time = new Vector4(t/20, t, t*2, t*3);
		Vector3 offsets;

		Vector4 AB = Vector4.Scale(Vector4.Scale(xxyy(steepness) , xxyy(amp)) , dirAB); //steepness.xxyy * amp.xxyy * dirAB.xyzw;
		Vector4 CD = Vector4.Scale(Vector4.Scale(zzww(steepness) , zzww(amp)) , dirCD); //steepness.zzww * amp.zzww * dirCD.xyzw;

		Vector4 dotABCD = Vector4.Scale(freq , new Vector4 (
			Vector2.Dot(new Vector2(dirAB.x,dirAB.y),xzVtx),
			Vector2.Dot(new Vector2(dirAB.z,dirAB.w),xzVtx),
			Vector2.Dot(new Vector2(dirCD.x,dirCD.y),xzVtx),
			Vector2.Dot(new Vector2(dirCD.z,dirCD.w),xzVtx)
		)); //freq.xyzw * half4(dot(dirAB.xy, xzVtx), dot(dirAB.zw, xzVtx), dot(dirCD.xy, xzVtx), dot(dirCD.zw, xzVtx));
		Vector4 TIME = Vector4.Scale(Vector4.one * _Time.y , speed); //_Time.yyyy * speed;

		Vector4 COS = new Vector4(
			Mathf.Cos (dotABCD.x + TIME.x),
			Mathf.Cos (dotABCD.y + TIME.y),
			Mathf.Cos (dotABCD.z + TIME.z),
			Mathf.Cos (dotABCD.w + TIME.w)
		); //cos (dotABCD + TIME);
		Vector4 SIN = new Vector4(
			Mathf.Sin (dotABCD.x + TIME.x),
			Mathf.Sin (dotABCD.y + TIME.y),
			Mathf.Sin (dotABCD.z + TIME.z),
			Mathf.Sin (dotABCD.w + TIME.w)
		); //sin (dotABCD + TIME);

		offsets.x = Vector4.Dot(COS, new Vector4(AB.x,AB.z, CD.x,CD.z)); // dot(COS, Vector4(AB.xz, CD.xz));
		offsets.z = Vector4.Dot(COS, new Vector4(AB.y,AB.w, CD.y,CD.w)); // dot(COS, Vector4(AB.yw, CD.yw));
		offsets.y = Vector4.Dot(SIN, amp); // dot(SIN, amp);

		return offsets;			
	}

	Vector4 xxyy(Vector4 _in){ return new Vector4 (_in.x, _in.x, _in.y, _in.y); }
	Vector4 zzww(Vector4 _in){ return new Vector4 (_in.z, _in.z, _in.w, _in.w); }
}
```

1. 上記スクリプトを空のオブジェクトにアタッチし、targetMrに水面のオブジェクト（Tile）を指定します。
![waveSettings.png](https://qiita-image-store.s3.amazonaws.com/0/59911/c9306a79-4a88-01e6-1a59-1b1153c4c0af.png)
2. 1のオブジェクトのY座標を水面と同じにし、オブジェクトの子として水面に浮かせたいオブジェクトを配置します。
3. 水面に合わせてオブジェクトが移動します。

![wave1.gif](https://qiita-image-store.s3.amazonaws.com/0/59911/4229b5c6-2578-ca85-4959-bf96f17f0299.gif)

---
追記：
上記のほか、2つのSkyboxをフェードさせるシェーダー、Objectに喫水線を追加するシェーダーなどをまとめたアセットを作りました。よかったら・・・買ってくださいXD
[WaterUtils](https://www.assetstore.unity3d.com/jp/#!/content/83674)
![スクリーンショット 2017-03-18 21.07.41.png](https://qiita-image-store.s3.amazonaws.com/0/59911/135c8a0f-f3d9-f9af-9101-df130809f899.png)