Wait, don't leave! Multithreading is a lot simpler than you might think, and in this post, I'll show you how to create infinite, procedural terrain in Unity with just a few lines of code that's fast, beautiful, and even has LODs.
The code is below:
using System.Collections; using System.Collections.Generic; using System.Threading; using System.Linq; using UnityEngine; public class TerrainGenerator : MonoBehaviour { public Material material; public int chunkSize = 64; public int rangeMultiplier = 2; public NoiseFilter noiseFilter; public float[] LODDistances = { 64f, 128f, 256f, 512f, 1024f }; public Transform player; public float heightThreshold = 128f; private bool isGenerating = true; private int range = 1; private Dictionary<Vector3, Geometry> chunkCache; private Dictionary<Vector3, GameObject> chunkMap; private List<Thread> terrainThreads; private volatile Mesh mesh; private Vector3 playerPos; private struct Geometry { public Vector3 position; public Vector3[] vertices; public int[] triangles; public Vector2[] uvs; public int LOD; } private void Start() { chunkCache = new Dictionary<Vector3, Geometry>(); chunkMap = new Dictionary<Vector3, GameObject>(); terrainThreads = new List<Thread>(); LoadChunkMap(); UnloadOldChunks(); StartCoroutine(UpdateChunks()); } private IEnumerator UpdateChunks() { while (isGenerating) { if (playerPos != player.position) { playerPos = player.position.y > heightThreshold ? player.position : new Vector3(player.position.x, 0, player.position.z); } LoadChunkMap(); UnloadOldChunks(); range = (int)(rangeMultiplier + Mathf.Abs(playerPos.y * 2 / chunkSize)); yield return new WaitForSeconds(0.5f); } } private void LoadChunkMap() { for (int x = (int)playerPos.x / chunkSize - range; x <= (int)playerPos.x / chunkSize + range; x++) { for (int z = (int)playerPos.z / chunkSize - range; z <= (int)playerPos.z / chunkSize + range; z++) { Vector3 chunkPos = new Vector3(x * chunkSize, 0, z * chunkSize); int LOD = GetLOD(chunkPos); if (!chunkCache.ContainsKey(chunkPos) || chunkCache[chunkPos].LOD != LOD) { Debug.Log("Generating chunk at " + chunkPos.ToString() + " with LOD " + LOD.ToString() + "."); try { chunkCache.Remove(chunkPos); Destroy(chunkMap[chunkPos]); chunkMap.Remove(chunkPos); } catch {} Geometry chunk = new Geometry(); chunk.position = chunkPos; Thread thread = new Thread(() => GenerateChunk(ref chunk, LOD)); thread.Start(); terrainThreads.Add(thread); foreach (Thread t in terrainThreads) { t.Join(); } chunkCache.Add(chunkPos, chunk); LoadChunk(chunk); } } } } private void UnloadOldChunks() { for (int i = 0; i < chunkMap.Count; i++) { Vector3 chunkPos = chunkMap.ElementAt(i).Key; if (Vector3.Distance(chunkPos, playerPos) > (range + 1) * chunkSize) { Debug.Log("Unloading chunk at " + chunkPos.ToString() + "."); Destroy(chunkMap[chunkPos]); chunkMap.Remove(chunkPos); } } } private void LoadChunk(Geometry chunk) { if (chunk.vertices != null && !chunkMap.ContainsKey(chunk.position)) { mesh = new Mesh(); mesh.vertices = chunk.vertices; mesh.triangles = chunk.triangles; mesh.uv = chunk.uvs; mesh.RecalculateNormals(); mesh.RecalculateTangents(); GameObject chunkObject = new GameObject("Chunk" + chunk.position.ToString()); chunkObject.transform.position = chunk.position; MeshRenderer meshRenderer = chunkObject.AddComponent<MeshRenderer>(); meshRenderer.material = material; MeshFilter meshFilter = chunkObject.AddComponent<MeshFilter>(); meshFilter.mesh = mesh; chunkMap.Add(chunk.position, chunkObject); } } private void GenerateChunk(ref Geometry chunk, int LOD) { int verticesPerSide = (int)Mathf.Pow(2, LOD) + 1; chunk.vertices = new Vector3[(verticesPerSide + 1) * (verticesPerSide + 1)]; chunk.triangles = new int[verticesPerSide * verticesPerSide * 6]; chunk.uvs = new Vector2[chunk.vertices.Length]; chunk.LOD = LOD; for (int x = 0, v = 0; x <= verticesPerSide; x++) { for (int z = 0; z <= verticesPerSide; z++, v++) { float xPos = x * chunkSize / (float)verticesPerSide - (chunkSize * 0.5f); float zPos = z * chunkSize / (float)verticesPerSide - (chunkSize * 0.5f); float height = noiseFilter.Evaluate(xPos + chunk.position.x, zPos + chunk.position.z); chunk.vertices[v] = new Vector3(xPos, height, zPos); chunk.uvs[v] = new Vector2((float)x / verticesPerSide, (float)z / verticesPerSide); } } for (int x = 0, t = 0, v = 0; x < verticesPerSide; x++, v++) { for (int z = 0; z < verticesPerSide; z++, v++, t += 6) { chunk.triangles[t] = v + verticesPerSide + 1; chunk.triangles[t + 1] = v; chunk.triangles[t + 2] = v + 1; chunk.triangles[t + 3] = v + verticesPerSide + 1; chunk.triangles[t + 4] = v + 1; chunk.triangles[t + 5] = v + verticesPerSide + 2; } } } private int GetLOD(Vector3 chunkPos) { float distance = Vector3.Distance(playerPos, chunkPos); for (int i = 0; i < LODDistances.Length; i++) { if (distance < LODDistances[i]) { return LODDistances.Length - i - 1; } } return 0; } }
Comments
Post a Comment