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.