Search Unity

Problem with my ocean waves (mesh deformation)

Discussion in 'Scripting' started by gsus725, Jan 25, 2015.

  1. gsus725

    gsus725

    Joined:
    Aug 23, 2010
    Posts:
    250
    I have this script that deforms a plane mesh to create ocean waves. Then I tiled a 10x10 grid of those planes to create the entire ocean for my world.

    Problem is, I need the edges of each tile to line up with its neighbor, to provide the illusion of a seamless ocean, so that the player can transition from one tile to the next without noticing a difference.

    Can you please suggest how you would accomplish seamless tiling? I am willing to redo the entire system if needed.

    Code (csharp):
    1.  
    2.  var scale = 10.0;
    3.  var speed = 1.0;
    4.  var noiseStrength = 4.0;
    5.  var noiseWalk=1f;
    6.  
    7.  private var baseHeight : Vector3[];
    8.  
    9.  function Update () {
    10.          var mesh : Mesh = GetComponent(MeshFilter).mesh;
    11.          if (baseHeight == null)
    12.                  baseHeight = mesh.vertices;
    13.          var vertices = new Vector3[baseHeight.Length];
    14.          for (var i=0;i<vertices.Length;i++)
    15.          {
    16.                  var vertex = baseHeight[i];
    17.                  vertex.y += Mathf.Sin(Time.time * speed+ baseHeight[i].x + baseHeight[i].y + baseHeight[i].z) * scale;
    18.                  vertex.y += Mathf.PerlinNoise(baseHeight[i].x + noiseWalk, baseHeight[i].y + Mathf.Sin(Time.time * 0.1)    ) * noiseStrength;
    19.                  vertices[i] = vertex;
    20.          }
    21.          mesh.vertices = vertices;
    22.          mesh.RecalculateNormals();
    23.  }
    24.  
     
  2. kiriri

    kiriri

    Joined:
    Jan 14, 2011
    Posts:
    107
    Couple of things first :
    You will run into performance issues if you call MeshFilter.mesh every frame, as it will copy the entire Mesh each time you call Update (and even worse the GC needs to clean up those massive meshes afterwards, which will mess up your generations). Not to mention then you only need to create the vertex array once instead of each frame!

    But more to the point, you're using managed code arrays so unlike Unity's internal mesh class you can create arrays with a lot of Vector3s in them.
    You can first use your algorithm to calculate a 10 times larger vertex array and then use a 2d index system on each of your ocean planes to get the right vertices for them.
    vertex.y could then be something like

    vertex.y = universalVertexArray[xTileIndex * tileWidth * numberOfTilesOnX + yTileIndex * tileHeight ]

    Or you could just use a 2D array like

    vertex.y = universalVertexArray[xTileIndex * tileWidth , yTileIndex * tileHeight ]

    If you're sure you only have 1 ocean per scene, you can make this universal vertex array static to easily get at it. Otherwise I'd recommend creating a parent GameObject and a new MonoBehaviour that manages this array as well as properties like scale, speed etc, since they are I assume the same for every tile. You can get at it via parent.GetComponent<>() then.

    The other option would be the inverse of this. It does not require an additional array but it'll be less readable. Each time you use x or z in your algorithm you will instead use x + xOffset or z + zOffset.
    The offset will have to be something like yTileIndex * tileHeight or xTileIndex * tileWidth. That way the x and z for vertices that are in different Tiles but essentially at the same x,z position will have the same x,z values in the algorithm and the same y value should be returned.

    If it doesn't work take a piece of paper and draw your ocean in a simplified way, like 3 * 3x3 planes, then look at your new formula and see if it works correctly. I think the tileHeight and tileWidth need to be the amount of vertices per row/column minus 1 (because the last vertex in a row/column overlaps with the first one of the tile right next to it), but I might not have put enough thought into it ;)

    Hope that helped, I sometimes have difficulties expressing myself I know, so if something's still unclear, don't hesitate to ask!

    EDIT:
    Also make sure to cache Time.time if you chose the second solution, otherwise you will get different y values for the same x,z positions if you calculate one after the other.
     
    Last edited: Jan 25, 2015
    sootie8 likes this.
  3. joni-giuro

    joni-giuro

    Joined:
    Nov 21, 2013
    Posts:
    435
    For the people stumbling across this in the future: I converted it to C#. It's worth to note that in unity5, if you want realistic looking water, there's the Water4 asset in the water effects included with unity that looks pretty good and saves you some work. If, like in my case, the water4 thingy is not what you want, here's the C# script, with some minor changes:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Waves : MonoBehaviour {
    5.  
    6.     public float waveHeight = 10.0f;
    7.     public float speed = 1.0f;
    8.     public float waveLength = 1.0f;
    9.     public float noiseStrength = 4.0f;
    10.     public float noiseWalk = 1.0f;
    11.     public bool diagonalWaves = false;
    12.  
    13.     private Vector3[] baseHeight;
    14.     private Vector3[] vertices;
    15.     private Mesh mesh;
    16.  
    17.     void Awake() {
    18.         mesh = GetComponent<MeshFilter>().mesh;
    19.         if (baseHeight == null) {
    20.             baseHeight = mesh.vertices;
    21.         }
    22.     }
    23.  
    24.     void Update () {
    25.         if (vertices == null) {
    26.             vertices = new Vector3[baseHeight.Length];
    27.         }
    28.  
    29.         for (int i=0;i<vertices.Length;i++) {
    30.             Vector3 vertex = baseHeight[i];
    31.             if (diagonalWaves) {
    32.                 vertex.y += Mathf.Sin(Time.time * speed + baseHeight[i].x * waveLength + baseHeight[i].y * waveLength + baseHeight[i].z * waveLength) * waveHeight;
    33.             } else {
    34.                 vertex.y += Mathf.Sin(Time.time * speed + baseHeight[i].x * waveLength + baseHeight[i].y * waveLength) * waveHeight;
    35.             }
    36.             vertex.y += Mathf.PerlinNoise(baseHeight[i].x + noiseWalk, baseHeight[i].y + Mathf.Sin(Time.time * 0.1f)) * noiseStrength;
    37.             vertices[i] = vertex;
    38.         }
    39.         mesh.vertices = vertices;
    40.         mesh.RecalculateNormals();
    41.     }
    42. }
    43.  
     
    Last edited: May 5, 2015