Generating waves for an ocean

Hi, I’m working on a game for school, and i need to make a realistic ocean on which the player can navigate a ship, but all i have been able to produce is the wavesas seen in the first attachment. These waves are a bit too regular, and iwould like to make them behave more like this:

So basically i need to make the waves a bit more random, and make them dip horizontally as well as vertically.
Thanks in advance!

I’m not sure if this would interest you. It’s an open source ocean shader:

hey DevCake_ can we see the script that makes the “the boring Waves” :slight_smile:

Thanks Nigey, I’ll definetely be looking in to that!

Jister, this script generates values for the waves:

using UnityEngine;
using System.Collections;

public class Waves : MonoBehaviour {

    public float scale = 0.1f;
    public float speed = 1.0f;
    public float waveDistance = 1f;
    public float noiseStrength = 1f;
    public float noiseWalk = 1f;

    public float GetWaveYPos(float x_coord, float z_coord)
    {
        float y_coord = 0f;

        y_coord += Mathf.Sin((Time.time * speed + z_coord)/waveDistance)*scale;
        y_coord += Mathf.PerlinNoise(x_coord + noiseWalk, z_coord + Mathf.Sin(Time.time * 0.1f)) * noiseStrength;
        return y_coord;
    }

try posting the whole script :slight_smile:

that’s the scripts that generates all the values for setting the wave height, and here is the scripts that manipulates the vertexes of my sea plane:

using UnityEngine;
using System.Collections;

public class GenerateWaves : MonoBehaviour {

    Mesh waterMesh;
    private Vector3[] newVertices;
    private Vector3[] originalVertices;
    private Waves waveScript;

    // Use this for initialization
    void Start () {
        waterMesh = this.GetComponent<MeshFilter>().mesh;
        originalVertices = waterMesh.vertices;
        GameObject gameController = GameObject.FindGameObjectWithTag("GameController");
        waveScript = gameController.GetComponent<Waves>();
   
    }
   
    // Update is called once per frame
    void Update () {

        moveSea();
   
    }

    void moveSea()
    {
        newVertices = new Vector3[originalVertices.Length];

        for (int i = 0; i < originalVertices.Length; i++)
        {
            Vector3 vertice = originalVertices[i];
            vertice = transform.TransformPoint(vertice);           
            vertice.y += waveScript.GetWaveYPos(vertice.x, vertice.z);
            newVertices[i] = transform.InverseTransformPoint(vertice);
        }
        waterMesh.vertices = newVertices;
        waterMesh.RecalculateNormals();
    }
}

You can get cheap cfd effects by treating the wave propogation like springs. Give each vertix a velocity. Each frame adjust the velocity according to the distance to neighbouring vertices. With a little bit of cleverness the waves will react to your vessels as well.

It’s the basis behind my 2D game, PondWars, but the principle also works in 3D

1 Like

Sounds like fun, share script??

It’s the sin that is “boring” being it periodic. You may try adding more waves with different frequencies changing over time. First look into @Kiwasi suggestion as it sounds very clever.

you could simply change this line:

y_coord += Mathf.Sin((Time.time * speed + (z_coord*x_coord))/waveDistance)*scale;

but you could also switch from float to Vector3 and have these parameters for all 3 coordinates.

UPDATED version:
still not perfect but a lot less jittery :slight_smile:

using UnityEngine;
using System.Collections;

[System.Serializable]
public class Wave
{
    public Vector3 scale = new Vector3(0.1f, 0.1f, 0.1f);
    public Vector3 speed = new Vector3(1.0f,1.0f,1.0f);
    public Vector3 waveDistance = new Vector3(1.0f,1.0f,1.0f);
    public Vector3 noiseStrength = new Vector3(1.0f,1.0f,1.0f);
    public Vector3 noiseWalk = new Vector3(1.0f,1.0f,1.0f);
   
    public Vector3 GetWave(Vector3 coord)
    {
        Vector3 xyz_coord1 = Vector3.zero;
        Vector3 xyz_coord2 = Vector3.zero;
       
        xyz_coord1.x += Mathf.Cos((Time.time * speed.x + coord.z)/waveDistance.x)*scale.x;
        xyz_coord1.x += Mathf.PerlinNoise(coord.y + noiseWalk.x, coord.z + Mathf.Sin(Time.time * 0.1f)) * noiseStrength.x;

        xyz_coord1.y += Mathf.Sin((Time.time * speed.y + coord.z)/waveDistance.y)*scale.y;
        xyz_coord1.y += Mathf.PerlinNoise(coord.x + noiseWalk.y, coord.z + Mathf.Sin(Time.time * 0.1f)) * noiseStrength.y;

        xyz_coord2.y += Mathf.Sin((Time.time * speed.y + coord.x)/waveDistance.y)*scale.y;
        xyz_coord2.y += Mathf.PerlinNoise(coord.x + noiseWalk.y, coord.z + Mathf.Sin(Time.time * 0.1f)) * noiseStrength.y;

        xyz_coord1.z += Mathf.Cos((Time.time * speed.z + coord.x)/waveDistance.z)*scale.z;
        xyz_coord1.z += Mathf.PerlinNoise(coord.x + noiseWalk.z, coord.y + Mathf.Sin(Time.time * 0.1f)) * noiseStrength.z;

        return xyz_coord1+xyz_coord2;
    }
}

as you may have noticed i changed the class to be Serialized so you can just create an instance in your generator script.
i don’t see the need for it to be monobehaviour…
so for your Wavegenerator:

using UnityEngine;
using System.Collections;

public class Ocean : MonoBehaviour {

    Mesh waterMesh;
    private Vector3[] newVertices;
    private Vector3[] originalVertices;
    public Wave waveScript =  new Wave();

