Having trouble getting Gerstner Waves to properly deform my plane mesh

Hey all,

I’m attempting to implement a single Gerstner wave so that it deforms vertices of the mesh properly, but at the moment it just moves the entire mesh around in circles, with its speed changing as i change the amplitude and time parameters.

Is this an issue with the way I’m changing the array of vertices? Or an issue with my implementation of the wave formula? Please forgive me for any bad coding practices or obvious errors, I’m very new to unity and coding in general, and this is all an amalgamation of several different articles and tutorials.

It’s all in a single script right now, attached to the plane:

using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using Unity.Mathematics;
using UnityEngine.UIElements;
using UnityEngine.ProBuilder;
using System.Runtime.InteropServices.WindowsRuntime;

public class GerstnerCodeTest : MonoBehaviour

    
{
    private MeshFilter meshFilter;

    [SerializeField] private Vector3 direction = Vector3.zero;
    [SerializeField] private Vector3 vertexPosition = Vector3.zero;

    [SerializeField] private float amplitude = 1f;
    [SerializeField] private float time = 1f;
    [SerializeField] private float phase = 1f;
    [SerializeField] private float frequency = 1f;

    [SerializeField] private float gravity = 9.81f;
    [SerializeField] private float depth = 1f;

    private void Awake()
    {
        meshFilter = GetComponent<MeshFilter>();
    }

    private void FixedUpdate()
    {
        Vector3[] vertices = meshFilter.mesh.vertices;

        for (int i = 0; i < vertices.Length; i++)
        {
            vertices[i] = Displacement(vertices[i]);
        }

        meshFilter.mesh.vertices = vertices;
        meshFilter.mesh.RecalculateNormals();
    }

    private Vector3 Displacement(Vector3 position)
    {
        Vector3 displacement = Gerstner(direction, vertexPosition, amplitude, time, gravity, phase, depth);
        Vector3 newPosition = displacement + position;
        return newPosition;
    }

    private Vector3 Gerstner(Vector3 direction, Vector3 vertexPosition, float amplitude, float time, float gravity, float phase, float depth)
    {
        //X COMPONENT
        float xComponent = 
            -1 * ((direction.x / direction.magnitude) * (amplitude / (math.tanh(direction.magnitude * depth)))
            * Mathf.Sin(Theta(direction, vertexPosition, gravity, depth, time, phase)));

        //Y COMPONENT
        float yComponent = amplitude * Mathf.Cos(Theta(direction, vertexPosition, gravity, depth, time, phase));

        //Z COMPONENT
        float zComponent =
            -1 * ((direction.z / direction.magnitude) * (amplitude / (math.tanh(direction.magnitude * depth)))
            * Mathf.Sin(Theta(direction, vertexPosition, gravity, depth, time, phase)));

        return new Vector3 (xComponent, yComponent, zComponent);
    }

    private float Theta(Vector3 direction, Vector3 vertexPosition, float gravity, float depth, float time, float phase)
    {
        float theta = (direction.x * vertexPosition.x + direction.z * vertexPosition.z) - (AngularFrequency(gravity, direction, depth) * time * Time.time - phase);
        return theta;
    }

    private float AngularFrequency(float gravity, Vector3 direction, float depth)
    {
        float angularFrequency = Mathf.Sqrt(gravity * direction.magnitude * math.tanh(direction.magnitude * depth));
        return angularFrequency;
    }

}

I’m pretty sure your problem is how lines 34 to 41 extract the existing vertices and add more onto them… again and again.

Wouldn’t you normally add the wave output into a fixed “original position” for the mesh, as in sample the verts at the start into an array in your class, then use those to add to the generated values?

Also: don’t do stuff like this in FixedUpdate()… if you ever reach max fixed update timeslice your framerate will slam to a stop and you’ll have snail snot framerate. So only physics in FixedUpdate(), everything else in Update()

2 Likes

I can remember we had pretty much the same question several years ago. A quick google search and I found it:

The question was on UnityAnswers and the formatting is still broken due to the migration. But the issue was actually the same. You need to keep the original vertices and then apply the transformation on the original values and write the modifed data into a separate array.

Also as I said in my other post, you want to avoid recreating those arrays all the time. You create tons of garbage each frame. Keep one source and one destination array and simply reuse them.

ps: I just fixed the formatting. Hopefully I didn’t make any errors as the migration interpreted [i] in code as BB codes for italic formatting. So every array index was literally missing…

2 Likes

Thanks Kurt!

Yeah this definitely seemed to be the issue, also that it looks like I wasn’t actually passing the proper mesh vertices into my wave function.

Spent a lot of last night troubleshooting and managed to get the vertices moving, but seems like my implementation of the equations are a little wonky so I’ll have to revisit that. Getting lots of weird behavior, the mesh will shoot off wildly if I raise the amplitude parameter, along with some funky looking geometry.

Appreciate the help!
Will post my updated code once I’m back at my computer.

This post was super helpful! Came across it the other day as I was troubleshooting, thanks for the reformatting!

I’ll see if I can post a screencap of the geometry now that it’s actually moving.

Thanks!

Here’s where I’m at now:

Should have paid more attention in math class…

Script:

public class GerstnerCodeTest : MonoBehaviour
{
    private MeshFilter meshFilter;
    private Mesh mesh;

    [SerializeField] private Vector3 direction = Vector3.zero;

    [SerializeField] private Vector3 vertexPosition = Vector3.zero;

    [SerializeField] private float amplitude = 1f;
    [SerializeField] private float time = 1f;
    [SerializeField] private float phase = 1f;

    [SerializeField] private float gravity = 9.81f;
    [SerializeField] private float depth = 1f;

    Vector3[] initialVertices;
    Vector3[] newVertices;
    


    private void Awake()
    {
        meshFilter = GetComponent<MeshFilter>();
        mesh = meshFilter.mesh;
    }

    private void Update()
    {
        initialVertices = meshFilter.mesh.vertices;
        newVertices = new Vector3[initialVertices.Length];
        direction = direction.normalized;

        for (int i = 0; i < initialVertices.Length; i++)
        {
            vertexPosition = initialVertices[i];

            newVertices[i] = Displacement(initialVertices[i]);
            
        }

        meshFilter.mesh.vertices = newVertices;
        //meshFilter.mesh.RecalculateNormals();

    }

    private Vector3 Displacement(Vector3 position)
    {
        Vector3 displacement = Gerstner(direction, vertexPosition, amplitude, time, gravity, phase, depth);
        Vector3 newPosition = displacement + position;
        return newPosition;
    }

    private Vector3 Gerstner(Vector3 direction, Vector3 vertexPosition, float amplitude, float time, float gravity, float phase, float depth)
    {

        //X COMPONENT
        float xComponent =
            ((direction.x / direction.magnitude) * (amplitude / (math.tanh(direction.magnitude * depth)))
            * Mathf.Sin(Theta(direction, vertexPosition, gravity, depth, time, phase)));

        //Y COMPONENT
        float yComponent = amplitude * Mathf.Cos(Theta(direction, vertexPosition, gravity, depth, time, phase));

        //Z COMPONENT
        float zComponent =
            ((direction.y / direction.magnitude) * (amplitude / (math.tanh(direction.magnitude * depth))) //Y TO Z
            * Mathf.Sin(Theta(direction, vertexPosition, gravity, depth, time, phase)));

        return new Vector3(-1 * xComponent, yComponent, -1 * zComponent);
        

        
    }

    private float Theta(Vector3 direction, Vector3 vertexPosition, float gravity, float depth, float time, float phase)
    {

        float theta = (direction.x * vertexPosition.x + direction.z * vertexPosition.z) - (AngularFrequency(gravity, direction, depth) * Time.time - phase);
        return theta;

    }

    private float AngularFrequency(float gravity, Vector3 direction, float depth)
    {

        float angularFrequency = Mathf.Sqrt(gravity * direction.magnitude * math.tanh(direction.magnitude * depth));
        return angularFrequency;

    }


}

Not sure if that means you’re happy or not… but if you’re not happy then…

It’s never too late! Hurry over to any one of the thousands of videos that come back for a basic tutorial to do gerstner waves in Unity. Nobody is going to retype it all for you in in this box and honestly it’s pretty basic trig stuff mixed with 2D loop iteration, nothing more.

Two steps to tutorials and / or example code:

  1. do them perfectly, to the letter (zero typos, including punctuation and capitalization)
  2. stop and understand each step to understand what is going on.

If you go past anything that you don’t understand, then you’re just mimicking what you saw without actually learning, essentially wasting your own time. It’s only two steps. Don’t skip either step.

Imphenzia: How Did I Learn To Make Games:

1 Like

Thanks for the link!

Yeah I certainly wasn’t expecting anyone to code the rest for me, just wanted to share where it was at now compared to the last version.

Not quite happy with the results of the wave function, definitely need to re-work the equations. But happy with the Vertex array situation for now! That was definitely one of the elements I didn’t quite understand before that makes sense now that I was able to implement it on something of my own.

Will definitely check out the video.

1 Like

Cool… what you’re tinkering with (procedural generation of 3D geometry) is near and dear to my heart… it is a massive area of study and I claim only the slightest tinkerer’s knowledge of the space, but I still play with procgen nearly daily, variously as both a professional and as a hobbyist.

If you’re curious, googling “procgen Unity” will turn you up all manner of whacky algorithmic ways of making content. In fact, I even keep a little open source repository of random procgen that I call MakeGeo.

MakeGeo is really just a random smattering of stuff that happened to interest me enough that I actually tinkered with it, then cleaned it up enough to go into that repo.

The best advice I can give to anyone playing with procgen is to move in small provable micro-baby steps, use source control rigorously, and always assume that what you have working so is a millisecond away from simply silently NOT working anymore for some of the most bafflingly-abstract reasons, reasons you would never imagine in advance. It’s one of those “final frontier” kinda subject matters.

MakeGeo is presently hosted at these locations:

https://bitbucket.org/kurtdekker/makegeo