0
\$\begingroup\$

I'm creating my own terrain terrain system for Unity using marching cubes but I've run into a problem I'm stumped on. First I'll explain how it works:

A compute shader creates an array of points placed in a 3D grid of sorts based on a given individual cube size, number of cubes per axis, and a number of chunks per axis in the terrain (right now it's working with only one chunk for simplicity). Each vertex also has a value property between 0 and 1, this is for the mesh generation next step and is set manually with an editor tool. This part works flawlessly.

Next, the mesh is generated one triangle at a time using the marching cube algorithm outlined here, and based a little on the project that's linked in the description here.

The code for the compute shader looks like this:

#pragma kernel CSMain
#include "MarchingTable.compute"

struct Vertex
{
    float3 position;
    float value;
};

struct Triangle
{
    float3 vertexC;
    float3 vertexB;
    float3 vertexA;
};

float surfaceLevel;
float3 chunkSize;

RWStructuredBuffer<Vertex> totalVertices;
AppendStructuredBuffer<Triangle> tBuffer;

float3 LerpVertex(float3 pointA, float3 pointB, float valueA, float valueB)
{
    float t = (surfaceLevel - valueA) / (valueB - valueA);
    return pointA + t * (pointB - pointA);
}

[numthreads(8, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID, uint3 threadID: SV_DispatchThreadID)
{
  if (id.x >= chunkSize.y)
      return;

  uint chunkPower = (uint) ((chunkSize.x + 1) * (chunkSize.z + 1));
    
  uint componentY = threadID.x * (((uint) chunkSize.x + 1) * ((uint) chunkSize.z + 1));
    
  for (uint i = 0; i < chunkSize.x * chunkSize.z; i++)
  {   
      uint componentX = round(i % (uint) chunkSize.x);
      uint componentZ = round((i / (uint) chunkSize.x) * ((uint) chunkSize.x + 1));

      uint startPoint = componentX + componentZ + componentY;

      uint corners[8];

      corners[0] = startPoint;
      corners[1] = startPoint + 1;
      corners[2] = (uint) (startPoint + chunkSize.x + 2);
      corners[3] = (uint) (startPoint + chunkSize.x + 1);

      corners[4] = startPoint + chunkPower;
      corners[5] = startPoint + chunkPower + 1;
      corners[6] = (uint) (startPoint + chunkPower + chunkSize.x + 2);
      corners[7] = (uint) (startPoint + chunkPower + chunkSize.x + 1);

      int cubeIndex = 0;

      for (uint j = 0; j < 8; j++)
      {
          if (totalVertices[corners[j]].value < surfaceLevel)
              cubeIndex |= 1 << j;
      }
        
      int triangulation[16] = triangles[cubeIndex];
      
      for (uint k = 0; triangulation[k] != -1; k += 3)
      {
          int indexA1 = cornerIndexFromEdge[triangulation[k]][0];
          int indexB1 = cornerIndexFromEdge[triangulation[k]][1];

          int indexA2 = cornerIndexFromEdge[triangulation[k + 1]][0];
          int indexB2 = cornerIndexFromEdge[triangulation[k + 1]][1];
            
          int indexA3 = cornerIndexFromEdge[triangulation[k + 2]][0];
          int indexB3 = cornerIndexFromEdge[triangulation[k + 2]][1];
            
          float valueA1 = totalVertices[corners[indexA1]].value;
          float valueB1 = totalVertices[corners[indexB1]].value;

          float valueA2 = totalVertices[corners[indexA2]].value;
          float valueB2 = totalVertices[corners[indexB2]].value;
          float valueA3 = totalVertices[corners[indexA3]].value;
          float valueB3 = totalVertices[corners[indexB3]].value;
            
          Triangle tri;
            
          tri.vertexA = LerpVertex(totalVertices[corners[indexA1]].position, totalVertices[corners[indexB1]].position, valueA1, valueB1);
          tri.vertexB = LerpVertex(totalVertices[corners[indexA2]].position, totalVertices[corners[indexB2]].position, valueA2, valueB2);
          tri.vertexC = LerpVertex(totalVertices[corners[indexA3]].position, totalVertices[corners[indexB3]].position, valueA3, valueB3);
            
       tBuffer.Append(tri);
      }
   }
}

The C# code that dispatches it looks like this

void ConstructCube(ref TerrainDataObject.Chunk chunk, uint cIndex, uint chunkIndex)
{
    byte totalSize = sizeof(float) * 3 + sizeof(float);

    ComputeBuffer vertexBuffer = new ComputeBuffer(dataObject.vertices.Length, totalSize);
    vertexBuffer.SetData(dataObject.vertices);

    ComputeBuffer tBuffer = new ComputeBuffer((chunkSize.x * chunkSize.y * chunkSize.z) * 5, sizeof(float) * 3 * 3, ComputeBufferType.Append);
    ComputeBuffer tCountBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.Raw);

    vertexBuffer.SetCounterValue(0);
    meshGenerationShader.SetBuffer(0, "totalVertices", vertexBuffer);
    meshGenerationShader.SetBuffer(0, "tBuffer", tBuffer);
    meshGenerationShader.SetFloats("chunkSize", new float[3] { chunkSize.x, chunkSize.y, chunkSize.z });
    meshGenerationShader.SetFloat("surfaceLevel", surfaceLevel);

    meshGenerationShader.Dispatch(0, Mathf.CeilToInt(chunkSize.y/8f), 1, 1);

    ComputeBuffer.CopyCount(tBuffer, tCountBuffer, 0);
    int[] tCountArray = { 0 };
    tCountBuffer.GetData(tCountArray);
    int numTri = tCountArray[0];

    // Get tri data from shader
    Triangle[] tris = new Triangle[numTri];
    tBuffer.GetData(tris, 0, 0, tris.Length);

    vertexBuffer.Release();

    tBuffer.Release();
    tCountBuffer.Release();

    Vector3[] verts = new Vector3[numTri * 3];
    var meshTriangles = new int[numTri * 3];

    for (int i = 0; i < numTri; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            meshTriangles[i * 3 + j] = i * 3 + j;
            verts[i * 3 + j] = tris[i][j];
        }
    }

    chunk.vertices = verts;
    chunk.triangles = meshTriangles;
}

At first it appeared to work pretty well

But then at larger amounts of cubes, this happens

The triangles at the front of the chunk are not being generated and the triangles at the back are... well I'm not sure what they're doing.

Anyone know how to fix this? I figure it has something to do with the threads or thread groups but I'm not sure how. I'm also pretty new to compute shaders in general if that helps.

\$\endgroup\$
2
  • \$\begingroup\$ By any chance, are the misbehaving triangles ones whose vertex coordinates are negative numbers in one axis or another? This may suggest a winding problem, where your code is emitting vertices in the wrong order, creating faces that point in the direction opposite their normal vector. \$\endgroup\$
    – DMGregory
    Commented Jul 15, 2021 at 1:44
  • \$\begingroup\$ I debugged it again while "sculpting" and the vertices appear to be all where the should be, no negatives or anything like that. This is the from the very end of the (massive) amount of points, where the triangles first start disappearing: i.imgur.com/ZdAykWQ.png Checking the triangle index array gets the same results, they're all in order and everything: i.imgur.com/4LWMsoc.png \$\endgroup\$
    – Leo
    Commented Jul 15, 2021 at 14:33

1 Answer 1

2
\$\begingroup\$

By default Unity handles only 65535 vertices in the mesh. You need to set

Mesh.indexFormat = IndexFormat.UInt32 

to handle more vertices.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .