I’ve been trying to implement a compute shader from Sebastian Lague’s marching cubes video into my own marching cubes project; however, unity is giving me cryptic errors from his code which shouldn’t make any sense since it worked totally fine for him. What is wrong with my project?
Here’s my chunk loading script:
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System.Threading.Tasks;
public class Chunk : MonoBehaviour
{
const int threadGroupSize = 8;
public Material material;
public Vector3Int chunkPos;
public ComputeShader shader;
private float[][][] blocks;
public float isoLevel = .5f;
public static Dictionary<Vector3Int, Chunk> chunks = new Dictionary<Vector3Int, Chunk>();
public static Vector3Int size = new Vector3Int(16, 16, 16);
Mesh mesh;
public async void generate()
{
Vector3Int flPos = Vector3Int.RoundToInt(transform.position);
chunkPos = Vector3Int.RoundToInt(new Vector3(flPos.x / size.x, flPos.y / size.y, flPos.z / size.z));
if (!GetComponent<MeshFilter>())
gameObject.AddComponent<MeshFilter>();
if (!GetComponent<MeshRenderer>())
gameObject.AddComponent<MeshRenderer>();
mesh = new Mesh();
await Task.Run(() => generateBlockArray());
generateMesh();
mesh.vertices = minimizedVerts.ToArray();
mesh.triangles = adjustedTris.ToArray();
mesh.RecalculateNormals();
mesh.RecalculateBounds();
try {
GetComponent<MeshFilter>().mesh = mesh;
GetComponent<MeshRenderer>().material = material;
}
catch { }
chunks[chunkPos] = this;
}
ComputeBuffer triangleBuffer;
ComputeBuffer pointsBuffer;
ComputeBuffer triCountBuffer;
List<int> adjustedTris;
List<Vector3> minimizedVerts;
void generateMesh()
{
int numVoxelsPerAxis = size.x;
int numThreadsPerAxis = Mathf.CeilToInt (numVoxelsPerAxis / (float) threadGroupSize);
int numPoints = (size.x+1)*(size.y+1)*(size.z+1);
int numVoxels = numVoxelsPerAxis * numVoxelsPerAxis * numVoxelsPerAxis;
int maxTriangleCount = numVoxels * 5;
triangleBuffer = new ComputeBuffer(maxTriangleCount, sizeof(float) * 3 * 3, ComputeBufferType.Append);
pointsBuffer = new ComputeBuffer(numPoints, sizeof(float) * 4);
triCountBuffer = new ComputeBuffer(1, sizeof(int), ComputeBufferType.Raw);
Vector4[] flattenedArray = new Vector4[numPoints];
int index = 0;
for (int x = 0; x < size.x; x++)
{
for (int y = 0; y < size.y; y++)
{
for (int z = 0; z < size.z; z++)
{
flattenedArray[index] = new Vector4(x, y, z, blocks[x][y][z]);
index++;
}
}
}
pointsBuffer.SetData(flattenedArray);
triangleBuffer.SetCounterValue(0);
shader.SetBuffer(0, "points", pointsBuffer);
shader.SetBuffer(0, "triangles", triangleBuffer);
shader.SetInt("numPointsPerAxis", size.x+1);
shader.SetFloat("isoLevel", isoLevel);
shader.Dispatch(0, numThreadsPerAxis, numThreadsPerAxis, numThreadsPerAxis);
// Get number of triangles in the triangle buffer
ComputeBuffer.CopyCount (triangleBuffer, triCountBuffer, 0);
int[] triCountArray = { 0 };
triCountBuffer.GetData (triCountArray);
int numTris = triCountArray[0];
// Get triangle data from shader
Triangle[] tris = new Triangle[numTris];
triangleBuffer.GetData (tris, 0, 0, numTris);
mesh = new Mesh();
var vertices = new Vector3[numTris * 3];
var meshTriangles = new int[numTris * 3];
for (int i = 0; i < numTris; i++) {
for (int j = 0; j < 3; j++) {
meshTriangles[i * 3 + j] = i * 3 + j;
vertices[i * 3 + j] = tris[i][j];
}
}
minimizedVerts = new List<Vector3>();
Dictionary<int, int> triPointers = new Dictionary<int, int>();
for (int i = 0; i < vertices.Length; i++) {
if (!minimizedVerts.Contains(vertices[i])) {
minimizedVerts.Add(vertices[i]);
triPointers[i] = minimizedVerts.Count - 1;
}
else
{
triPointers[i] = minimizedVerts.IndexOf(vertices[i]);
}
}
adjustedTris = new List<int>();
for (int i = 0; i < meshTriangles.Length; i++)
{
adjustedTris.Add(triPointers[meshTriangles[i]]);
}
}
struct Triangle
{
#pragma warning disable 649 // disable unassigned variable warning
public Vector3 a;
public Vector3 b;
public Vector3 c;
public Vector3 this[int i]
{
get
{
switch (i)
{
case 0:
return a;
case 1:
return b;
default:
return c;
}
}
}
}
Vector3 interpolateVerts(Vector4 v1, Vector4 v2)
{
float t = (isoLevel-v1.w) / (v2.w - v1.w);
return v1 + t * (v2 - v1);
}
float getBlock(Vector3 pos)
{
if (pos.x < 0 || pos.y < 0 || pos.z < 0 || pos.x > size.z || pos.y > size.y || pos.z > size.z)
{
Vector3Int nPos = chunkPos + Vector3Int.FloorToInt(new Vector3(pos.x/(float)size.x, pos.y/(float)size.y, pos.z/(float)size.z));
if (chunks.ContainsKey(nPos))
{
return chunks[nPos].getBlock(new Vector3Int((int)(pos.x+size.x)%size.x, (int)(pos.y+size.y)%size.y, (int)(pos.z + size.z)%size.z));
}
else
{
return -1;
}
}
return blocks[(int)pos.x][(int)pos.y][(int)pos.z];
}
async Task generateBlockArray()
{
blocks = new float[(int)size.x+1][][];
for (int x = 0; x < size.x+1; x++)
{
blocks[x] = new float[(int)size.y+1][];
for (int y = 0; y < size.y+1; y++)
{
blocks[x][y] = new float[(int)size.z+1];
for (int z = 0; z < size.z+1; z++)
{
blocks[x][y][z] = generateBlock(new Vector3(x+chunkPos.x*size.x, y+chunkPos.y*size.y, z+chunkPos.z*size.z));
}
}
}
}
float generateBlock(Vector3 pos)
{
float height = getHeight(pos.x, pos.z);
return height - pos.y;
}
float smoothMax(float x, float y, float s)
{
return (x + y + Mathf.Sqrt(Mathf.Pow(x - y, 2) + s)) / 2;
}
float getHeight(float x, float y)
{
x += 1000;
y += 234000;
float height = 0;
height += Mathf.PerlinNoise(x * .005f, y * .005f) * 20;
float mountainBase = 40;
float accumulationHeight = 10;
float mountains = 0;
mountains += Mathf.PerlinNoise(x * .01f, y * .01f) * 20;
mountains += (1-Mathf.Abs(Mathf.PerlinNoise(x * .005f, y * .005f)-.5f)*2) * 60;
mountains += (1 - Mathf.Abs(Mathf.PerlinNoise(x * .05f, y * .05f) - .5f) * 2) * 8;
mountains = smoothMax(mountains, mountainBase, 20) - mountainBase;
if (mountains > accumulationHeight)
mountains = accumulationHeight + Mathf.Pow((mountains - accumulationHeight), 2)/15;
height += mountains;
height += Mathf.PerlinNoise(x * .1f, y * .1f) * 3;
return height;
}
}
[CustomEditor(typeof(Chunk))]
public class GenerateChunkEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
Chunk chunk = (Chunk)target;
if (GUILayout.Button("Generate Chunk"))
{
Debug.Log("Generated Chunk");
chunk.generate();
}
}
}
Here’s Sebastian Lague’s compute shader that I’m using:
#pragma kernel March
#include "MarchTables.compute"
static const int numThreads = 8;
struct Triangle
{
float3 vertexC;
float3 vertexB;
float3 vertexA;
};
AppendStructuredBuffer<Triangle> triangles;
RWStructuredBuffer<float4> points;
int numPointsPerAxis;
float isoLevel;
float3 interpolateVerts(float4 v1, float4 v2)
{
float t = (isoLevel - v1.w) / (v2.w - v1.w);
return v1.xyz + t * (v2.xyz - v1.xyz);
}
int indexFromCoord(int x, int y, int z)
{
return z * numPointsPerAxis * numPointsPerAxis + y * numPointsPerAxis + x;
}
[numthreads(numThreads, numThreads, numThreads)]
void March(int3 id : SV_DispatchThreadID)
{
// Stop one point before the end because voxel includes neighbouring points
if (id.x >= numPointsPerAxis - 1 || id.y >= numPointsPerAxis - 1 || id.z >= numPointsPerAxis - 1)
{
return;
}
// 8 corners of the current cube
float4 cubeCorners[8] =
{
points[indexFromCoord(id.x, id.y, id.z)],
points[indexFromCoord(id.x + 1, id.y, id.z)],
points[indexFromCoord(id.x + 1, id.y, id.z + 1)],
points[indexFromCoord(id.x, id.y, id.z + 1)],
points[indexFromCoord(id.x, id.y + 1, id.z)],
points[indexFromCoord(id.x + 1, id.y + 1, id.z)],
points[indexFromCoord(id.x + 1, id.y + 1, id.z + 1)],
points[indexFromCoord(id.x, id.y + 1, id.z + 1)]
};
// Calculate unique index for each cube configuration.
// There are 256 possible values
// A value of 0 means cube is entirely inside surface; 255 entirely outside.
// The value is used to look up the edge table, which indicates which edges of the cube are cut by the isosurface.
int cubeIndex = 0;
if (cubeCorners[0].w < isoLevel)
cubeIndex |= 1;
if (cubeCorners[1].w < isoLevel)
cubeIndex |= 2;
if (cubeCorners[2].w < isoLevel)
cubeIndex |= 4;
if (cubeCorners[3].w < isoLevel)
cubeIndex |= 8;
if (cubeCorners[4].w < isoLevel)
cubeIndex |= 16;
if (cubeCorners[5].w < isoLevel)
cubeIndex |= 32;
if (cubeCorners[6].w < isoLevel)
cubeIndex |= 64;
if (cubeCorners[7].w < isoLevel)
cubeIndex |= 128;
// Create triangles for current cube configuration
for (int i = 0; triangulation[cubeIndex][i] != -1; i += 3)
{
// Get indices of corner points A and B for each of the three edges
// of the cube that need to be joined to form the triangle.
int a0 = cornerIndexAFromEdge[triangulation[cubeIndex][i]];
int b0 = cornerIndexBFromEdge[triangulation[cubeIndex][i]];
int a1 = cornerIndexAFromEdge[triangulation[cubeIndex][i + 1]];
int b1 = cornerIndexBFromEdge[triangulation[cubeIndex][i + 1]];
int a2 = cornerIndexAFromEdge[triangulation[cubeIndex][i + 2]];
int b2 = cornerIndexBFromEdge[triangulation[cubeIndex][i + 2]];
Triangle tri;
tri.vertexA = interpolateVerts(cubeCorners[a0], cubeCorners[b0]);
tri.vertexB = interpolateVerts(cubeCorners[a1], cubeCorners[b1]);
tri.vertexC = interpolateVerts(cubeCorners[a2], cubeCorners[b2]);
triangles.Append(tri);
}
}