Voxel meshing system: Anybody have good optimization tips?

For fun I am trying to make a voxel meshing script that is fast and memory efficient. The current code works well at memory management (no leaks) and is fairly fast but the time needed to generate an entire chunk (16 sub chunks) still is anywhere between 10-20ms.

Sub-Chunk Code

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.Profiling;

[RequireComponent(typeof(MeshFilter)), RequireComponent(typeof(MeshRenderer)), RequireComponent(typeof(MeshCollider))]
public class SubChunk : MonoBehaviour
{
    public Chunk chunk;
    public World world;
    public int height;

    private MeshFilter meshFilter;
    private MeshRenderer meshRenderer;
    private MeshCollider meshCollider;
    private Mesh mesh;

    // Declared here to help with optimization of the lists and GC
    private static Vector3[] northNormals = { Vector3.right, Vector3.right, Vector3.right, Vector3.right };
    private static Vector3[] southNormals = { Vector3.left, Vector3.left, Vector3.left, Vector3.left };
    private static Vector3[] eastNormals = { Vector3.back, Vector3.back, Vector3.back, Vector3.back };
    private static Vector3[] westNormals = { Vector3.forward, Vector3.forward, Vector3.forward, Vector3.forward };
    private static Vector3[] upNormals = { Vector3.up, Vector3.up, Vector3.up, Vector3.up };
    private static Vector3[] downNormals = { Vector3.down, Vector3.down, Vector3.down, Vector3.down };

    public void Awake()
    {
        meshFilter = gameObject.GetComponent<MeshFilter>();
        meshRenderer = gameObject.GetComponent<MeshRenderer>();
        meshCollider = gameObject.GetComponent<MeshCollider>();

        mesh = new Mesh();
        meshFilter.sharedMesh = mesh;
    }

