LoginSignup
64
36

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-24

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

ただ、水面の高さをシェーダー側で処理しているため、水面のオブジェクトと同じ高さにオブジェクトを配置しただけでは、オブジェクトが水面に潜ったり浮き上がりすぎたりしてしまいます。
wave0.gif

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

まず、水面で使用されているシェーダー"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
  2. 1のオブジェクトのY座標を水面と同じにし、オブジェクトの子として水面に浮かせたいオブジェクトを配置します。
  3. 水面に合わせてオブジェクトが移動します。

wave1.gif


追記:
上記のほか、2つのSkyboxをフェードさせるシェーダー、Objectに喫水線を追加するシェーダーなどをまとめたアセットを作りました。よかったら・・・買ってくださいXD
WaterUtils
スクリーンショット 2017-03-18 21.07.41.png

64
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
36