using System; using System.Collections.Generic; using System.Security.Cryptography; using UnityEngine; using Random = UnityEngine.Random; public class GrassField : MonoBehaviour { public int size = 100; public int numChunks = 5; public GrassChunk chunkPrefab; [Header("LOD 0")] public Material grassMaterialLOD0; [Header("LOD 1")] public Material grassMaterialLOD1; [Header("LOD 2")] public Material grassMaterialLOD2; private struct BladeData { public Vector3 rootPosition; public Vector3 tipOffset; } // ReSharper disable Unity.PerformanceAnalysis public void GenerateGrassField() { Debug.Log("Generating grass field... "); var blades = GenerateBladesRandom(1000000); CreateChunks(blades); Debug.Log("Generating grass field done"); } private BladeData[] GenerateBladesRandom(int num) { var blades = new BladeData[num]; for (int i = 0; i < num; i++) { blades[i].rootPosition = new Vector3( Random.Range(0f, size), Random.Range(-0.25f, 0f), Random.Range(0f, size) ); blades[i].tipOffset = new Vector3(Random.Range(-.8f, .8f), Random.Range(0.7f, 1f), Random.Range(-.8f, .8f)); } return blades; } private BladeData[] GenerateBladesEven() { var blades = new BladeData[size * size * 100]; for (int x = 0; x < size * 10; x++) for (int y = 0; y < size * 10; y++) { var i = x + y * size * 10; blades[i].rootPosition = new Vector3(x/10.0f, 0.0f, y/10.0f); blades[i].tipOffset = new Vector3(0.01f, 1.0f, 0.01f); } return blades; } private void CreateChunks(BladeData[] blades) { GameObject chunks; // If a child called "Chunks" exists, destroy it -> children are also destroyed var chunksTransform = gameObject.transform.Find("Chunks"); if (chunksTransform != null) { // Destroy(obj) does not work in edit mode DestroyImmediate(chunksTransform.gameObject); } // create GameObject "Chunks" as a container for all GrassChunk objects chunks = new GameObject("Chunks"); chunks.transform.SetParent(this.transform, false); var chunkSize = ((float)size) / numChunks; // bounds of the chunk (relative to chunk) - do not consider y position var chunkBounds = new Bounds( new Vector3(0.5f * chunkSize, 0.0f, 0.5f * chunkSize), new Vector3(chunkSize, 10000f, chunkSize) ); for (int x = 0; x < numChunks; x++) for (int y = 0; y < numChunks; y++) { var chunkPos = new Vector3(x, 0.0f, y) * chunkSize; List rootPositions = new(); List tipOffsets = new(); List indices = new(); foreach (var blade in blades) { // position relative to chunk var localRootPos = blade.rootPosition - chunkPos; if (chunkBounds.Contains(localRootPos)) { rootPositions.Add(localRootPos); tipOffsets.Add(blade.tipOffset); indices.Add(rootPositions.Count - 1); } } var mesh = new Mesh(); mesh.name = "grass_chunk[" + x + "," + y + "]"; if (rootPositions.Count > 65536) { // 16 bit (default) supports 65536 vertices, 32 bit supports 4 billion mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; Debug.LogWarning(mesh.name + " has more than 65536 grass blades. Mesh.indexFormat set to UInt32."); } mesh.SetVertices(rootPositions); mesh.SetUVs(0, tipOffsets); // Vector3 UVs // AABB gets calculated automatically (considers all root positions) mesh.SetIndices(indices, MeshTopology.Points, 0); // update the AABB to match the actual grass (not just the root positions) // use mesh.bounds rather than meshRenderer.localBounds, // because the latter gets overwritten automatically var previousBounds = mesh.bounds; float width = 1f; // a blade should never bend sideways farther than this float height = 1f; // a blade should not be higher than this mesh.bounds = new Bounds( previousBounds.center + new Vector3(0f, height * 0.5f, 0f), previousBounds.size + new Vector3(2f*width, height, 2f*width) ); var chunk = Instantiate(chunkPrefab, chunks.transform); chunk.name = "Chunk [" + x + ", " + y +"]"; chunk.transform.localPosition = chunkPos; chunk.grassMaterialLOD0 = grassMaterialLOD0; chunk.grassMaterialLOD1 = grassMaterialLOD1; chunk.grassMaterialLOD2 = grassMaterialLOD2; var meshFilter = chunk.GetComponent(); meshFilter.sharedMesh = mesh; chunk.UpdateLOD(); } } private void OnDrawGizmos() { Gizmos.color = new Color(0f, 0.2f, 1f, 1f); var chunkSize = ((float)size) / numChunks; var bounds = new Bounds( new Vector3(0.5f * chunkSize, 0.0f, 0.5f * chunkSize), new Vector3(chunkSize, 0f, chunkSize) ); for (int x = 0; x < numChunks; x++) for (int y = 0; y < numChunks; y++) { var chunkPos = new Vector3(x, 0.0f, y) * chunkSize; Gizmos.DrawWireCube(bounds.center + chunkPos + transform.position, bounds.size); } } }