I'm making a Minecraft clone, and the chunks take ages to load. The probable way to optimize this is by using the Greedy Meshing algorithm. As you can see:
![alt text][1]
The chunks that are generated are not using this algorithm.
[1]: /storage/temp/202246-screenshot-41-min.png
If someone could help me implement this algorithm into my code, that'd be *amazing.*
here is my code:
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using Extensions;
///
/// A chunk is a cubic structure of fixed side that contains individual blocks.
/// Chunks are generated by the TerrainGenerator.
///
[RequireComponent(typeof(TextureStitcher))]
public class Chunk
{
///
/// Individual blockNames contained within the chunk.
///
public BaseBlock[,,] blocks;
///
/// (x,z) size of the chunk.
///
public static int chunkSize = 16;
///
/// y-size of the chunk.
///
public static int chunkHeight = 350;
///
/// Private reference to the game object first created by BuildMesh().
///
public GameObject chunkGameObject;
///
/// Whether the mesh was already built or not.
///
public bool meshBuilt = false;
///
/// x-position of the chunk.
///
public int x;
///
/// z-position of the chunk.
///
public int z;
///
/// Used to build a quad's triangles.
///
private int[] identityQuad = new int[] {
0, 1, 3,
1, 2, 3
};
public Chunk()
{
this.blocks = new BaseBlock[chunkSize, chunkHeight, chunkSize];
}
///
/// Builds the chunk's mesh.
///
public void BuildMesh()
{
if (this.chunkGameObject == null)
this.SpawnGameObject();
Mesh mesh = new Mesh();
List vertices = new List();
List uvs = new List();
List triangles = new List();
this.BuildMeshFaces(vertices, uvs, triangles);
mesh.vertices = vertices.ToArray();
mesh.uv = uvs.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
this.chunkGameObject.GetComponent().mesh = mesh;
this.chunkGameObject.GetComponent().sharedMesh = mesh;
this.chunkGameObject.GetComponent().material = CachedResources.Load("PhysicsMaterials/FrictionLess");
this.meshBuilt = true;
}
private enum ThreadStatus { STARTED, FINISHED };
private ThreadStatus status = ThreadStatus.STARTED;
///
/// Allows to build a chunk mesh almost entirely asynchronously.
/// This has to be run on the main thread!
/// Mesh building is done in parallel, spawning a new thread. GameObject instantiation & Unity API calls are executed by the coroutine
/// on the main thread.
///
public IEnumerator BuildMeshAsync()
{
if (this.chunkGameObject == null)
this.SpawnGameObject();
Mesh mesh = new Mesh();
List vertices = new List();
List uvs = new List();
List triangles = new List();
Thread meshBuildingThread = new Thread(() => {
this.BuildMeshFaces(vertices, uvs, triangles);
this.status = ThreadStatus.FINISHED;
});
meshBuildingThread.Start();
while (this.status == ThreadStatus.STARTED)
yield return new WaitForEndOfFrame();
mesh.vertices = vertices.ToArray();
mesh.uv = uvs.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
this.chunkGameObject.GetComponent().mesh = mesh;
this.chunkGameObject.GetComponent().sharedMesh = mesh;
this.chunkGameObject.GetComponent().material = CachedResources.Load("PhysicsMaterials/FrictionLess");
this.meshBuilt = true;
}
///
/// Given the list of vertices, UVs and triangles, this builds the whole chunk's faces writing into the given parameters.
///
void BuildMeshFaces(List vertices, List uvs, List triangles)
{
int builtFaces = 0;
for (int i = 0; i < chunkSize; i++)
for (int j = 0; j < chunkHeight; j++)
for (int k = 0; k < chunkSize; k++)
{
// Determine block adjacency with air. For each adjacent block face, render the face.
// If the block itself is air, don't render anything
if (this.blocks[i,j,k] == null || this.blocks[i,j,k].blockName == "air")
continue;
// Top face adjacency
if (j >= 0 && j <= chunkHeight - 1)
if (j == chunkHeight - 1 || this.blocks[i, j + 1, k]?.blockName == "air" || this.blocks[i, j + 1, k]?.blockName == "leaves" || this.blocks[i, j + 1, k]?.blockName == "glass")
// Always render the top most face, OR if the top-adjacent block is "air".
this.AddFace(i, j, k, builtFaces++, "top", CubeMeshFaces.top, vertices, uvs, triangles);
// Bottom face adjacency
if (j >= 0 && j < chunkHeight)
if (j == 0 || this.blocks[i, j - 1, k]?.blockName == "air" || this.blocks[i, j - 1, k]?.blockName == "leaves" || this.blocks[i, j - 1, k]?.blockName == "glass")
this.AddFace(i, j, k, builtFaces++, "bottom", CubeMeshFaces.bottom, vertices, uvs, triangles);
// West face adjacency
if (i >= 0 && i < chunkSize)
if (i == 0 || this.blocks[i - 1, j, k]?.blockName == "air" || this.blocks[i - 1, j, k]?.blockName == "leaves" || this.blocks[i - 1, j, k]?.blockName == "glass")
this.AddFace(i, j, k, builtFaces++, "west", CubeMeshFaces.west, vertices, uvs, triangles);
// East face adjacency
if (i >= 0 && i <= chunkSize)
if (i == chunkSize - 1 || this.blocks[i + 1, j, k]?.blockName == "air" || this.blocks[i + 1, j, k]?.blockName == "leaves" || this.blocks[i + 1, j, k]?.blockName == "glass")
this.AddFace(i, j, k, builtFaces++, "east", CubeMeshFaces.east, vertices, uvs, triangles);
// Front face adjacency
if (k >= 0 && k < chunkSize)
if (k == 0 || this.blocks[i, j, k - 1]?.blockName == "air" || this.blocks[i, j, k - 1]?.blockName == "leaves" || this.blocks[i, j, k - 1]?.blockName == "glass")
this.AddFace(i, j, k, builtFaces++, "front", CubeMeshFaces.front, vertices, uvs, triangles);
// Back face adjacency
if (k >= 0 && k <= chunkSize)
if (k == chunkSize - 1 || this.blocks[i, j, k + 1]?.blockName == "air" || this.blocks[i, j, k + 1]?.blockName == "leaves" || this.blocks[i, j, k + 1]?.blockName == "glass")
this.AddFace(i, j, k, builtFaces++, "back", CubeMeshFaces.back, vertices, uvs, triangles);
}
}
///
/// Generates and spawns the chunk's GameObject.
///
private void SpawnGameObject()
{
// Instantiate the GameObject (and implictly add it to the scene).
this.chunkGameObject = new GameObject("Chunk");
// Get the stitched texture.
Texture2D texture = TextureStitcher.instance.StitchedTexture;
// Add mesh filter and renderer.
this.chunkGameObject.AddComponent();
this.chunkGameObject.AddComponent();
this.chunkGameObject.AddComponent();
this.chunkGameObject.AddComponent();
this.chunkGameObject.tag = "chunk";
this.chunkGameObject.GetComponent().material = Resources.Load("ChunkCutout");
this.chunkGameObject.GetComponent().material.mainTexture = texture;
this.chunkGameObject.transform.position = new Vector3(this.x * chunkSize, 0, this.z * chunkSize);
}
///
/// Given the block's (i,j,k) vector, the number of built faces, face to build, vertices, uvs and triangles references,
/// this appends a new face mesh's data on the vertices, uvs and triangles references.
///
void AddFace(int i, int j, int k, int builtFaces, string faceName, Vector3[] face, List vertices, List uvs, List triangles)
{
string textureName = this.blocks[i,j,k].blockName;
if (this.blocks[i,j,k].textureName != "default")
textureName = this.blocks[i,j,k].textureName;
if (this.blocks[i,j,k].hasSidedTextures)
{
if (TextureStitcher.instance.TextureUVs.ContainsKey(System.String.Format("{0}_{1}", textureName, faceName)))
textureName = System.String.Format("{0}_{1}", textureName, faceName);
else
textureName = System.String.Format("{0}_{1}", textureName, "side");
}
vertices.AddRange(face.Add((i,j,k)));
uvs.AddRange(TextureStitcher.instance.TextureUVs[textureName].ToArray());
triangles.AddRange(this.identityQuad.Add(builtFaces * 4));
}
public void Destroy()
{
GameObject.Destroy(this.chunkGameObject);
}
///
/// Recalculates the chunk mesh's normals.
///
public void RecalculateNormals()
{
this.chunkGameObject.GetComponent().mesh.RecalculateNormals();
}
}
↧