using UnityEngine; using UnityEditor; [RequireComponent(typeof(MeshFilter))] [RequireComponent(typeof(MeshRenderer))] public class MeshGenerator : MonoBehaviour { public Texture2D heightMap; public Vector3 size = new Vector3(10, 3, 10); public Vector2Int sampleCount = new Vector2Int(25, 25); public LayerMask terrainLayer; [HideInInspector] public MeshData meshData; [HideInInspector] public Mesh mesh; [HideInInspector] public RenderTexture colorTexture; void OnEnable() { var colorCameraObject = new GameObject("Color Camera"); colorCameraObject.transform.parent = transform; colorCameraObject.transform.localPosition = Vector3.zero; var offset = new Vector3(0, size.y, 0); var rotation = Quaternion.Euler(90, 0, 0); colorCameraObject.transform.SetLocalPositionAndRotation(offset, rotation); var colorCamera = colorCameraObject.AddComponent(); colorCamera.orthographic = true; colorCamera.orthographicSize = size.z / 2; colorCamera.aspect = size.x / size.z; colorCamera.nearClipPlane = 0; colorCamera.clearFlags = CameraClearFlags.Nothing; colorCamera.depthTextureMode = DepthTextureMode.None; colorCamera.cullingMask = 1 << (int)Mathf.Log(terrainLayer.value, 2); colorTexture = new RenderTexture(1024, 1024, 0, RenderTextureFormat.ARGB32); colorCamera.targetTexture = colorTexture; } public void GenerateTerrainMesh() { // Check if the height map is null if (heightMap == null) { Debug.LogError("Height map is null"); return; } // Initialize the mesh data meshData = new MeshData(sampleCount.x, sampleCount.y); // Loop through each vertex var index = 0; for (var y = 0; y < sampleCount.y; y++) { for (var x = 0; x < sampleCount.x; x++) { // Calculate the uv, vertex position var uv = new Vector2(x, y) / (sampleCount - Vector2.one); var vertex = (uv - Vector2.one / 2) * new Vector2(size.x, size.z); var heightValue = GetTrueHeight(uv); // Set the vertex position and uv meshData.vertices[index] = new Vector3(vertex.x, heightValue, vertex.y); meshData.uvs[index] = uv; // Add triangles if not at the edge if (x < sampleCount.x - 1 && y < sampleCount.y - 1) { meshData.AddTriangle(index, index + sampleCount.x + 1, index + sampleCount.x); meshData.AddTriangle(index + sampleCount.x + 1, index, index + 1); } // Increment the index index++; } } // Create the mesh mesh = meshData.CreateMesh(); var meshFilter = GetComponent(); if (meshFilter != null) meshFilter.sharedMesh = mesh; else Debug.LogError("Mesh filter is null"); var meshCollider = GetComponent(); if (meshCollider != null) meshCollider.sharedMesh = mesh; } // Get the height of the terrain at the given uv from the mesh public float GetMeshHeight(Vector2 uv) { // Calculate the precise indices float xIndexPrecise = uv.x * (sampleCount.x - 1); float zIndexPrecise = uv.y * (sampleCount.y - 1); // Calculate the indices of the vertices around the point var xIndex = Mathf.FloorToInt(uv.x * (sampleCount.x - 1)); var zIndex = Mathf.FloorToInt(uv.y * (sampleCount.y - 1)); // Ensure indices are within the bounds of the mesh xIndex = Mathf.Clamp(xIndex, 0, sampleCount.x - 2); zIndex = Mathf.Clamp(zIndex, 0, sampleCount.y - 2); // Get the four vertices of the square (assuming row major and clockwise order) var v1 = meshData.vertices[zIndex * sampleCount.x + xIndex]; var v2 = meshData.vertices[zIndex * sampleCount.x + xIndex + 1]; var v3 = meshData.vertices[(zIndex + 1) * sampleCount.x + xIndex]; var v4 = meshData.vertices[(zIndex + 1) * sampleCount.x + xIndex + 1]; // Bilinear interpolation var xRem = xIndexPrecise - xIndex; var zRem = zIndexPrecise - zIndex; var height1 = Mathf.Lerp(v1.y, v2.y, xRem); var height2 = Mathf.Lerp(v3.y, v4.y, xRem); return Mathf.Lerp(height1, height2, zRem); } // Get the true height of the terrain at the given uv from the height map public float GetTrueHeight(Vector2 uv) { var x = Mathf.RoundToInt(uv.x * heightMap.width); var y = Mathf.RoundToInt(uv.y * heightMap.height); return heightMap.GetPixel(x, y).grayscale * size.y; ; } // Check if the mesh and mesh data are generated public bool IsMeshGenerated() { return mesh != null && meshData != null; } } // MeshData class which allows for easy mesh creation public class MeshData { public Vector3[] vertices; public int[] triangles; public Vector2[] uvs; int triangleIndex; public MeshData(int meshWidth, int meshHeight) { vertices = new Vector3[meshWidth * meshHeight]; uvs = new Vector2[meshWidth * meshHeight]; triangles = new int[(meshWidth - 1) * (meshHeight - 1) * 6]; } public void AddTriangle(int a, int b, int c) { triangles[triangleIndex] = a; triangles[triangleIndex + 1] = c; triangles[triangleIndex + 2] = b; triangleIndex += 3; } public Mesh CreateMesh() { var mesh = new Mesh { vertices = vertices, triangles = triangles, uv = uvs }; mesh.RecalculateNormals(); return mesh; } } #if UNITY_EDITOR // Custom editor which adds a button to generate the terrain [CustomEditor(typeof(MeshGenerator))] public class MeshGeneratorEditor : Editor { public override void OnInspectorGUI() { var script = (MeshGenerator)target; DrawDefaultInspector(); GUILayout.Space(10); if (GUILayout.Button("Generate Terrain")) script.GenerateTerrainMesh(); } } #endif