    // Use this for initialization
    void Start ()
    {
        waterMesh = this.GetComponent<MeshFilter>().mesh;
        originalVertices = waterMesh.vertices;
    }

    // Update is called once per frame
    void Update () {
    
        moveSea();
    
    }

    void moveSea()
    {
        newVertices = new Vector3[originalVertices.Length];
    
        for (int i = 0; i < originalVertices.Length; i++)
        {
            Vector3 vertice = originalVertices[i];
            vertice = transform.TransformPoint(vertice);      
            vertice += waveScript.GetWavePos(vertice);
            newVertices[i] = transform.InverseTransformPoint(vertice);
        }
        waterMesh.vertices = newVertices;
        waterMesh.RecalculateNormals();
    }
}

Edit: just tested it on a plane with some more subdivisions then the unity plane… and it’s really noisy what i gave you. let me fine tune it a bit :slight_smile:
edit edit: updated the wave script

I’ll see what I can dig out. Its been a while since I worked on the game. This one was done with rigidbodies, but its a little slow on performance

https://www.youtube.com/watch?v=OwYpdoh49KA

1 Like

I haven’t really finished optimizing it, there is a lot more performance you could squeeze out of this with micro optimizations. And its probably hammering the GC. I also cheated on dealing with the edge case.

The script provides verticies that behave like water. To get it to look like water you are probably want to attach textures and shader magic.

Simply attach both scripts to a GameObject with a MeshFilter and a MeshRenderer and hit play.

public class WaterMaker : MonoBehaviour {

        [SerializeField] int waterLength = 100;
        [SerializeField] int waterWidth = 100;
        Vector4 [,] water;
        Mesh waterMesh;
        Vector3[] vertices;
        [SerializeField, Range(0,1)] float damping = 0.1f;
        [SerializeField, Range(0,100)] float waveSpeed = 10;

        void Start () {
            MakeWater();
            MakeWaterMesh();
        }

        void Update () {
            UpdateWater ();
            UpdateMesh ();
        }          

        void MakeWaterMesh(){
            List<Vector3> verticies = new List<Vector3>();
            foreach(Vector4 point in water){
                verticies.Add ((Vector3)point);
            }
            waterMesh = new Mesh();
            waterMesh.MarkDynamic ();
            waterMesh.name = "Water Mesh";
            List<int> triangles = new List<int>();
            for (int i = 0; i < waterLength - 1; i++){
                for (int j = 0; j < waterWidth - 1; j++){
                    triangles.Add (i * waterWidth  + j);
                    triangles.Add (i * waterWidth  + j + 1);
                    triangles.Add ((i+1) * waterWidth  + j);
                }
            }
            for (int i = 1; i < waterLength; i++){
                for (int j = 1; j < waterWidth; j++){
                    triangles.Add (i * waterWidth  + j);
                    triangles.Add (i * waterWidth  + j - 1);
                    triangles.Add ((i-1) * waterWidth  + j);
                }
            }
           
            waterMesh.vertices = verticies.ToArray ();
            vertices = verticies.ToArray ();
            waterMesh.triangles = triangles.ToArray ();
            waterMesh.RecalculateNormals();
            waterMesh.RecalculateBounds();
            GetComponent<MeshFilter>().mesh = waterMesh;
        }

        void MakeWater(){
            water = new Vector4[waterLength,waterWidth];
            for (int i = 0; i < waterLength; i++){
                for (int j = 0; j < waterWidth; j++){
                    water [i,j] = new Vector4(i,0,j,0);
                }
            }
        }

        void UpdateWater (){
            for (int i = 1; i < waterLength - 1; i++){
                for (int j = 1; j < waterWidth - 1; j++){
                    water[i,j].w -= ((water[i,j].y - water[i-1,j].y)
                        + (water[i,j].y - water[i+1,j].y)
                        + (water[i,j].y - water[i,j-1].y)
                        + (water[i,j].y - water[i,j+1].y)) * Time.deltaTime * waveSpeed;
                }
            }   
            for (int i = 0; i < waterLength; i++){
                for (int j = 0; j < waterWidth; j++){
                    water [i,j].w *= Mathf.Pow((1-damping),Time.deltaTime);
                    water[i,j].y += water[i,j].w * Time.deltaTime;
                }
            }   
        }

        void UpdateMesh (){
            for (int i = 0; i < waterLength; i++){
                for (int j = 0; j < waterWidth; j++){
                    vertices[i * waterWidth + j] = (Vector3)water[i,j];
                }
            }
            waterMesh.vertices = vertices;
        }

        public void MoveVertix (int x, int z, float amount){
            water[x,z].y += amount;
        }
    }

And a little bit of randomization to get things started:

    public class FakeWave : MonoBehaviour {
        [SerializeField] float period = 1f;
        [SerializeField] float magnitude = 5f;

        void Start () {
            StartCoroutine (Wave());
        }
       
        IEnumerator Wave (){
            WaterMaker water = GetComponent<WaterMaker>();
            while (true){
                yield return new WaitForSeconds(period);
                int x = Random.Range(2,98);
                int z = Random.Range(2,98);
                water.MoveVertix (x,z,magnitude);
                water.MoveVertix (x-1,z,magnitude);
                water.MoveVertix (x+1,z,magnitude);
                water.MoveVertix (x,z-1,magnitude);
                water.MoveVertix (x,z+1,magnitude);
            }
        }
    }
2 Likes

@DevCake you lucky sausage

1 Like

@Kiwasi trying your water, but it’s not showing up… if i debug your waterMesh.vertices they only have value’s on z, x and y are all 0.

That’s weird. I’ll try importing this to a new project tonight and see what happens.