    public void GenerateMesh()
    {

        Vector3[] vertices = new Vector3[19652];
        Vector3[] normals = new Vector3[19652];
        Vector2[] uv = new Vector2[19652];
        int[] triangles = new int[29478];

        BlockData.Material self;
        BlockData.Material north;
        BlockData.Material south;
        BlockData.Material east;
        BlockData.Material west;
        BlockData.Material up;
        BlockData.Material down;
        int vertexCount = 0;

        for (int x = 0; x < 16; x++)
        {
            for (int y = 0; y < 16; y++)
            {
                for (int z = 0; z < 16; z++)
                {
                    self = chunk.GetChunkBlock(x, y + height, z);
                    north = chunk.GetChunkBlock(x + 1, y + height, z);
                    south = chunk.GetChunkBlock(x - 1, y + height, z);
                    east = chunk.GetChunkBlock(x, y + height, z - 1);
                    west = chunk.GetChunkBlock(x, y + height, z + 1);
                    up = chunk.GetChunkBlock(x, y + 1 + height, z);
                    down = chunk.GetChunkBlock(x, y - 1 + height, z);

                    // Only render faces if the block is transparent
                    if (BlockData.IsBlock(self))
                    {
                        if (ShouldRenderFace(self, north))
                        {
                            // Make north quad
                           
                            // Add verts
                            vertices[vertexCount] = (new Vector3(x - 7.5f, y - 8.5f, z - 8.5f));
                            vertices[vertexCount + 1] = (new Vector3(x - 7.5f, y - 7.5f, z - 8.5f));
                            vertices[vertexCount + 2] = (new Vector3(x - 7.5f, y - 7.5f, z - 7.5f));
                            vertices[vertexCount + 3] = (new Vector3(x - 7.5f, y - 8.5f, z - 7.5f));
                            // Add normals
                            northNormals.CopyTo(normals, vertexCount);
                            // Add uvs
                            uv[vertexCount] = new Vector2(0, 0);
                            uv[vertexCount + 1] = new Vector2(0, 1);
                            uv[vertexCount + 2] = new Vector2(1, 1);
                            uv[vertexCount + 3] = new Vector2(1, 0);

                            triangles[vertexCount / 4 * 6] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 1] = vertexCount + 1;
                            triangles[vertexCount / 4 * 6 + 2] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 3] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 4] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 5] = vertexCount + 3;
                            // Add tris
                            vertexCount += 4;
                        }

                        if (ShouldRenderFace(self, south))
                        {
                            // Make south quad

                            // Add verts
                            vertices[vertexCount] = (new Vector3(x - 8.5f, y - 8.5f, z - 7.5f));
                            vertices[vertexCount + 1] = (new Vector3(x - 8.5f, y - 7.5f, z - 7.5f));
                            vertices[vertexCount + 2] = (new Vector3(x - 8.5f, y - 7.5f, z - 8.5f));
                            vertices[vertexCount + 3] = (new Vector3(x - 8.5f, y - 8.5f, z - 8.5f));
                            // Add normals
                            southNormals.CopyTo(normals, vertexCount);
                            // Add uvs
                            uv[vertexCount] = new Vector2(0, 0);
                            uv[vertexCount + 1] = new Vector2(0, 1);
                            uv[vertexCount + 2] = new Vector2(1, 1);
                            uv[vertexCount + 3] = new Vector2(1, 0);
                            // Add tris
                            triangles[vertexCount / 4 * 6] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 1] = vertexCount + 1;
                            triangles[vertexCount / 4 * 6 + 2] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 3] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 4] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 5] = vertexCount + 3;

                            vertexCount += 4;
                        }

                        if (ShouldRenderFace(self, east))
                        {
                            // Make east quad

                            // Add verts
                            vertices[vertexCount] = (new Vector3(x - 8.5f, y - 8.5f, z - 8.5f));
                            vertices[vertexCount + 1] = (new Vector3(x - 8.5f, y - 7.5f, z - 8.5f));
                            vertices[vertexCount + 2] = (new Vector3(x - 7.5f, y - 7.5f, z - 8.5f));
                            vertices[vertexCount + 3] = (new Vector3(x - 7.5f, y - 8.5f, z - 8.5f));
                            // Add normals
                            eastNormals.CopyTo(normals, vertexCount);
                            // Add uvs
                            uv[vertexCount] = new Vector2(0, 0);
                            uv[vertexCount + 1] = new Vector2(0, 1);
                            uv[vertexCount + 2] = new Vector2(1, 1);
                            uv[vertexCount + 3] = new Vector2(1, 0);
                            // Add tris
                            triangles[vertexCount / 4 * 6] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 1] = vertexCount + 1;
                            triangles[vertexCount / 4 * 6 + 2] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 3] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 4] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 5] = vertexCount + 3;

                            vertexCount += 4;
                        }

                        if (ShouldRenderFace(self, west))
                        {
                            // Make west quad
                           
                            // Add verts
                            vertices[vertexCount] = (new Vector3(x - 7.5f, y - 8.5f, z - 7.5f));
                            vertices[vertexCount + 1] = (new Vector3(x - 7.5f, y - 7.5f, z - 7.5f));
                            vertices[vertexCount + 2] = (new Vector3(x - 8.5f, y - 7.5f, z - 7.5f));
                            vertices[vertexCount + 3] = (new Vector3(x - 8.5f, y - 8.5f, z - 7.5f));
                            // Add normals
                            westNormals.CopyTo(normals, vertexCount);
                            // Add uvs
                            uv[vertexCount] = new Vector2(0, 0);
                            uv[vertexCount + 1] = new Vector2(0, 1);
                            uv[vertexCount + 2] = new Vector2(1, 1);
                            uv[vertexCount + 3] = new Vector2(1, 0);
                            // Add tris
                            triangles[vertexCount / 4 * 6] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 1] = vertexCount + 1;
                            triangles[vertexCount / 4 * 6 + 2] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 3] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 4] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 5] = vertexCount + 3;

                            vertexCount += 4;
                        }

                        if (ShouldRenderFace(self, up))
                        {
                            // Make up quad
                           
                            // Add verts
                            vertices[vertexCount] = (new Vector3(x - 8.5f, y - 7.5f, z - 7.5f));
                            vertices[vertexCount + 1] = (new Vector3(x - 7.5f, y - 7.5f, z - 7.5f));
                            vertices[vertexCount + 2] = (new Vector3(x - 7.5f, y - 7.5f, z - 8.5f));
                            vertices[vertexCount + 3] = (new Vector3(x - 8.5f, y - 7.5f, z - 8.5f));
                            // Add normals
                            upNormals.CopyTo(normals, vertexCount);
                            // Add uvs
                            uv[vertexCount] = new Vector2(0, 0);
                            uv[vertexCount + 1] = new Vector2(0, 1);
                            uv[vertexCount + 2] = new Vector2(1, 1);
                            uv[vertexCount + 3] = new Vector2(1, 0);
                            // Add tris
                            triangles[vertexCount / 4 * 6] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 1] = vertexCount + 1;
                            triangles[vertexCount / 4 * 6 + 2] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 3] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 4] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 5] = vertexCount + 3;

                            vertexCount += 4;
                        }

                        if (ShouldRenderFace(self, down))
                        {
                            // Make down quad
                           
                            // Add verts
                            vertices[vertexCount] = (new Vector3(x - 7.5f, y - 8.5f, z - 7.5f));
                            vertices[vertexCount + 1] = (new Vector3(x - 8.5f, y - 8.5f, z - 7.5f));
                            vertices[vertexCount + 2] = (new Vector3(x - 8.5f, y - 8.5f, z - 8.5f));
                            vertices[vertexCount + 3] = (new Vector3(x - 7.5f, y - 8.5f, z - 8.5f));
                            // Add normals
                            downNormals.CopyTo(normals, vertexCount);
                            // Add uvs
                            uv[vertexCount] = new Vector2(0, 0);
                            uv[vertexCount + 1] = new Vector2(0, 1);
                            uv[vertexCount + 2] = new Vector2(1, 1);
                            uv[vertexCount + 3] = new Vector2(1, 0);
                            // Add tris
                            triangles[vertexCount / 4 * 6] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 1] = vertexCount + 1;
                            triangles[vertexCount / 4 * 6 + 2] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 3] = vertexCount;
                            triangles[vertexCount / 4 * 6 + 4] = vertexCount + 2;
                            triangles[vertexCount / 4 * 6 + 5] = vertexCount + 3;

                            vertexCount += 4;
                        }
                    }
                }
            }
        }

        mesh.Clear();

        Vector3[] finalVertices = new Vector3[vertexCount];
        Vector3[] finalNormals = new Vector3[vertexCount];
        Vector2[] finalUV = new Vector2[vertexCount];
        int[] finalTriangles= new int[vertexCount / 4 * 6];

        for (int i = 0; i < vertexCount; i++)
        {
            finalVertices[i] = vertices[i];
            finalNormals[i] = normals[i];
            finalUV[i] = uv[i];
        }

        for (int i = 0; i < vertexCount / 4 * 6; i++)
        {
            finalTriangles[i] = triangles[i];
        }

        mesh.vertices = finalVertices;
        mesh.normals = finalNormals;
        mesh.uv = finalUV;
        mesh.triangles = finalTriangles;
        mesh.Optimize();
        meshCollider.sharedMesh = mesh;
    }

    private bool ShouldRenderFace(BlockData.Material self, BlockData.Material relitive)
    {
        return (!BlockData.IsBlock(relitive) || (!BlockData.IsTransparent(self) && BlockData.IsTransparent(relitive)));
    }
}

Chunk Code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;

public class Chunk : MonoBehaviour
{
    public BlockData.Material[,,] chunkData = new BlockData.Material[16,256,16];

    public int chunkX = 0;
    public int chunkZ = 0;
    private World world;

    public SubChunk subChunk_0;
    public SubChunk subChunk_1;
    public SubChunk subChunk_2;
    public SubChunk subChunk_3;
    public SubChunk subChunk_4;
    public SubChunk subChunk_5;
    public SubChunk subChunk_6;
    public SubChunk subChunk_7;
    public SubChunk subChunk_8;
    public SubChunk subChunk_9;
    public SubChunk subChunk_10;
    public SubChunk subChunk_11;
    public SubChunk subChunk_12;
    public SubChunk subChunk_13;
    public SubChunk subChunk_14;
    public SubChunk subChunk_15;

    public void Awake()
    {
        subChunk_0.chunk = this;
        subChunk_1.chunk = this;
        subChunk_2.chunk = this;
        subChunk_3.chunk = this;
        subChunk_4.chunk = this;
        subChunk_5.chunk = this;
        subChunk_6.chunk = this;
        subChunk_7.chunk = this;
        subChunk_8.chunk = this;
        subChunk_9.chunk = this;
        subChunk_10.chunk = this;
        subChunk_11.chunk = this;
        subChunk_12.chunk = this;
        subChunk_13.chunk = this;
        subChunk_14.chunk = this;
        subChunk_15.chunk = this;

        subChunk_0.height = 0 * 16;
        subChunk_1.height = 1 * 16;
        subChunk_2.height = 2 * 16;
        subChunk_3.height = 3 * 16;
        subChunk_4.height = 4 * 16;
        subChunk_5.height = 5 * 16;
        subChunk_6.height = 6 * 16;
        subChunk_7.height = 7 * 16;
        subChunk_8.height = 8 * 16;
        subChunk_9.height = 9 * 16;
        subChunk_10.height = 10 * 16;
        subChunk_11.height = 11 * 16;
        subChunk_12.height = 12 * 16;
        subChunk_13.height = 13 * 16;
        subChunk_14.height = 14 * 16;
        subChunk_15.height = 15 * 16;
    }

    public void setWorld(World world)
    {
        this.world = world;
        subChunk_0.world = world;
        subChunk_1.world = world;
        subChunk_2.world = world;
        subChunk_3.world = world;
        subChunk_4.world = world;
        subChunk_5.world = world;
        subChunk_6.world = world;
        subChunk_7.world = world;
        subChunk_8.world = world;
        subChunk_9.world = world;
        subChunk_10.world = world;
        subChunk_11.world = world;
        subChunk_12.world = world;
        subChunk_13.world = world;
        subChunk_14.world = world;
        subChunk_15.world = world;
    }

