Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Water wave optimization

Discussion in 'Scripting' started by AndreiTache, Oct 23, 2019.

  1. AndreiTache

    AndreiTache

    Joined:
    Nov 8, 2014
    Posts:
    31
    Hi everyone! I'm working on a boat game and I wanted to add waves to the water.
    I got the following script working, and it correctly modifies the vertices of a plane.

    However, it is really resource intensive. On a 10² vertex plane (the default one from Unity) it's not a big problem, but if I want to increase the nr of vertices to have more resolution (to something like 20²), the fps goes down massively (~400fps -> ~100fps), which makes sense as there are now 4x the nr of vertices.

    The water's area in-game is 5000²m, and a resolution of 20² is not ideal, especially given the framerate cost...


    One way I can think of gaining performance is to have one plane with a high resolution where the player is currently and have another one of a much smaller resolution around it, then switch the position of the high-rez one when the player enters the low-rez plane.
    But I don't know how to make it work, nor how to deal with (literal) edge cases, where the player could overlap a corner & need 4 high-rez planes for accurate physics simulation.

    So I'm a bit stuck =D Any help would be much appreciated!


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. public class WaterScr : MonoBehaviour {
    5.     public float waveHeight = 5f;
    6.     public float waveNoise = 0.1f;
    7.     public float waveSpeed = 0.25f;
    8.     public float waveFrequency = 1f;
    9.  
    10.     private Mesh mesh;
    11.     private MeshCollider meshCollider;
    12.  
    13.     private void Awake() {
    14.         mesh = GetComponent<MeshFilter>().mesh;
    15.         meshCollider = GetComponent<MeshCollider>();
    16.     }
    17.  
    18.     private void FixedUpdate() {
    19.         UpdateWater();
    20.     }
    21.  
    22.     private Vector3[] baseHeight;
    23.     public void UpdateWater() {
    24.         if (baseHeight == null) baseHeight = mesh.vertices;
    25.  
    26.         Vector3[] newVertices = mesh.vertices;
    27.         float width = Mathf.Sqrt(newVertices.Length);
    28.         for (int y = 0; y < width; y++) {
    29.             for (int x = 0; x < width; x++) {
    30.                 int index = Mathf.RoundToInt(y * width + x);
    31.  
    32.                 float newWavePos = Time.time * waveSpeed;
    33.                 newVertices[index].y = Mathf.Sin((Time.time * waveSpeed + baseHeight[index].x + baseHeight[index].y + baseHeight[index].z) + (index * waveFrequency / 100f)) * waveHeight/2;
    34.                 newVertices[index].y += Mathf.PerlinNoise((x + newWavePos) * waveNoise, (y + newWavePos));
    35.             }
    36.         }
    37.  
    38.  
    39.         mesh.SetVertices(new List<Vector3>(newVertices));
    40.         meshCollider.sharedMesh = mesh;
    41.     }
    42. }
    Edit: I should also mention that I'm not looking for a life-like wave simulation, just something that looks interesting and is not too hard to understand and make
     
    Last edited: Oct 23, 2019
  2. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,019
    check with profiler to see whats the slowest part.

    some ideas
    - does it need to be on fixedupdate?
    - can do that calculation loop in another thread or jobs/dots systems
    - apparently meshcollider can be somehow created in jobs
    - do you really need mesh collider? you could have other ways to detect waves by position or from depth texture or so
    - can use shader to add waves/modify mesh vertices
     
    MadeFromPolygons likes this.
  3. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,883
    So it being in fixed update is uncessary. If your doing this to get collisions with water, updating a mesh + mesh collider each frame with fixed update is certainly not the way to go about that.

    Some other points:

    - Updating the collider so often is not great. Find some other way to go about that. Do you really need a collider? What are you using it for, because as said above if for water collisions updating a mesh collider regularly is not the way to go about that
    - Mathf.Sqrt can be expensive
    - Mathf.PerlinNoise can be expensive

    Those expensive bits will be much worse when done in fixedupdate - which your doing.

    Those are the main issues I can see off the top of my head. The updating a collider each frame in fixedupdate is probably the biggest killer. But in general, you really dont need to and shouldnt be doing all that in fixed update.

    You would probably be better off using a noise texture + shader for this, and in general you should do this via a shader tbh.
     
    SparrowGS likes this.
  4. AndreiTache

    AndreiTache

    Joined:
    Nov 8, 2014
    Posts:
    31
    Oh? I didn't know you could get the height from a shader, I haven't played with them at all actually.
    Do you have some example code/tutorial to get me going in the right direction?
     
  5. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,883
    Sure I do :)

    https://www.habrador.com/tutorials/unity-boat-tutorial/4-waves-endless-ocean/

    You want to start at the first part, itll take you through making a water mesh and updating it via shader and then reading it via script to do things like have a boat bob up and down with it.

    That blog is great for a range of math based tutorials too, if your interested in that stuff :) None of this is super easy / newbie friendly though so be prepared to get a bit technical!

    That said , the principle is simple. If you change the mesh vertices using a shader, you can query the mesh at runtime using normal c# like you have done and the returned values will match the changes. So you can animate the waves in shader for performance, but still read the height via script and react to it based on that. Just do so avoiding the mesh collider, I dont see any use case for having one for water.
     
    Westland and SparrowGS like this.
  6. AndreiTache

    AndreiTache

    Joined:
    Nov 8, 2014
    Posts:
    31
    Haha, I actually tried following that tutorial a while ago, but I couldn't keep up with it :) Do you have something simpler?

    I made a simple wave shader, but I'm not entirely sure how to get the height from it's mesh at a specific position(x, z) instead of a vertex.. (the way I've done this before was with a raycast, but as the water won't use a mesh collider anymore, that won't work)

    Edit: Also, I have a code working for buoyancy (just a simple Y check with proportional upwards force), I just don't know how to get the height at a specific position

    Edit edit: Never mind, seems like it'd be easier to write the wave formula in c# and then send the data to the shader, instead of finding the mesh's height. Thanks for the help!
     
    Last edited: Oct 24, 2019