Unity Gerstner Waves(模擬大海波浪)

之前學習了一些Gerstner Waves的知識,看了一些教程,但發現說的略不夠通俗,爲了便於自己理解,我自己來寫一個給自己的教程吧。

該教程會有三個步驟,一闡述原理,二給出源碼和源碼解釋,三提供demo下載。這樣一來就算文字看不懂,

也可以下載demo,運行demo,結合源碼一起看,總有一種方式能讓你看懂(^_^)。

原理解釋

參考:GPU Gems的官方文檔

https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch01.html

以下是它的原理的翻譯:

Gerstner波函數最初是在計算機圖形學很久之前就開發出來的,用於物理模擬海水。

我們選擇Gerstner Waves這了因爲他們有一種容易被忽視的(  often-overlooked )特質:

他們通過向每個波峯移動頂點,形成更清晰、尖銳(sharper)的波峯。

我的理解就是:x、z座標向波峯靠攏,y軸座標做sin波計算。所以看起來波峯更尖銳,波谷更廣闊(因爲頂點向波峯靠攏了,

波谷的頂點距離變更大了,看起來就更寬闊。)

方程式爲:

溫馨提示:不要被公式嚇到了,看似複雜,但它僅僅是個公式而已,我們不一定要完全理解它,我們只需要知道每個參數是幹啥的即可。然後代入到我們代碼裏運行即可。最終都只是加減乘除等基礎的運算。

方程式參數解釋:

其中Qi是控制波浪陡度的參數,i是第i個波浪(也可以只有一個波浪,更好理解)。

如果Qi是0,則是一般的正弦波。

當Qi = 1/(wi*Ai)時,波峯最尖銳。

其他參數解釋請看下面:

爲了便於理解,我們必須要結合正弦波的圖來看,因爲Gerstner Waves也是正弦波的基礎上演變過來的

下圖中的參數和Gerstner Waves方程式裏的參數是 一致的。

正弦波:

正弦波方程式:

L(WaveLength)是世界座標上波峯與波峯之間的距離。

參數wi是頻率,是一個常數,wi = 2 π /L(GUP Gems網頁版有錯誤,寫少了個π)。根據百度百科,頻率的意思是單位時間中完成週期性變化的次數。

根據正弦方程的標準函數(也叫正弦型函數)f(x)=Asin(wx+β),最小正週期L就是L=2π/w。所以 w = 2π/L。

具體可見百度百科正弦型函數的解釋:

https://baike.baidu.com/item/%E6%AD%A3%E5%BC%A6%E5%9E%8B%E5%87%BD%E6%95%B0/898409?fr=aladdin

Ai是水平面到波峯的高度。

Di是波峯移動的水平向量。

t x φi 是初相,是一個常數。根據正弦型函數,ωx+φ叫做相位,φ叫做初相(即當x=0時的相位 )。t x φi 決定了所有的點是否向左(t x φi >0)或向右(t x φi <0)平行移動|t x φi |個單位。

t是時間。

(x,y)是水平位置(horizontal position)

因此Di點乘(x,y)是可知兩者的夾角方向,等於0爲90度,大於0夾角是銳角。小於0夾角是鈍角。

因此全部正弦波疊加的公式爲:

ch01_eqn002.jpg

由於Gerstner Wave沒有嚴格的高度公式,所以套上上面的公式,我們的Gerstner Wave方程式爲:

這樣就更好理解了吧?H(x,y,t)是波的高度,是正弦波。

x,y對應着unity 裏vector3裏的x,z。

理解上面的參數是幹嘛的,我們就可以寫代碼啦!

計算頂點的代碼在C#裏爲:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class WaterWave : MonoBehaviour
{
    Mesh mesh;
    public float height;
    //尖銳
    [Range(0, 1f)]
    public float sharp = 0.5f;
    [Range(0.5f, 10f)]
    public float speed = 2f;//初相
    [Range(1, 50)]
    public int waveT;//週期
    public Vector2 WaveDir = Vector2.left;

    private Vector3[] baseVertices;
    private void OnEnable()
    {
        mesh = GetComponent<MeshFilter>().mesh;
        baseVertices = mesh.vertices;
    }

    void Update()
    {
            Vector3[] vertices = this.mesh.vertices;
            for (int i = 0; i < vertices.Length; i++)
            {
                Vector3 vertice = this.baseVertices[i];
                float A = this.height;
                float w = (float)(2 * Math.PI / waveT);
                float Qi = sharp / w * A;
                float cosNum = Mathf.Cos(Time.time * speed + w * Vector2.Dot(WaveDir, new Vector2(vertices[i].x, vertices[i].z)));
                float sinNum = Mathf.Sin(Time.time * speed + w * Vector2.Dot(WaveDir, new Vector2(vertices[i].x, vertices[i].z)));

                vertice.x += Qi * A * WaveDir.x * cosNum;
                vertice.z += Qi * A * WaveDir.y * cosNum;
                vertice.y = sinNum * A;

                vertices[i] = vertice;
            }
            this.mesh.vertices = vertices;
            this.mesh.RecalculateNormals();//重新計算法線
        }

}

代碼很少吧?這只是一個波的計算。

在以上代碼裏,this.mesh.RecalculateNormals();幫我們重新計算法線了。

法線公式及解釋

如果要手動計算法線,官方也給出了公式,並且指出說明計算效率非常高:

013equ04.jpg

其中:

P是P(x,y,t)

其他的參數和計算頂點的一樣。具體代碼請下載demo查看。

上圖是多個波疊加起來的效果,疊加的源碼在demo裏可以查看。

Demo下載

如果以上內容看起來有點吃力,是因爲沒有活生生的例子。所以我們提供了demo哦!

裏面有源碼。在編輯器調整參數,運行看看效果,結合本文看看源碼,相信你很快就能理解並開始運用啦!

某度網盤Demo下載

github鏈接

demo裏分別有shader和C#的例子。我發現C#裏寫GerstnerWave操作頂點和法線,會可能出現閃爍等異常效果。

但是同樣的公式在shader裏寫就不會。非常費解,所以建議在shader裏寫GerstnerWave。

上圖是shader寫的GerstnerWave效果。

但是不在C#裏寫海波浪,如果是多種LOD的多個mesh拼接成的大海效果,如何計算波浪呢?目前我還沒答案。

等我研究出了,我會寫第二篇的。

更新:

shader代碼裏

float w = (float)(2 * UNITY_PI / waveT);和

float w = 1 / waveT;的問題不用糾結,都是初相的大小不同而已,大小不同,效果也不同,大家可以註釋其中一個看效果。

最後:

因爲教程是基於我自己的理解來寫的,如果有紕漏和錯誤,請讀者指出,共同學習,共同進步,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章