    public void RenderChunk()
    {
        System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
        stopwatch.Start();
        subChunk_0.GenerateMesh();
        subChunk_1.GenerateMesh();
        subChunk_2.GenerateMesh();
        subChunk_3.GenerateMesh();
        subChunk_4.GenerateMesh();
        subChunk_5.GenerateMesh();
        subChunk_6.GenerateMesh();
        subChunk_7.GenerateMesh();
        subChunk_8.GenerateMesh();
        subChunk_9.GenerateMesh();
        subChunk_10.GenerateMesh();
        subChunk_11.GenerateMesh();
        subChunk_12.GenerateMesh();
        subChunk_13.GenerateMesh();
        subChunk_14.GenerateMesh();
        subChunk_15.GenerateMesh();
        stopwatch.Stop();
        Debug.Log(stopwatch.ElapsedMilliseconds);
    }

    public BlockData.Material GetChunkBlock(int x, int y, int z)
    {
        if (x >= 0 && x < 16)
        {
            if (y >= 0 && y < 256)
            {
                if (z >= 0 && z < 16)
                {
                    return chunkData[x, y, z];
                } else if (z < 0)
                {
                    return world.GetBlockAt(chunkX, chunkZ - 1, x, y, 15);
                } else
                {
                    return world.GetBlockAt(chunkX, chunkZ + 1, x, y, 0);
                }
            }
        } else if (x < 0)
            {
                return world.GetBlockAt(chunkX - 1, chunkZ, 15, y, z);
            } else
            {
                return world.GetBlockAt(chunkX + 1, chunkZ, 0, y, z);
            }

        return BlockData.Material.AIR;
    }
}

World Code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class World : MonoBehaviour
{
    public static int SEED = 15163;
    public GameObject chunkPrefab;

    private Dictionary<Vector2Int, Chunk> chunks = new Dictionary<Vector2Int, Chunk>();

    public void Start()
    {
        for (int x = -8; x < 8; x++)
        {
            for (int z = -8; z < 8; z++)
            {
                GenerateChunk(x, z);
            }
        }
    }

    public void GenerateChunk(int chunkX, int chunkZ)
    {
        Vector2Int key = new Vector2Int(chunkX, chunkZ);

        Vector2Int keyN = new Vector2Int(chunkX + 1, chunkZ);
        Vector2Int keyS = new Vector2Int(chunkX - 1, chunkZ);
        Vector2Int keyE = new Vector2Int(chunkX, chunkZ - 1);
        Vector2Int keyW = new Vector2Int(chunkX, chunkZ + 1);

        if (!chunks.ContainsKey(key))
        {
            GameObject chunkObject = Instantiate(chunkPrefab, transform);
            chunkObject.transform.position = new Vector3(chunkX * 16, 0,  chunkZ * 16);

            Chunk chunk = chunkObject.GetComponent<Chunk>();

            BlockData.Material[,,] blockData = new BlockData.Material[16, 256, 16];

            // This is a temporary noise generator
            for (int x = 0; x < 16; x++)
            {
                for (int z = 0; z < 16; z++)
                {
                    float height = Mathf.PerlinNoise((x + SEED + chunkX * 16) / 30f, (z + SEED + chunkZ * 16) / 30f) * 20 + 64;

                    for (int y = 0; y < 256; y++)
                    {
                        if (height > y)
                        {
                            blockData[x, y, z] = BlockData.Material.STONE;
                        }
                    }
                }
            }
            // End temporary noise generator

            chunk.chunkData = blockData;
            chunk.chunkX = chunkX;
            chunk.chunkZ = chunkZ;
            chunk.setWorld(this);
            chunk.RenderChunk();
            chunks.Add(key, chunk);
        }

        if (chunks.ContainsKey(keyN))
        {
            chunks[keyN].RenderChunk();
        }

        if (chunks.ContainsKey(keyS))
        {
            chunks[keyS].RenderChunk();
        }

        if (chunks.ContainsKey(keyE))
        {
            chunks[keyE].RenderChunk();
        }

        if (chunks.ContainsKey(keyW))
        {
            chunks[keyW].RenderChunk();
        }
    }

    public BlockData.Material GetBlockAt(int chunkX, int chunkZ, int x, int y, int z)
    {
        Vector2Int key = new Vector2Int(chunkX, chunkZ);

        if (chunks.ContainsKey(key))
        {
            if (x >= 0 && x < 16 && y >= 0 && y < 256 && z >= 0 && z < 16)
            {
                return chunks[key].chunkData[x, y, z];
            } else
            {
                return BlockData.Material.AIR;
            }

        } else
        {
            return BlockData.Material.AIR;
        }
    }
}

