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

Resolved Unity Marching Cubes creates odd triangles instead of mesh

Discussion in 'Scripting' started by Fluffysnails46, Aug 2, 2023.

  1. Fluffysnails46

    Fluffysnails46

    Joined:
    Jan 15, 2021
    Posts:
    52
    Recently I was moving code to different scripts in my project, and it caused weird triangles to generate. The world was procedurally generated, and for some reason it was the same triangles in the same position everytime, despite the seed. I used gizmos to draw the weights that are sent into a compute shader and it was working properly. I tried to see if the new changes had caused something to execute later than expected, but that was also working just fine. I checked if the creation of the mesh from the values returned from the compute shader caused it, but the values were as expected. I know the chunks of code where the issue is in, but not what is going wrong. Here is the code where it is going wrong:
    C# side:
    Code (CSharp):
    1.  
    2. //converts a 3d coordinate to a single int for a 1d array of floats
    3. int indexFromCoord(int x, int y, int z)
    4. {
    5.     return x + (main.chunkSize+1) * (y + (main.chunkSize+1) * z);
    6. }
    7. //set values and execute compute shader, then begin mesh creation
    8.     Mesh ConstructMesh(int chunkID) {
    9.         MarchingShader.SetBuffer(0, "_Triangles", _trianglesBuffer);
    10.         MarchingShader.SetBuffer(0, "_Weights", _weightsBuffer);
    11.          MarchingShader.SetBuffer(0, "_Materials", _materialBuffer);
    12.  
    13.         MarchingShader.SetInt("_chunkSize", 16);
    14.         MarchingShader.SetFloat("_IsoLevel", .5f);
    15.         MarchingShader.SetInt("_LOD", GridMetrics.PointsPerChunk(LOD));
    16.         MarchingShader.SetInt("_LODID", LOD);
    17.         _weightsBuffer.SetData(main.worldData[chunkID].chunkData);
    18.         _materialBuffer.SetData(main.worldData[chunkID].materials);
    19.         _trianglesBuffer.SetCounterValue(0);
    20.  
    21.  
    22.         MarchingShader.Dispatch(0, GridMetrics.ThreadGroups(LOD), GridMetrics.ThreadGroups(LOD),      GridMetrics.ThreadGroups(LOD));
    23.         Triangle[] triangles = new Triangle[ReadTriangleCount()];
    24.         _trianglesBuffer.GetData(triangles);
    25.         return CreateMeshFromTriangles(triangles);
    26.     }
    27. //gets count of triangles
    28.     int ReadTriangleCount() {
    29.         int[] triCount = { 0 };
    30.         ComputeBuffer.CopyCount(_trianglesBuffer, _trianglesCountBuffer, 0);
    31.         _trianglesCountBuffer.GetData(triCount);
    32.         return triCount[0];
    33.     }
    34. //creates mesh from provided info
    35.     Mesh CreateMeshFromTriangles(Triangle[] triangles) {
    36.      List<Vector3> verts = new List<Vector3>();
    37.     List<List<int>> submeshTriangles = new List<List<int>>();
    38.         for (int submeshIndex = 0; submeshIndex < 5; submeshIndex++)
    39.     {
    40.             submeshTriangles.Add(new List<int>());
    41.     }
    42.  
    43.         for (int i = 0; i < triangles.Length; i++) {
    44.             int startIndex = i * 3;
    45.  
    46.             verts.Add(triangles[i].a);
    47.             verts.Add(triangles[i].b);
    48.             verts.Add(triangles[i].c);
    49.             submeshTriangles[triangles[i].materialIndex].Add(startIndex);
    50.             submeshTriangles[triangles[i].materialIndex].Add(startIndex + 1);
    51.             submeshTriangles[triangles[i].materialIndex].Add(startIndex + 2);
    52.         }
    53.                 Mesh mesh = new Mesh();
    54.  
    55.         mesh.vertices = verts.ToArray();
    56.         mesh.subMeshCount = 5;
    57.                         for(int i = 0; i < submeshTriangles.Count; i++)
    58.         {
    59.             mesh.SetTriangles(submeshTriangles[i].ToArray(), i);
    60.  
    61.             //submeshTriangles[triangles[i].materialIndex].Clear();
    62.         }
    63.         mesh.RecalculateNormals();
    64.         return mesh;
    65.     }
    Compute Shader (couldn't find hlsl option in code):
    #pragma kernel March

    #include "Includes\MarchingTable.hlsl"
    #include "Includes\MetricsCompute.compute"

    RWStructuredBuffer<float> _Weights;
    float _IsoLevel;
    int _LOD;
    int _LODID;
    RWStructuredBuffer<int> _Materials;

    struct Triangle {
    float3 a, b, c;
    int materialIndex;
    };

    AppendStructuredBuffer<Triangle> _Triangles;

    float3 interp(float3 edgeVertex1, float valueAtVertex1, float3 edgeVertex2, float valueAtVertex2)
    {
    return (edgeVertex1 + (_IsoLevel - valueAtVertex1) * (edgeVertex2 - edgeVertex1) / (valueAtVertex2 - valueAtVertex1));
    }

    [numthreads(numThreads, numThreads, numThreads)]
    void March(uint3 id : SV_DispatchThreadID)
    {
    if (id.x >= _LOD || id.y >= _LOD || id.z >= _LOD)
    {
    return;
    }
    int scale = (5 - _LODID);
    float3 samplePos = id * scale;
    float cubeValues[8] = {
    _Weights[indexFromCoord(samplePos.x, samplePos.y, samplePos.z + scale)],
    _Weights[indexFromCoord(samplePos.x + scale, samplePos.y, samplePos.z + scale)],
    _Weights[indexFromCoord(samplePos.x + scale, samplePos.y, samplePos.z)],
    _Weights[indexFromCoord(samplePos.x, samplePos.y, samplePos.z)],
    _Weights[indexFromCoord(samplePos.x, samplePos.y + scale, samplePos.z + scale)],
    _Weights[indexFromCoord(samplePos.x + scale, samplePos.y + scale, samplePos.z + scale)],
    _Weights[indexFromCoord(samplePos.x + scale, samplePos.y + scale, samplePos.z)],
    _Weights[indexFromCoord(samplePos.x, samplePos.y + scale, samplePos.z)]
    };

    int cubeIndex = 0;
    if (cubeValues[0] < _IsoLevel) cubeIndex |= 1;
    if (cubeValues[1] < _IsoLevel) cubeIndex |= 2;
    if (cubeValues[2] < _IsoLevel) cubeIndex |= 4;
    if (cubeValues[3] < _IsoLevel) cubeIndex |= 8;
    if (cubeValues[4] < _IsoLevel) cubeIndex |= 16;
    if (cubeValues[5] < _IsoLevel) cubeIndex |= 32;
    if (cubeValues[6] < _IsoLevel) cubeIndex |= 64;
    if (cubeValues[7] < _IsoLevel) cubeIndex |= 128;

    int edges[] = triTable[cubeIndex];

    for (int i = 0; edges != -1; i += 3)
    {
    // First edge lies between vertex e00 and vertex e01
    int e00 = edgeConnections[edges][0];
    int e01 = edgeConnections[edges][1];

    // Second edge lies between vertex e10 and vertex e11
    int e10 = edgeConnections[edges[i + 1]][0];
    int e11 = edgeConnections[edges[i + 1]][1];

    // Third edge lies between vertex e20 and vertex e21
    int e20 = edgeConnections[edges[i + 2]][0];
    int e21 = edgeConnections[edges[i + 2]][1];

    Triangle tri;
    tri.a = (interp(cornerOffsets[e00], cubeValues[e00], cornerOffsets[e01], cubeValues[e01]) + id) * scale;
    tri.b = (interp(cornerOffsets[e10], cubeValues[e10], cornerOffsets[e11], cubeValues[e11]) + id) * scale;
    tri.c = (interp(cornerOffsets[e20], cubeValues[e20], cornerOffsets[e21], cubeValues[e21]) + id) * scale;
    tri.materialIndex = _Materials[indexFromCoord(id.x, id.y, id.z)];
    _Triangles.Append(tri);
    }
    }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,947
    Big datasets like this are going to be hard to debug.

    My approach is always to make the tiniest possible dataset that still shows the problem.

    In the case of marching cubes that might be a single voxel of dirt with nothing else around it.

    If that doesn't show the problem, perhaps two side by side? Or one top/bottom? etc.

    Having a small dataset lets you single-step through the entire process with the debugger until you find where things went wrong.

    Obviously with two separate steps in the data cascade (this compute shader and your geometry builder), you would test each stage separately to validate the data.

    Testing each stage separately might involve hand-mocking data from one step to feed to the next, just to get reliable testable input data.
     
  3. Fluffysnails46

    Fluffysnails46

    Joined:
    Jan 15, 2021
    Posts:
    52
    I made some code to generate different values to test, and before I tried anything small, I thought it might be interesting to give it random densities, and it didn't generate anything. I then gave it values that would make any coordinate with the value of 8 in it have the density of 0.5, which caused weird diagonal hexagons and triangles to generate. I then restricted it slightly more by making it so the y value also had to be 8. When testing this, I got the same results, and I then made it so (8,8,8) in each chunk would be 0.5 and everything else would be 0, and it still generated the diagonal hexagons. When this was done I thought maybe it has to do with creating the mesh from the triangles, which after giving triangles for a cube, it correctly created a cube. I think there might be something wrong in the compute shader, but when moving things around I never messed with the compute shader.

    (I have included a image of the mess of triangles)
     

    Attached Files:

  4. Fluffysnails46

    Fluffysnails46

    Joined:
    Jan 15, 2021
    Posts:
    52
    I figured it out. The indexfromcoord function being access from the compute shader had the wrong chunk size. Now everything is working.
     
    Kurt-Dekker likes this.