Search code examples
c#unity-game-enginemeshheightmap

calculating reduced level of details of a mesh


I am attempting to create randomised terrain meshes as I have done so in the screenshot below:

Randomly Generated Terrain Mesh

However, the issue I am facing is when attempting to reduces the number of triangles and vertices (Level of Detail).

I understand that to do this I can just skip over vertices. for example: The above mesh is full detail in that the vertices are generated like so:

0->1->2->3->4->5->6->7->8->9->...

and to generate a lower level of detail i can skip vertices as long as the skipping of vertices does not exceed the length of vertices so i could do the following generation to lower detail:

0->2->4->6->8->10->12->14->16->...

or:

0->4->8->12->16->20->24->28->32->...

Using a 2D array and a nested loop makes this trivial as each iteration on 2D coordinates x/y can be incremented by the increment: 1, 2, 4, 8, however, i am dealing with 2D arrays in 1D format.

I have the following code which executes and almost correctly generates the above mesh in the screenshot above. Unfortunately it does seem to be missing one line of of vertices on the top left (3d z axis) as seen below: missing line of vertices

One caveat to the Execute(int, int) method below is that any access to the NativeArray which is not labeled [ReadOnly] will throw an exception if the array is accessing indexes outside of it's batch size.

public struct int6
{
    public int a, b, c, d, e, f;
    public int6(int a, int b, int c, int d, int e, int f) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; }
}

public class MeshGeneratorJob2
{
    [ReadOnly] public static int width = 241;
    [ReadOnly] public static int height = 241;
    [ReadOnly] public static float topLeftX = (width - 1) / -2f;
    [ReadOnly] public static float topLeftZ = (height - 1) / 2f;
    [ReadOnly] public static NativeArray<float> heightMap = new NativeArray<float>(width * height, Allocator.TempJob);
    public static NativeArray<float> heightCurveSamples;
    public static NativeArray<float3> vertices = new NativeArray<float3>(width * height, Allocator.TempJob);
    public static NativeArray<int6> triangles = new NativeArray<int6>((width - 1) * (height - 1), Allocator.TempJob);
    public static NativeArray<float2> uvs = new NativeArray<float2>(width * height, Allocator.TempJob);

    public void Execute()
    {
        for (int i = 0; i < vertices.Length; i += 5)
        {
            Execute(i, 5);
        }
    }

    private void Execute(int startIndex, int count)
    {
        for (int vertexIndex = startIndex; vertexIndex < startIndex + count; vertexIndex++)
        {
            int x = vertexIndex % width;
            int y = vertexIndex / width;

            vertices[vertexIndex] = new float3(topLeftX + x, heightMap[vertexIndex] * 16.67f, topLeftZ - y);
            uvs[vertexIndex] = new float2(x / (float)width, y / (float)height);

            if (vertexIndex < triangles.Length && x < width - 1 && y < height - 1)
            {
                triangles[vertexIndex] = new int6(vertexIndex, vertexIndex + width + 1, vertexIndex + width,
                                                  vertexIndex + width + 1, vertexIndex, vertexIndex + 1);
            }
        }
    }
}

Solution

  • I have come up with the following solution to this problem:

    The first issue i solved was using a nested for loop y, x with y always starting at startIndex.

    this, however, caused an issue as the vertexIndex could be higher than the length of the triangles length, so i calculated the current vertexIndex at the supplied startIndex as follows: Here i introduced an incrementer value which increments both the x and y loops rather than y++, x++ however in this example incrementer is 1 which is essentially the same thing.

     int vertexIndex = (int)(math.ceil((float)width / incrementer) * math.ceil((float)startIndex / incrementer));
    

    however calculating the vertexIndex caused another issue which again caused out of bounds exceptions on setting the vertices. This was due to the startIndex being incremented by count, where count was not the same as the incrementer.

    To solve this I at the start of the method added the following code to round the startIndex up to the next incremental count if needed.

    startIndex += startIndex % incrementer;
    

    and altogether i then get the following code:

    public struct int6
    {
        public int a, b, c, d, e, f;
        public int6(int a, int b, int c, int d, int e, int f) { this.a = a; this.b = b; this.c = c; this.d = d; this.e = e; this.f = f; }
    }
    
    public class MeshGeneratorJob2
    {
        public static int width = 241;
        public static int height = 241;
        public static float topLeftX = (width - 1) / -2f;
        public static float topLeftZ = (height - 1) / 2f;
        public static int increment = 1;
        public static NativeArray<float> heightMap = new NativeArray<float>(width * height, Allocator.TempJob);
        public static NativeArray<float> heightCurveSamples;
        public static NativeArray<float3> vertices = new NativeArray<float3>(width * height, Allocator.TempJob);
        public static NativeArray<int6> triangles = new NativeArray<int6>((width - 1) * (height - 1), Allocator.TempJob);
        public static NativeArray<float2> uvs = new NativeArray<float2>(width * height, Allocator.TempJob);
    
        public void Execute()
        {
            for (int i = 0; i < vertices.Length; i += 5)
            {
                Execute(i, 5);
            }
        }
    
        private void Execute(int startIndex, int count)
        {
            startIndex += startIndex % increment;
            int vertexIndex = (int)(math.ceil((float)width / increment) * math.ceil((float)startIndex / increment));
            for (int y = startIndex; y < startIndex + count && y < height; y++)
            {
                for (int x = 0; x < width; x += increment)
                {
                    vertices[vertexIndex] = new float3(topLeftX + x, heightMap[vertexIndex] * 16.67f, topLeftZ - y);
                    uvs[vertexIndex] = new float2(x / (float)width, y / (float)height);
    
                    if (vertexIndex < triangles.Length && x < width - 1 && y < height - 1)
                    {
                        triangles[vertexIndex] = new int6(vertexIndex, vertexIndex + width + 1, vertexIndex + width,
                                                          vertexIndex + width + 1, vertexIndex, vertexIndex + 1);
                    }
                    vertexIndex++;
                }
            }
        }
    }