Optimization should always start with the profiler… Window → Analyze → Profiler.

From that window, see if what it tells you indicates anything you could be doing less of in the code and still solve the problem.

Well, some general things:

Avoid the use of multi dimensional arrays, they are slow. It’s generally faster to simply use a flattend array and calculate the proper flattend index yourself. With 3 nested loops that can be done quite efficient as you can save intermediate values at each nesting level.

Next thing is you should seperate the generation of a chunk section into 3 parts:

  • voxel data generation
  • mesh data generation
  • mesh object creation and update.

Everything except the last step can be executed on a seperate thread. So you can take advantage of multi threading. Only the last step, the assignment of the vertex and index buffers to the mesh object has to be done on the main thread.

Avoid the additional Vertex, normals and triangle arrays. Use generic Lists instead and use the proper SetVertices / SetIndices methods of the mesh class. It can directly use Lists.

Of course when you load / unload chunks / subchunks you want to create a pool for them to avoid the destruction and recreation of those objects. Of course if you do cache those objects you have to take care of properly resetting them for reuse.

Finally it seems really weird that you use 16 individual subchunk variables. Is there a reason why you didn’t use an array? I don’t see any benefit of your current approach.

You generally should avoid repetitive code in tight loops. Looking up the neighboring chunks for every neighboring block is unnecessary. A subchunk need access to all 6 neighboring subchunks and each subchunk is required 256 times (16 * 16). It’s true that dictionary lookups have a constant time complexity, though constant doesn’t equal cheap.

1 Like

After viewing the profiler and the responses here are some changes I am going to make:

  • Move code that is not needed outside an if statement into the if statement.
  • Save a “skin” of block data around the chunk that can be accessed more locally instead of getting neighbor chunks each time.
  • Flatten the arrays.
  • Pre-load chunks (without rendering) around the chunk wanting to render to get a skin. (No more re-rendering chunks)

With this, generating normal terrain is quite fast on the main thread but I will also try multithreading because more complex geometry will take longer to load.

I did this so they can be set in the editor, it is not a permanent solution.

Thanks to everyone that gave feedback. The code now runs around 1ms per chunk which is a great improvement. I will post the final code for anybody that is curious and wants to make voxel terrain.

SubChunk.cs

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.Profiling;

[RequireComponent(typeof(MeshFilter)), RequireComponent(typeof(MeshRenderer)), RequireComponent(typeof(MeshCollider))]
public class SubChunk : MonoBehaviour
{
    public Chunk chunk;
    public World world;
    public int height;

    private MeshFilter meshFilter;
    private MeshRenderer meshRenderer;
    private MeshCollider meshCollider;
    private Mesh mesh;

    private static Vector3[] northNormals = { Vector3.right, Vector3.right, Vector3.right, Vector3.right };
    private static Vector3[] southNormals = { Vector3.left, Vector3.left, Vector3.left, Vector3.left };
    private static Vector3[] eastNormals = { Vector3.back, Vector3.back, Vector3.back, Vector3.back };
    private static Vector3[] westNormals = { Vector3.forward, Vector3.forward, Vector3.forward, Vector3.forward };
    private static Vector3[] upNormals = { Vector3.up, Vector3.up, Vector3.up, Vector3.up };
    private static Vector3[] downNormals = { Vector3.down, Vector3.down, Vector3.down, Vector3.down };

    public void Awake()
    {
        meshFilter = gameObject.GetComponent<MeshFilter>();
        meshRenderer = gameObject.GetComponent<MeshRenderer>();
        meshCollider = gameObject.GetComponent<MeshCollider>();

        mesh = new Mesh();
        meshFilter.sharedMesh = mesh;
    }

