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;的问题不用纠结,都是初相的大小不同而已,大小不同,效果也不同,大家可以注释其中一个看效果。

最后:

因为教程是基于我自己的理解来写的,如果有纰漏和错误,请读者指出,共同学习,共同进步,谢谢。

猜你喜欢

转载自blog.csdn.net/zakerhero/article/details/100113375