Search code examples
unity-game-enginemeshhlslprocedural-generationcompute-shader

Generating a flat mesh that shares vertices using compute shaders


I have what seemed to be a simple problem that has now resulted in noise complaints from the neighbours over my screams of frustration.

TL;DR Procedural meshes are normally make using strips of quads. I'm instead trying to make a mesh as one piece, reusing edge vertices, instead of lining up the quad strips as if it was one mesh.

I'm testing something so maybe this is a wierd way to do it, but it should work.

Shader 1:

RWStructuredBuffer<float3> vertexBuffer;
uniform uint yColumnHeight; 

[numthreads(8,8,1)]
void calcVerts (uint3 id : SV_DispatchThreadID)
{
    //convert x and y to 1 dimensional counter
    int idx = (id.y + (yColumnHeight * id.x));

    //create a flat array of vertices
    float3 vA = float3(id.x, 1, id.y); 
    vertexBuffer[idx] = vA;
}

Shader 2:

RWStructuredBuffer<float3> vertexBuffer;
RWStructuredBuffer<float3> triangleBuffer;
uniform uint yColumnHeight; 

[numthreads(8,8,1)]
void createMeshFromVerts (uint3 id : SV_DispatchThreadID)
{

    int idx = (id.y + (yColumnHeight * id.x));
    
    if (id.x > 0 && id.y > 0){

        //convert idx to index for tri/quad vertices, skipping first row and column
        int subtractFirstYColumn = idx - yColumnHeight;
        int subtractFirstXRow = id.y - 1;
        int trID = (subtractFirstYColumn - subtractFirstXRow) * 6;

        //find the vertices of the quad using verts from first row and column
        int tri_a = idx - yColumnHeight - 1;
        int tri_b = idx - 1;
        int tri_c = idx;
        int tri_d = idx - yColumnHeight;

        triangleBuffer[trID]     = vertexBuffer[tri_a];
        triangleBuffer[trID + 1] = vertexBuffer[tri_b];
        triangleBuffer[trID + 2] = vertexBuffer[tri_c];

        triangleBuffer[trID + 3] = vertexBuffer[tri_d];
        triangleBuffer[trID + 4] = vertexBuffer[tri_a];
        triangleBuffer[trID + 5] = vertexBuffer[tri_c];
    }
}

The second shader may initially seem obtuse, but it's quite simple. I'm getting an array of verts:

. . . . 
. . . .
. . . .
. . . .

In the above, that's a 3x3 grid of quads, made of 4x4 verts. I start by getting the vert 1 across and 1 down, and making a quad with the top left corner verts. Each quad starts with vert _ and uses preceeding verts . like this:

. . 
. _

And tied together in the main C#:

        //buffers for vertices and map of vertices to make triangles
        vertexBuffer = new ComputeBuffer(triVertCount, stride, ComputeBufferType.Default);
        triangleBuffer = new ComputeBuffer(tris, stride, ComputeBufferType.Default);

        //create initial vertices grid
        calcVerts.SetBuffer(verts, "vertexBuffer", vertexBuffer);

        calcVerts.Dispatch(verts, Mathf.Max(1, (widthInVertices) / (int)threadsx), Mathf.Max(1, (heightInVertices) / (int)threadsy), (int)z);

        //use vertices grid to make mesh
        createMeshFromVerts.SetBuffer(meshFromVerts, "vertexBuffer", vertexBuffer);
        createMeshFromVerts.SetBuffer(meshFromVerts, "triangleBuffer", triangleBuffer);

        createMeshFromVerts.Dispatch(meshFromVerts, Mathf.Max(1, (widthInVertices) / (int)threadsx), Mathf.Max(1, (heightInVertices) / (int)threadsy), (int)z);

I skipped the code for normals, and where I pass to material to render. When this runs I get scrambled triangles. Can you see where I messed up?


Solution

  • The calculation of trId results in overlapping of some indices, and skipping of other values.

    With a 4x2 grid of vertices (idx shown) and yColumnHeight of 4:

    0 4
     x <- (desired trID 0)
    1 5
     x <- (desired trID 6)
    2 6
     x <- (desired trID 12)
    3 7
    

    The currently calculated trId for id = 1,1 (idx 5) comes out to 6, but it should probably come to 0 so that the first 6 items in the triangleBuffer are set to something useful. In fact, no trId ever equals 0 using the current calculation. Furthermore, the currently calculated trId for id = 1,2 (idx 6) comes out to 6 as well! And so does id=1,3 (idx 7).

    Sadly, this overlap occurs in every column, and most of the triangleBuffer goes unset as a result of this.

    The answer is to change how trId is calculated.

    A simple way is to re-use your method of mapping from 2d to 1d array, only reducing the x and y coordinates by 1 and also reducing the height by one:

    Vertex mapping (current):

    int idx = (id.y + (yColumnHeight * id.x));
    

    Triangle mapping (proposed):

    int trId = ((id.y-1) + ((yColumnHeight-1) * (id.x-1));
    trId *= 6;
    

    or more simply:

    int trId = 6 * (id.y - 1 + (yColumnHeight-1) * (id.x-1));
    

    or, expanding and substituting idx. I find this less clear what's happening but it's more succinct:

    //       = 6 * (id.y - 1 + yColumnHeight * id.x - yColumnHeight - id.x + 1)
    //       = 6 * (id.y + yColumnHeight * id.x - yColumnHeight - id.x)
    int trId = 6 * (idx - yColumnHeight - id.x);