    public void GenerateMesh()
    {

        List<Vector3> meshVerticies = new List<Vector3>();
        List<Vector3> meshNormals = new List<Vector3>();
        List<Vector2> meshUVs = new List<Vector2>();
        List<int> meshTriangles = new List<int>();

        BlockData.Material self;

        int vertexCount = 0;

        for (int x = 0; x < 16; ++x)
        {
            for (int y = 0; y < 16; ++y)
            {
                for (int z = 0; z < 16; ++z)
                {
                    self = chunk.GetChunkBlock(x, y + height, z);

                    // Only render faces if the block is transparent
                    if (BlockData.IsBlock(self))
                    {
                        if (ShouldRenderFace(self, chunk.GetChunkBlock(x + 1, y + height, z)))
                        {
                            // Make north quad
                            vertexCount = meshVerticies.Count;
                            // Add verts
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 8.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 7.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 7.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 8.5f, z - 7.5f));
                            // Add normals
                            meshNormals.Add(northNormals[0]);
                            meshNormals.Add(northNormals[1]);
                            meshNormals.Add(northNormals[2]);
                            meshNormals.Add(northNormals[3]);
                            // Add uvs
                            meshUVs.Add(new Vector2(0, 0));
                            meshUVs.Add(new Vector2(0, 1));
                            meshUVs.Add(new Vector2(1, 1));
                            meshUVs.Add(new Vector2(1, 0));
                            // Add tris
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 1);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount + 3);
                        }

                        if (ShouldRenderFace(self, chunk.GetChunkBlock(x - 1, y + height, z)))
                        {
                            // Make south quad
                            vertexCount = meshVerticies.Count;
                            // Add verts
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 8.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 7.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 7.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 8.5f, z - 8.5f));
                            // Add normals
                            meshNormals.Add(southNormals[0]);
                            meshNormals.Add(southNormals[1]);
                            meshNormals.Add(southNormals[2]);
                            meshNormals.Add(southNormals[3]);
                            // Add uvs
                            meshUVs.Add(new Vector2(0, 0));
                            meshUVs.Add(new Vector2(0, 1));
                            meshUVs.Add(new Vector2(1, 1));
                            meshUVs.Add(new Vector2(1, 0));
                            // Add tris
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 1);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount + 3);

                        }

                        if (ShouldRenderFace(self, chunk.GetChunkBlock(x, y + height, z - 1)))
                        {
                            // Make east quad
                            vertexCount = meshVerticies.Count;
                            // Add verts
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 8.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 7.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 7.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 8.5f, z - 8.5f));
                            // Add normals
                            meshNormals.Add(eastNormals[0]);
                            meshNormals.Add(eastNormals[1]);
                            meshNormals.Add(eastNormals[2]);
                            meshNormals.Add(eastNormals[3]);
                            // Add uvs
                            meshUVs.Add(new Vector2(0, 0));
                            meshUVs.Add(new Vector2(0, 1));
                            meshUVs.Add(new Vector2(1, 1));
                            meshUVs.Add(new Vector2(1, 0));
                            // Add tris
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 1);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount + 3);

                        }

                        if (ShouldRenderFace(self, chunk.GetChunkBlock(x, y + height, z + 1)))
                        {
                            // Make west quad
                            vertexCount = meshVerticies.Count;
                            // Add verts
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 8.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 7.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 7.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 8.5f, z - 7.5f));
                            // Add normals
                            meshNormals.Add(westNormals[0]);
                            meshNormals.Add(westNormals[1]);
                            meshNormals.Add(westNormals[2]);
                            meshNormals.Add(westNormals[3]);
                            // Add uvs
                            meshUVs.Add(new Vector2(0, 0));
                            meshUVs.Add(new Vector2(0, 1));
                            meshUVs.Add(new Vector2(1, 1));
                            meshUVs.Add(new Vector2(1, 0));
                            // Add tris
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 1);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount + 3);

                        }

                        if (ShouldRenderFace(self, chunk.GetChunkBlock(x, y + 1 + height, z)))
                        {
                            // Make up quad
                            vertexCount = meshVerticies.Count;
                            // Add verts
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 7.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 7.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 7.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 7.5f, z - 8.5f));
                            // Add normals
                            meshNormals.Add(upNormals[0]);
                            meshNormals.Add(upNormals[1]);
                            meshNormals.Add(upNormals[2]);
                            meshNormals.Add(upNormals[3]);
                            // Add uvs
                            meshUVs.Add(new Vector2(0, 0));
                            meshUVs.Add(new Vector2(0, 1));
                            meshUVs.Add(new Vector2(1, 1));
                            meshUVs.Add(new Vector2(1, 0));
                            // Add tris
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 1);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount + 3);

                        }

                        if (ShouldRenderFace(self, chunk.GetChunkBlock(x, y - 1 + height, z)))
                        {
                            // Make down quad
                            vertexCount = meshVerticies.Count;
                            // Add verts
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 8.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 8.5f, z - 7.5f));
                            meshVerticies.Add(new Vector3(x - 8.5f, y - 8.5f, z - 8.5f));
                            meshVerticies.Add(new Vector3(x - 7.5f, y - 8.5f, z - 8.5f));
                            // Add normals
                            meshNormals.Add(downNormals[0]);
                            meshNormals.Add(downNormals[1]);
                            meshNormals.Add(downNormals[2]);
                            meshNormals.Add(downNormals[3]);
                            // Add uvs
                            meshUVs.Add(new Vector2(0, 0));
                            meshUVs.Add(new Vector2(0, 1));
                            meshUVs.Add(new Vector2(1, 1));
                            meshUVs.Add(new Vector2(1, 0));
                            // Add tris
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 1);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount);
                            meshTriangles.Add(vertexCount + 2);
                            meshTriangles.Add(vertexCount + 3);

                        }
                    }
                }
            }
        }

        mesh.Clear();

        mesh.SetVertices(meshVerticies);
        mesh.SetNormals(meshNormals);
        mesh.SetUVs(0, meshUVs);
        mesh.SetTriangles(meshTriangles, 0);
        //mesh.Optimize(); // This probably isn't needed but can save mesh memory
        meshCollider.sharedMesh = mesh;
    }

    private bool ShouldRenderFace(BlockData.Material self, BlockData.Material relitive)
    {
        return (!BlockData.IsBlock(relitive) || (relitive == BlockData.Material.GLASS && self != BlockData.Material.GLASS));
    }
}

