Unity Marching Cubes creates odd triangles instead of mesh

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:

//converts a 3d coordinate to a single int for a 1d array of floats
int indexFromCoord(int x, int y, int z)
{
    return x + (main.chunkSize+1) * (y + (main.chunkSize+1) * z);
}
//set values and execute compute shader, then begin mesh creation
    Mesh ConstructMesh(int chunkID) {
        MarchingShader.SetBuffer(0, "_Triangles", _trianglesBuffer);
        MarchingShader.SetBuffer(0, "_Weights", _weightsBuffer);
         MarchingShader.SetBuffer(0, "_Materials", _materialBuffer);

        MarchingShader.SetInt("_chunkSize", 16);
        MarchingShader.SetFloat("_IsoLevel", .5f);
        MarchingShader.SetInt("_LOD", GridMetrics.PointsPerChunk(LOD));
        MarchingShader.SetInt("_LODID", LOD);
        _weightsBuffer.SetData(main.worldData[chunkID].chunkData);
        _materialBuffer.SetData(main.worldData[chunkID].materials);
        _trianglesBuffer.SetCounterValue(0);


        MarchingShader.Dispatch(0, GridMetrics.ThreadGroups(LOD), GridMetrics.ThreadGroups(LOD),      GridMetrics.ThreadGroups(LOD));
        Triangle[] triangles = new Triangle[ReadTriangleCount()];
        _trianglesBuffer.GetData(triangles);
        return CreateMeshFromTriangles(triangles);
    }
//gets count of triangles
    int ReadTriangleCount() {
        int[] triCount = { 0 };
        ComputeBuffer.CopyCount(_trianglesBuffer, _trianglesCountBuffer, 0);
        _trianglesCountBuffer.GetData(triCount);
        return triCount[0];
    }
//creates mesh from provided info
    Mesh CreateMeshFromTriangles(Triangle[] triangles) {
     List<Vector3> verts = new List<Vector3>();
    List<List<int>> submeshTriangles = new List<List<int>>();
        for (int submeshIndex = 0; submeshIndex < 5; submeshIndex++)
    {
            submeshTriangles.Add(new List<int>());
    }

        for (int i = 0; i < triangles.Length; i++) {
            int startIndex = i * 3;

            verts.Add(triangles[i].a);
            verts.Add(triangles[i].b);
            verts.Add(triangles[i].c);
            submeshTriangles[triangles[i].materialIndex].Add(startIndex);
            submeshTriangles[triangles[i].materialIndex].Add(startIndex + 1);
            submeshTriangles[triangles[i].materialIndex].Add(startIndex + 2);
        }
                Mesh mesh = new Mesh();

        mesh.vertices = verts.ToArray();
        mesh.subMeshCount = 5;
                        for(int i = 0; i < submeshTriangles.Count; i++)
        {
            mesh.SetTriangles(submeshTriangles[i].ToArray(), i);

            //submeshTriangles[triangles[i].materialIndex].Clear();
        }
        mesh.RecalculateNormals();
        return mesh;
    }

Compute Shader (couldn’t find hlsl option in code):
#pragma kernel March

#include “Includes\MarchingTable.hlsl”
#include “Includes\MetricsCompute.compute”

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

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

AppendStructuredBuffer _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);
}
}

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.

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)

I figured it out. The indexfromcoord function being access from the compute shader had the wrong chunk size. Now everything is working.

1 Like