Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Procedural mesh feels beveled when it shouldn't

Discussion in 'Scripting' started by ADNCG, Oct 16, 2019.

  1. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    992
    I'm working on a prototype where the player can carve through a bunch of chunks, making use of marching squares.

    My issue is that it should look like it's one big mesh, not a bunch of chunks.

    It works just fine when it's rendered without "depth", like in the following screenshot.

    2d.png

    The problem happens when I try to give depth to the mesh. What I do is figure out the boundary edges, then create additional vertices matching the boundaries, further on the z axis.

    I'm aware this creates more faces than needed, since all chunks act independantly, but the performance is fine and it is not a concern. What I don't understand is that this should act like a bunch of cubes next to each other, why does it feel beveled?
    3d.png

    Here's the code I used to add depth.
    Code (CSharp):
    1. // This returns a list of edges under the form of triangle indexes.
    2. HashSet<Vector2Int> boundaryTriangles = GetBoundaryTriangles(triangles);
    3.      
    4. foreach(Vector2Int edge in boundaryTriangles)
    5. {
    6.     vertices.Add(vertices[edge.x] + new Vector3(0f, 0f, chunkThickness));
    7.     vertices.Add(vertices[edge.y] + new Vector3(0f, 0f, chunkThickness));
    8.     triangles.AddRange(new List<int>
    9.     {
    10.         edge.x, vertices.Count - 2, vertices.Count - 1,
    11.         vertices.Count - 1, edge.y, edge.x
    12.     });
    13. }
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Could you more clearly explain what the problem is with the result? I can't tell what the result is versus what you want to happen.
     
  3. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    992
    Of course!

    What should happen:
    The surface should look whole, flat, uniform, like in the first screenshot. The user could mistake it for a plane.

    What actually happens:
    The surface looks segregated. It's obvious where the chunks are.

    The problem happens when I give depth to the mesh.
    Does that make sense?
     
  4. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    I'm still not sure I follow, but is it possible you have the winding order backwards on one of the two triangles for each quad? Looks like it might only be showing half the triangles (meaning the other half are facing the other way). Try swapping the order around on one triangle, then if that doesn't work try swapping the other one.
     
  5. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    992
    Both seem to work as intended. I'll try to be more explicit about my issue.

    Forget about the whole digging thing, I did it only to illustrate what I meant by depth.

    Here's how it should look and does look when I don't add the depth.
    Screen Shot 2019-10-16 at 2.36.51 PM.png
    Here's how it looks when I add the code above. You see how the chunks become super obvious?
    Screen Shot 2019-10-16 at 2.37.34 PM.png

    I'm reusing the same triangle indices that were used to render the surface.
     
  6. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    Oh, the white part is the mesh you're working on! OK, that helps.

    Seems like it's adding triangles to each grid square, which suggests it must be a problem with the part of the algorithm that find the "edges", right? Like it's returning every square and not just the ones on the edge.
     
  7. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    992
    Oh, I see the confusion, my bad.
    Yeah, for the time being, chunks don't communicate with each other, they're all responsible for finding and drawing their own edges, independently from other chunks.

    That means the expected result, when the player hasn't interacted with anything yet, should be that all the chunks have the shape of a cube.

    Now if you take 2 cubes and put them next to each other, like actual unity primitive cubes, the surface will look flat. Shouldn't it look exactly like this? A flat surface? Am I being clear?


    EDIT : Here's the algo finding the edges, just in case. Basically iterates through all the edges and if an edge is used twice, it's discarded.
    Code (CSharp):
    1. private HashSet<Vector2Int> GetBoundaryEdges(List<int> triangles)
    2. {
    3.     HashSet<Vector2Int> boundaryEdges = new HashSet<Vector2Int>();
    4.     HashSet<Vector2Int> rejectedEdges = new HashSet<Vector2Int>();
    5.  
    6.     for (int i = 0; i < triangles.Count; i += 3)
    7.     {
    8.         HashSet<Vector2Int> edges = new HashSet<Vector2Int>
    9.         {
    10.             new Vector2Int(triangles[i], triangles[i + 1]),
    11.             new Vector2Int(triangles[i + 1], triangles[i + 2]),
    12.             new Vector2Int(triangles[i + 2], triangles[i])
    13.         };
    14.  
    15.         foreach (Vector2Int edge in edges)
    16.         {
    17.             if (boundaryEdges.Contains(edge))
    18.             {
    19.                 boundaryEdges.Remove(edge);
    20.                 rejectedEdges.Add(edge);
    21.             }
    22.             else
    23.             {
    24.                 if (!rejectedEdges.Contains(edge))
    25.                 {
    26.                     boundaryEdges.Add(edge);
    27.                 }
    28.             }
    29.         }
    30.     }
    31.  
    32.     return boundaryEdges;
    33. }
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,773
    sssssssometimes. Sometimes lighting and shadows do weird things with meshes in that kind of edge case. Small things like the order of polygons in the mesh can actually matter, annoyingly.

    Ideally, if the sides won't be visible, they shouldn't be generated at all, and if you're getting this kind of artifact I think that has to be the solution, it's not just an optimization. If I understand the algorithm correctly, I think you should be able to just ignore all 4 main sides without impacting the appearance? In other words, for each of the edges you're generating, if both of the vertices' X or both of their Y are on the same edge, just don't AddRange?
     
  9. ADNCG

    ADNCG

    Joined:
    Jun 9, 2014
    Posts:
    992
    Ok! Turns out you are right and none of the edges were getting rejected. I updated the method to this(below) and now the boundaries are properly selected. Still running into the lighting issue, although the mesh is much cleaner.
    Code (CSharp):
    1. private HashSet<Vector2Int> GetBoundaryEdges(List<int> triangles)
    2. {
    3.     HashSet<Vector2Int> boundaryEdges = new HashSet<Vector2Int>();
    4.     HashSet<Vector2Int> rejectedEdges = new HashSet<Vector2Int>();
    5.  
    6.     for (int i = 0; i < triangles.Count - 2; i += 3)
    7.     {
    8.         HashSet<Vector2Int> edges = new HashSet<Vector2Int>
    9.         {
    10.             new Vector2Int(triangles[i], triangles[i + 1]),
    11.             new Vector2Int(triangles[i + 1], triangles[i + 2]),
    12.             new Vector2Int(triangles[i + 2], triangles[i])
    13.         };
    14.  
    15.         foreach (Vector2Int edge in edges)
    16.         {
    17.             Vector2Int invertedEdge = new Vector2Int(edge.y, edge.x);
    18.  
    19.             if (boundaryEdges.Contains(edge))
    20.             {
    21.                 boundaryEdges.Remove(edge);
    22.                 rejectedEdges.Add(edge);
    23.             }
    24.             else if (boundaryEdges.Contains(invertedEdge))
    25.             {
    26.                 boundaryEdges.Remove(invertedEdge);
    27.                 rejectedEdges.Add(edge);
    28.             }
    29.             else
    30.             {
    31.                 if (!rejectedEdges.Contains(edge) || !rejectedEdges.Contains(invertedEdge))
    32.                 {
    33.                     boundaryEdges.Add(edge);
    34.                 }
    35.             }
    36.         }
    37.     }
    38.     return boundaryEdges;
    39. }

    If I understand you right, then that won't work, the sides need to be generated. My camera is in perspective and the mesh needs that 3D feel.

    I'm getting an error when trying to upload more screenshots, I'm guessing there's a cap per thread.

    Here's one chunk, pulled out, in wireframe mode
    https://ibb.co/ZxfmN1v
    The same chunk in shaded mode
    https://ibb.co/bHd88Ld
    Here's a screenshot from behind the mesh in shaded wireframe when dug in a bit. Working exactly as you'd expect.
    https://ibb.co/k6Szxtr

    Anyway, you helped me isolate the problem a great deal, thank you very much. I'll update this as soon as I find a fix.

    EDIT1: Ok, after some digging, turns out my problem most likely lies in the fact that I'm using shared vertices for adjacent faces. Apparently that causes normals to be smoothed across them. I'll re-edit to confirm once I modified my code.

    EDIT2: Edited my code and that was exactly it. Thanks for helping me with the mesh optimization and also for pointing out it was a lighting issue.

    Here's how I modified the code that generates the sides, duplicating the vertices instead of reusing them to avoid normals being smoothed.
    Code (CSharp):
    1. HashSet<Vector2Int> boundaryTriangles = GetBoundaryEdges(triangles);
    2.      
    3. foreach(Vector2Int edge in boundaryTriangles)
    4. {
    5.     vertices.Add(vertices[edge.x]);
    6.     vertices.Add(vertices[edge.y]);
    7.     vertices.Add(vertices[edge.x] + new Vector3(0f, 0f, chunkThickness));
    8.     vertices.Add(vertices[edge.y] + new Vector3(0f, 0f, chunkThickness));
    9.     triangles.AddRange(new List<int>
    10.     {
    11.         vertices.Count - 4, vertices.Count - 2, vertices.Count - 1,
    12.         vertices.Count - 1, vertices.Count - 3, vertices.Count - 4
    13.     });
    14. }
     
    Last edited: Oct 16, 2019