Chunk.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;

public class Chunk : MonoBehaviour
{
    public BlockData.Material[] chunkMaterials = new BlockData.Material[65536];

    public BlockData.Material[] northSkinMaterials = new BlockData.Material[4096];
    public BlockData.Material[] southSkinMaterials = new BlockData.Material[4096];
    public BlockData.Material[] eastSkinMaterials = new BlockData.Material[4096];
    public BlockData.Material[] westSkinMaterials = new BlockData.Material[4096];

    public int chunkX = 0;
    public int chunkZ = 0;
    private World world;

    public SubChunk subChunk_0;
    public SubChunk subChunk_1;
    public SubChunk subChunk_2;
    public SubChunk subChunk_3;
    public SubChunk subChunk_4;
    public SubChunk subChunk_5;
    public SubChunk subChunk_6;
    public SubChunk subChunk_7;
    public SubChunk subChunk_8;
    public SubChunk subChunk_9;
    public SubChunk subChunk_10;
    public SubChunk subChunk_11;
    public SubChunk subChunk_12;
    public SubChunk subChunk_13;
    public SubChunk subChunk_14;
    public SubChunk subChunk_15;

    public void Awake()
    {
        subChunk_0.chunk = this;
        subChunk_1.chunk = this;
        subChunk_2.chunk = this;
        subChunk_3.chunk = this;
        subChunk_4.chunk = this;
        subChunk_5.chunk = this;
        subChunk_6.chunk = this;
        subChunk_7.chunk = this;
        subChunk_8.chunk = this;
        subChunk_9.chunk = this;
        subChunk_10.chunk = this;
        subChunk_11.chunk = this;
        subChunk_12.chunk = this;
        subChunk_13.chunk = this;
        subChunk_14.chunk = this;
        subChunk_15.chunk = this;

        subChunk_0.height = 0 * 16;
        subChunk_1.height = 1 * 16;
        subChunk_2.height = 2 * 16;
        subChunk_3.height = 3 * 16;
        subChunk_4.height = 4 * 16;
        subChunk_5.height = 5 * 16;
        subChunk_6.height = 6 * 16;
        subChunk_7.height = 7 * 16;
        subChunk_8.height = 8 * 16;
        subChunk_9.height = 9 * 16;
        subChunk_10.height = 10 * 16;
        subChunk_11.height = 11 * 16;
        subChunk_12.height = 12 * 16;
        subChunk_13.height = 13 * 16;
        subChunk_14.height = 14 * 16;
        subChunk_15.height = 15 * 16;
    }

    public void setWorld(World world)
    {
        this.world = world;
        subChunk_0.world = world;
        subChunk_1.world = world;
        subChunk_2.world = world;
        subChunk_3.world = world;
        subChunk_4.world = world;
        subChunk_5.world = world;
        subChunk_6.world = world;
        subChunk_7.world = world;
        subChunk_8.world = world;
        subChunk_9.world = world;
        subChunk_10.world = world;
        subChunk_11.world = world;
        subChunk_12.world = world;
        subChunk_13.world = world;
        subChunk_14.world = world;
        subChunk_15.world = world;
    }

    public void RenderChunk()
    {
        subChunk_0.GenerateMesh();
        subChunk_1.GenerateMesh();
        subChunk_2.GenerateMesh();
        subChunk_3.GenerateMesh();
        subChunk_4.GenerateMesh();
        subChunk_5.GenerateMesh();
        subChunk_6.GenerateMesh();
        subChunk_7.GenerateMesh();
        subChunk_8.GenerateMesh();
        subChunk_9.GenerateMesh();
        subChunk_10.GenerateMesh();
        subChunk_11.GenerateMesh();
        subChunk_12.GenerateMesh();
        subChunk_13.GenerateMesh();
        subChunk_14.GenerateMesh();
        subChunk_15.GenerateMesh();
    }

