Search Unity

Low-Poly water help

Discussion in 'Shaders' started by kurai, Oct 28, 2014.

  1. kurai

    kurai

    Joined:
    Jan 9, 2012
    Posts:
    118
    Hi there,
    I'm trying to make a low-poly water for my game. Right now I have this:



    Which is kinda good but not great. I made it in Shader Forge, but I'm stuck. I don't know enough about shaders and I really cannot go further than this.

    So I guess I'm open to try different ways to do it. What I'd like is a shader that could move the vertices of my water mesh in a way that resembles ripples (right now my mesh just expand/contract altogether, and I cannot have a real wave effect). The reflection is not important (if I cannot go further than this, I may as well get rid of it), but I'd like to have a nice, stylized water which can go well with the low-poly non-textured style of the scenario.

    So, what I'm asking you is simple: ideas, advice, directions. Both to ready-made shaders (but I've looked at the asset store and didn't find anything useful) and to tutorials, documentation, anything that could help me improve what I've done.

    As you may have understood, I'm a total newbie with shaders, so any help is useful. Thank you so much!
     
  2. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    If you want to move the vertices, your water can't really be low poly. It needs to be fairly high poly to prevent artifacts.

    You can however add a few normal maps to the water and slide those in various directions. This will make the water appear to have ripples, without actually having to introduce ripples into the model.
     
  3. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    You can write a shader, which has a vertex function that moves the vertices.

    then you can do stuff like

    v.vertex.xyz = float3(0,1,0)*Sin(v.vertex.x*_Time.x);

    (I'm writing that from the top of my head, it might not work).

    You should do more complex math than that obviously, but it's a place to start.
     
    SunnyChow and kurai like this.
  4. johnschwarzorz

    johnschwarzorz

    Joined:
    Nov 3, 2014
    Posts:
    1
    How'd this turn out?
     
  5. kurai

    kurai

    Joined:
    Jan 9, 2012
    Posts:
    118
    I ended up just using a reflective rim-shaded material and moving the whole texture rather than single vertices. It has not the ripple effect I wanted, but I really couldn't manage to write the shader the way I wanted to (again, total newbie) and the normal map solution totally clashed with the flat-shaded style of the game.

    Maybe I can try again sometime, but right now that's the best I can do :)

    You can see it on the game webpage, and in motion probably on this let's play video.

    If you have any kind of advice on how improve it, it's very welcome!
     
  6. aubergine

    aubergine

    Joined:
    Sep 12, 2009
    Posts:
    2,880
    Your graphics are very nice, here is my advice on water.
    Instead of normal maps, you could use a cartoony white foam texture and move that around your water to give it more life.
     
  7. kurai

    kurai

    Joined:
    Jan 9, 2012
    Posts:
    118
    That's another interesting solution. But I really would like to get an effect like the one on the image below (with moving ripples via vertex displacement)



    Any advice on that? I particularly like the subtle reflection :)
     
  8. aubergine

    aubergine

    Joined:
    Sep 12, 2009
    Posts:
    2,880
    That also is easy if you had a high poly water mesh, so you could move the vertices. But that is alot of polygons.

    On the other hand, it could still be possible to get a smiliar effect with a nice texture(with those triangles and changing their colors) but i have no idea how to animate it.
     
  9. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    I don't think there's reflection in that. I think it's just a bit transparent and the model goes below the sea a bit.
     
  10. kurai

    kurai

    Joined:
    Jan 9, 2012
    Posts:
    118
    Now that I look better, you're right. It seems the polygons are more (or less) transparent depending on the normal angle with the camera. Does it make sense? Hmmm... got to experiment a bit on that. I'd like to try moving vertices with shaders for the ripple effect, can someone point me to some simple documentation about that?

    Going step by step, here, but I really would like to obtain an effect similar to the pic I posted before :)
     
  11. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    Well, this is for surface shaders, but still:

    http://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html

    Scroll down to Normal Extrusion with Vertex Modifier.

    The lines that are most interesting are
    Code (csharp):
    1. void vert (inout appdata_full v) {
    2.         v.vertex.xyz += v.normal * _Amount;
    3. }
    You need to add Time somehow to that equation. As I said in my first reply something like Sin(v.vertex.x*_Time.x); will create a sinewave across the x axis.
     
  12. joni-giuro

    joni-giuro

    Joined:
    Nov 21, 2013
    Posts:
    435
    Sorry to dig out an old topic but I was just wondering if you found a good, fast solution..
    I'm pretty much on the same boat, trying to create a low poly water and I'm pretty satisfied with the look of my solution but not quite happy with the performance of it (I want to develop for mobile).
    Here's a video to demonstrate what I have:


    What I did is:
    1) create a plane in blender and bring it to unity
    2) With the NoSharedVertices.cs script I split said plane into separate triangles (first problem: This makes the number of vertices to animate explode, since no vertice will be shared among the various triangles, but I need to separate them in order to obtain the low-poly look)
    3) With the Waves.cs script I look through each vertices and add a generic wave and a pseudo random noise (I say pseudo random because the value for each vertex depends from its position)

    Again, this looks good but is a performance killer, has anyone any advice on that? Some code review? Is there a way to accomplish this without splitting the plane? What would be the fastest solution? Sorry for all the questions, I'm a beginner.

    Also, I'm sharing the scripts I'm using so maybe you guys could give some advice, or maybe someone wants to try them out.

    NoSharedVertices.cs (found on the web) this is basically the same as the "split edges" modifier in blender:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public class NoSharedVertices : EditorWindow {
    5.    
    6.     private string error = "";
    7.    
    8.     [MenuItem("Window/No Shared Vertices")]
    9.     public static void ShowWindow() {
    10.         EditorWindow.GetWindow(typeof(NoSharedVertices));
    11.     }
    12.    
    13.     void OnGUI() {
    14.         //Transform curr = Selection.activeTransform;
    15.         GUILayout.Label ("Creates a clone of the game object where the triangles\n" +
    16.                          "do not share vertices");
    17.         GUILayout.Space(20);
    18.        
    19.         if (GUILayout.Button ("Process")) {
    20.             error = "";
    21.             NoShared();
    22.         }
    23.        
    24.         GUILayout.Space(20);
    25.         GUILayout.Label(error);
    26.     }
    27.    
    28.     void NoShared() {
    29.         Transform curr = Selection.activeTransform;
    30.        
    31.         if (curr == null) {
    32.             error = "No appropriate object selected.";
    33.             Debug.Log (error);  
    34.             return;
    35.         }
    36.        
    37.         MeshFilter mf;
    38.         mf = curr.GetComponent<MeshFilter>();
    39.         if (mf == null || mf.sharedMesh == null) {
    40.             error = "No mesh on the selected object";
    41.             Debug.Log (error);
    42.             return;
    43.         }
    44.        
    45.         // Create the duplicate game object
    46.         GameObject go = Instantiate (curr.gameObject) as GameObject;
    47.         mf = go.GetComponent<MeshFilter>();
    48.         Mesh mesh = Instantiate (mf.sharedMesh) as Mesh;
    49.         mf.sharedMesh = mesh;
    50.         Selection.activeObject = go.transform;
    51.        
    52.         //Process the triangles
    53.         Vector3[] oldVerts = mesh.vertices;
    54.         int[] triangles = mesh.triangles;
    55.         Vector3[] vertices = new Vector3[triangles.Length];
    56.         for (int i = 0; i < triangles.Length; i++) {
    57.             vertices[i] = oldVerts[triangles[i]];
    58.             triangles[i] = i;
    59.         }
    60.         mesh.vertices = vertices;
    61.         mesh.triangles = triangles;
    62.         mesh.RecalculateBounds();
    63.         mesh.RecalculateNormals();
    64.        
    65.         // Save a copy to disk
    66.         string name = "Assets/Editor/"+go.name+Random.Range (0, int.MaxValue).ToString()+".asset";
    67.         AssetDatabase.CreateAsset(mf.sharedMesh, name);
    68.         AssetDatabase.SaveAssets();
    69.     }
    70. }
    And the animated waves Waves.cs:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class Waves : MonoBehaviour {
    6.    
    7.     public float waveHeight = 10.0f;
    8.     public float speed = 1.0f;
    9.     public float waveLength = 1.0f;
    10.     public float noiseStrength = 4.0f;
    11.     public float noiseWalk = 1.0f;
    12.     public float randomHeight = 0.2f;
    13.     public float randomSpeed = 5.0f;
    14.     public float noiseOffset = 20.0f;
    15.    
    16.     private Vector3[] baseHeight;
    17.     private Vector3[] vertices;
    18.     private List<float> perVertexRandoms = new List<float>();
    19.     private Mesh mesh;
    20.    
    21.     void Awake() {
    22.         mesh = GetComponent<MeshFilter>().mesh;
    23.         if (baseHeight == null) {
    24.             baseHeight = mesh.vertices;
    25.         }
    26.  
    27.         for(int i=0; i < baseHeight.Length; i++) {
    28.             perVertexRandoms.Add(Random.value * randomHeight);
    29.         }
    30.     }
    31.    
    32.     void Update () {
    33.         if (vertices == null) {
    34.             vertices = new Vector3[baseHeight.Length];
    35.         }
    36.        
    37.         for (int i=0;i<vertices.Length;i++) {
    38.             Vector3 vertex = baseHeight[i];
    39.             Random.seed = (int)((vertex.x + noiseOffset) * (vertex.x + noiseOffset) + (vertex.z + noiseOffset) * (vertex.z + noiseOffset));
    40.             vertex.y += Mathf.Sin(Time.time * speed + baseHeight[i].x * waveLength + baseHeight[i].y * waveLength) * waveHeight;
    41.             vertex.y += Mathf.Sin(Mathf.Cos(Random.value * 1.0f) * randomHeight * Mathf.Cos (Time.time * randomSpeed * Mathf.Sin(Random.value * 1.0f)));
    42.             //vertex.y += Mathf.PerlinNoise(baseHeight[i].x + Mathf.Cos(Time.time * 0.1f) + noiseWalk, baseHeight[i].y + Mathf.Sin(Time.time * 0.1f)) * noiseStrength;
    43.             vertices[i] = vertex;
    44.         }
    45.         mesh.vertices = vertices;
    46.         mesh.RecalculateNormals();
    47.     }
    48. }
     
    nacs likes this.
  13. nacs

    nacs

    Joined:
    Jun 5, 2013
    Posts:
    6
    I think the biggest performance killer in your code is actively deforming the mesh every frame. Your code is basically recreating the entire mesh every frame which won't perform well (and really bad on mobile / lower end).

    The better way to do it would be by moving your method into a custom shader and moving the vertices via the shader instead of recreating the mesh in the engine. I'm not a shader expert so can't help you there but have experimented with procedural mesh generation a lot in code so know how badly it performs if you're doing it every frame.
     
  14. joni-giuro

    joni-giuro

    Joined:
    Nov 21, 2013
    Posts:
    435
    I tried that path and wrote in this forum thread http://forum.unity3d.com/threads/flat-shaded-ocean.323808/#post-2103510 . As you can see it looks promising but I can't get the flat-shaded thing working. Any advice?
     
  15. delinx32

    delinx32

    Joined:
    Apr 20, 2012
    Posts:
    417
    That flat shading you're seeing is basically what the polyworld assets give you. i own a polyworld asset and it comes with a water shader that looks like the example you posted. Heck, that might even be a screenshot from a polyworld generated mesh. Its not particularly cheap, but if that's the style you're going for then it will probably be well worth the 50$ it will cost you. It also includes scripts to convert any mesh to a polyworld style mesh.
     
  16. saenkoav

    saenkoav

    Joined:
    May 31, 2016
    Posts:
    4
    Kurai, you water really like physic water? I mean, boat will be like on water from Standart Asset?
     
  17. Lanre

    Lanre

    Joined:
    Dec 26, 2013
    Posts:
    3,973
    Looks great! I recently implemented this myself. Moving it off to a shader is nearly impossible if you are targeting mobile (especially OpenGLES 2 and 3). The reason why it looks flat is because of one thing: normals. Deforming the mesh vertices is extremely easy in the vertex shader. You can literally copy and paste code from C#. The normals however aren't so easy. Lighting at any point on a surface depend on the normals, and the normals depend on the neighboring vertices of a particular vertex. So except you have geometry shader with the ability to synthesize this information, the closest you can get is an approximation which looks terrible (I tested it myself).

    I managed to get my seas with almost 2k verts waving nicely at 60FPS on an iPhone 4S. On an iPhone 4S. The secret is quite simple: Threads. If you animate the vertices on the main thread, you'll be allotting precious CPU frame time. Instead of doing this, move the vertex deformation to a worker thread. This will usually run faster than 60FPS, so you can slow it down by using an EventWaitHandle that is set in the main thread's Update.

    Oh, and to avoid re-killing performance, stay away from Monitor/lock when updating the mesh filter on the main thread (Unity's API isn't thread safe). Instead, double buffer the vertices and ping pong between them. This becomes memory consuming, but I believe it's more optimized. A user won't see how much memory your application is consuming, but they will definitely see the stuttering on the screen. I guess it's all about UX.
     
    joni-giuro likes this.
  18. Lanre

    Lanre

    Joined:
    Dec 26, 2013
    Posts:
    3,973
    I'm not sure if you need physics for floating a boat. You can get the closest vertex to the boat, extract its normal, and align the boat's up axis with the normal. This will create a nice-looking boat that floats and jiggles.
     
  19. Jildert

    Jildert

    Joined:
    Feb 15, 2010
    Posts:
    33
    You can still calculate the normals if your mesh is uniform. Just use the UV to store additional vertice index information. Since you are not using any textures anyways. ;-) That way you can calculate the other 2 vertice-positions in your code to do the normal calculation.

    I've done something like this. You can see the result here. The example uses a noise texture but you could also just calculate the wave patterns. Do note that if you use a noise texture that it won't work on all android device. As not all devices support texture access in vertex function :'(
     
  20. Lanre

    Lanre

    Joined:
    Dec 26, 2013
    Posts:
    3,973
    Interesting approach, you could encode neighbor vertex positions as TEXCOORD1 for instance and use it to calculate world positions in the vertex shader. For my game, the mesh is non-uniform (it is displaced on the XZ axes) to look more interesting. Thanks for letting me know of this technique!
     
  21. geeosp

    geeosp

    Joined:
    Aug 15, 2016
    Posts:
    8
    joni.giuro, I kind of landed here. I havent tried yet but this low poly water is also a effect I wish to archive. Have you tried create a plane outside of unity, import it like a normal 3d model asset, go to the plane import settings and change the smooting angle to 0? Does not it gives you the "flat normal" for each vertex. I mean, it works for static models, doew it works with dynamic meshes?
     
  22. GiyomuGames

    GiyomuGames

    Joined:
    Jan 12, 2015
    Posts:
    80
    Hey Jildert,

    The video is down but I'm still interested in what you are suggesting. Can you show us your shader code as I'm not sure how to accomplish what you are saying.