    public BlockData.Material GetChunkBlock(int x, int y, int z)
    {
        if (x >= 0 && x < 16)
        {
            if (y >= 0 && y < 256)
            {
                if (z >= 0 && z < 16)
                {
                    return chunkMaterials[x + z * 16 + y * 256];
                } else if (z < 0)
                {
                    return eastSkinMaterials[x + y * 16];
                } else
                {
                    return westSkinMaterials[x + y * 16];
                }
            }
        } else if (x < 0)
        {
            return southSkinMaterials[z + y * 16];
        } else
        {
            return northSkinMaterials[z + y * 16];
        }

        return BlockData.Material.AIR;
    }
}

World.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class World : MonoBehaviour
{
    public static int SEED = 15163;
    public GameObject chunkPrefab;

    private Dictionary<Vector2Int, Chunk> chunks = new Dictionary<Vector2Int, Chunk>();

    public void Start()
    {
        StartCoroutine(test());
    }

    public IEnumerator test()
    {
        for (int x = -16; x < 16; x++)
        {
            for (int z = -16; z < 16; z++)
            {
                GenerateChunk(x, z);
                yield return new WaitForEndOfFrame();
            }
        }
    }

    public void SoftGenerateChunk(int chunkX, int chunkZ)
    {
        Vector2Int key = new Vector2Int(chunkX, chunkZ);

        if (!chunks.ContainsKey(key))
        {
            GameObject chunkObject = Instantiate(chunkPrefab, transform);
            chunkObject.transform.position = new Vector3(chunkX * 16, 0, chunkZ * 16);

            Chunk chunk = chunkObject.GetComponent<Chunk>();

            BlockData.Material[] chunkMaterials = new BlockData.Material[65536];

            // This is a temporary noise generator
            for (int x = 0; x < 16; ++x)
            {
                for (int z = 0; z < 16; ++z)
                {
                    float height = Mathf.PerlinNoise((x + SEED + chunkX * 16) / 60f, (z + SEED + chunkZ * 16) / 60f) * 40 + 64 + Mathf.PerlinNoise((x + SEED + chunkX * 16) / 12f, (z + SEED + chunkZ * 16) / 12f) * 4;

                    for (int y = 0; y < 256; ++y)
                    {
                        if (height > y)
                        {
                            chunkMaterials[x + z * 16 + y * 256] = BlockData.Material.STONE;
                        }
                    }
                }
            }
            // End temporary noise generator

            chunk.chunkMaterials = chunkMaterials;
            chunk.chunkX = chunkX;
            chunk.chunkZ = chunkZ;
            chunk.setWorld(this);
            chunks.Add(key, chunk);
        }
    }

    public void GenerateChunk(int chunkX, int chunkZ)
    {
        Chunk chunk;

        SoftGenerateChunk(chunkX, chunkZ);

        chunk = chunks[new Vector2Int(chunkX, chunkZ)];

        Vector2Int keyN = new Vector2Int(chunkX + 1, chunkZ);
        Vector2Int keyS = new Vector2Int(chunkX - 1, chunkZ);
        Vector2Int keyE = new Vector2Int(chunkX, chunkZ - 1);
        Vector2Int keyW = new Vector2Int(chunkX, chunkZ + 1);

        SoftGenerateChunk(keyN.x, keyN.y);
        SoftGenerateChunk(keyS.x, keyS.y);
        SoftGenerateChunk(keyE.x, keyE.y);
        SoftGenerateChunk(keyW.x, keyW.y);

        Chunk northChunk = chunks[new Vector2Int(chunkX + 1, chunkZ)];
        Chunk southChunk = chunks[new Vector2Int(chunkX - 1, chunkZ)];
        Chunk eastChunk = chunks[new Vector2Int(chunkX, chunkZ - 1)];
        Chunk westChunk = chunks[new Vector2Int(chunkX, chunkZ + 1)];

        for (int r = 0; r < 16; r++)
        {
            for (int y = 0; y < 256; y++)
            {
                chunk.northSkinMaterials[r + y * 16] = northChunk.chunkMaterials[0 + r * 16 + y * 256];
                chunk.southSkinMaterials[r + y * 16] = southChunk.chunkMaterials[15 + r * 16 + y * 256];
                chunk.eastSkinMaterials[r + y * 16] = eastChunk.chunkMaterials[r + 15 * 16 + y * 256];
                chunk.westSkinMaterials[r + y * 16] = westChunk.chunkMaterials[r + 0 * 16 + y * 256];
            }
        }

        chunk.RenderChunk();
    }
}

BlockData.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BlockData : MonoBehaviour
{
    public enum Material
    {
        AIR, STONE, GLASS
    }

    public static bool IsTransparent(Material material)
    {
        switch (material)
        {
            case Material.AIR:
                return true;
            default:
                return false;
        }
    }

    public static bool IsBlock (Material material)
    {
        switch (material)
        {
            case Material.AIR:
                return false;
            default:
                return true;
        }
    }
}
1 Like