using System.Collections.Generic; using System.IO; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; namespace AwesomeTechnologies.Grass { public enum GrassPatchLod { Lod0 = 0, Lod1 = 1, Lod2 = 2 } [HelpURL("http://www.awesometech.no/index.php/grass-patch-generator")] public class GrassPatchGenerator : MonoBehaviour { public GrassPatchLod GrassPatchLod = GrassPatchLod.Lod0; public int PlaneCount = 15; public float Size = 0.40f; public float MinScale = 0.8f; public float MaxScale = 1.2f; public float PlaneHeight = 0.4f; public float PlaneMaxHeight = 0.5f; public float PlaneWidth = 0.4f; public float PlaneMaxWidth = 0.5f; public int PlaneWidthSegments = 2; public int PlaneHeightSegments = 2; public int RandomSeed = 1; public float MaxBendDistance = 0.25f; public float CurveOffset = 0.25f; public Material GrassMaterial; public Texture2D GrassTexture; public Material CustomMaterial; public float MinBendHeight = 0.05f; public AnimationCurve WindBend = new AnimationCurve(); public AnimationCurve AmbientOcclusion = new AnimationCurve(); public bool BakePhase = true; public bool BakeBend = true; public bool BakeAo = true; public bool ShowVertexColors = false; public bool GenerateBackside = false; public Color ColorTint1 = Color.white; public Color ColorTint2 = Color.white; public float RandomDarkening = 0.31f; public float RootAmbient = 0.63f; public float TextureCutoff = 0.1f; private Material _vertexColorMaterial; public List GrassPlaneList; // ReSharper disable once UnusedMember.Local private void Reset() { WindBend.AddKey(0f, 0f); WindBend.AddKey(1f, 1f); AmbientOcclusion.AddKey(0f, 0f); AmbientOcclusion.AddKey(1f, 1f); if (GrassTexture == null) { GrassTexture = Resources.Load("GrassTextures/GrassFrond01") as Texture2D; UpdateTexture(); } GenerateGrassPatch(); } public void UpdateTexture() { if (CustomMaterial == null) { #if UNITY_2019_3_OR_NEWER Material material = new Material(Shader.Find("AwesomeTechnologies/Release/Grass/Grass")); #else Material material = new Material(Shader.Find("AwesomeTechnologies/Grass/Grass")); #endif material.SetTexture("_MainTex", GrassTexture); //material.SetFloat("_Cutoff", 0.2f); material.SetVector("_AG_ColorNoiseArea", new Vector4(0, 30, 0, 1)); material.SetTexture("_AG_ColorNoiseTex", Resources.Load("PerlinSeamless") as Texture2D); material.SetColor("_Color", ColorTint1); material.SetColor("_ColorB", ColorTint2); material.SetFloat("_Cutoff", TextureCutoff); material.SetFloat("_RandomDarkening", RandomDarkening); material.SetFloat("_RootAmbient", RootAmbient); #if UNITY_5_6_OR_NEWER material.enableInstancing = true; #endif material.EnableKeyword("_ALPHATEST_ON"); GrassMaterial = material; } else { Material material = new Material(CustomMaterial); material.SetTexture("_MainTex", GrassTexture); GrassMaterial = material; } GenerateGrassPatch(); } private void ClearGrassPlanes() { if (GrassPlaneList == null) GrassPlaneList = new List(); for (int i = 0; i <= GrassPlaneList.Count - 1; i++) { DestroyImmediate(GrassPlaneList[i].gameObject); } GrassPlaneList.Clear(); Transform[] ts = transform.GetComponentsInChildren(); foreach (Transform t in ts) { if (t) { if (t.gameObject.name.StartsWith("Plane_")) { DestroyImmediate(t.gameObject); } } } } public int GetMeshVertexCount() { int vertexCount = 0; for (int i = 0; i <= GrassPlaneList.Count - 1; i++) { MeshFilter meshFilter = GrassPlaneList[i].gameObject.GetComponent(); if (meshFilter) vertexCount += meshFilter.sharedMesh.vertexCount; } return vertexCount; } public int GetMeshTriangleCount() { int triangleCount = 0; for (int i = 0; i <= GrassPlaneList.Count - 1; i++) { MeshFilter meshFilter = GrassPlaneList[i].gameObject.GetComponent(); if (meshFilter) triangleCount += meshFilter.sharedMesh.triangles.Length / 3; } return triangleCount; } public void GenerateGrassPatch() { _vertexColorMaterial = Resources.Load("GrassPatchVertexColor") as Material; ClearGrassPlanes(); Random.InitState(RandomSeed); for (int i = 0; i <= PlaneCount - 1; i++) { // GameObject go = new GameObject { hideFlags = HideFlags.HideInHierarchy, name = "Plane_" + i.ToString() }; go.transform.SetParent(transform); float scale = Random.Range(MinScale, MaxScale); float selectedPlaneWidth = PlaneWidth * scale; float selectedPlaneHeight = PlaneHeight * scale; ProceduralGrassPlane proceduralGrassPlane = go.AddComponent(); proceduralGrassPlane.CurveOffset = Random.Range(-CurveOffset, CurveOffset); proceduralGrassPlane.Offset1 = Random.Range(-MaxBendDistance, MaxBendDistance); proceduralGrassPlane.Offset2 = Random.Range(-MaxBendDistance, MaxBendDistance); proceduralGrassPlane.height = selectedPlaneHeight; proceduralGrassPlane.width = selectedPlaneWidth; proceduralGrassPlane.BakeBend = BakeBend; proceduralGrassPlane.BakePhase = BakePhase; proceduralGrassPlane.BakeAO = BakeAo; proceduralGrassPlane.BendCurve = WindBend; proceduralGrassPlane.AmbientOcclusionCurve = AmbientOcclusion; proceduralGrassPlane.Phase = i * (1f /PlaneCount); proceduralGrassPlane.GenerateBackside = GenerateBackside; proceduralGrassPlane.Index = i; if (i % 4 == 1) { proceduralGrassPlane.LODLevel = 2; } else if (i % 2 == 1) { proceduralGrassPlane.LODLevel = 1; } else { proceduralGrassPlane.LODLevel = 0; } if (ShowVertexColors) { proceduralGrassPlane.Material = _vertexColorMaterial; } else { proceduralGrassPlane.Material = GrassMaterial; } proceduralGrassPlane.MinimumBendHeight = MinBendHeight; proceduralGrassPlane.heightSegments = PlaneHeightSegments; proceduralGrassPlane.widthSegments = PlaneWidthSegments; go.transform.localRotation = Quaternion.Euler(new Vector3(0, Random.Range(0, 364), 0)); go.transform.localPosition = new Vector3(Random.Range(-Size / 2f, Size / 2f), selectedPlaneHeight / 2f, Random.Range(-Size / 2f, Size / 2f)); GrassPlaneList.Add(proceduralGrassPlane); proceduralGrassPlane.CreateGrassPlane(0); } } Mesh GetCombinedMesh(int lod) { MeshFilter[] meshFilters = gameObject.GetComponentsInChildren(); List filterList = new List(); for (int i = 0; i <= meshFilters.Length - 1; i++) { ProceduralGrassPlane proceduralPlane = meshFilters[i].gameObject.GetComponent(); proceduralPlane.CreateGrassPlane(lod); if (proceduralPlane.LODLevel >= lod) filterList.Add(meshFilters[i]); } CombineInstance[] combine = new CombineInstance[filterList.Count]; for (int i = 0; i <= filterList.Count - 1; i++) { combine[i].mesh = filterList[i].sharedMesh; combine[i].transform = filterList[i].transform.localToWorldMatrix; } Mesh mesh = new Mesh(); mesh.CombineMeshes(combine); return mesh; } public void BuildPrefab() { #if UNITY_EDITOR string path = EditorUtility.SaveFilePanelInProject("Save prefab", "", "prefab", "Please enter a file name to save the prefab to"); if (path.Length != 0) { string prefabName = Path.GetFileNameWithoutExtension(path); string directory = Path.GetDirectoryName(path); string prefabFilename = path; string meshFilenameLod0 = directory + "/" + prefabName + "_LOD0.asset"; string materialFilename = Path.ChangeExtension(prefabFilename, ".mat"); Vector3 oldPosition = transform.position; Quaternion oldRotation = transform.rotation; transform.position = Vector3.zero; transform.rotation = Quaternion.identity; Material material; if (CustomMaterial) { material = new Material(CustomMaterial); material.SetTexture("_MainTex", GrassTexture); } else { material = new Material(Shader.Find("AwesomeTechnologies/Grass/Grass")); material.SetTexture("_MainTex", GrassTexture); material.SetColor("_Color", ColorTint1); material.SetColor("_ColorB", ColorTint2); material.SetFloat("_Cutoff", TextureCutoff); material.SetFloat("_RandomDarkening", RandomDarkening); material.SetFloat("_RootAmbient", RootAmbient); material.SetVector("_AG_ColorNoiseArea", new Vector4(0, 30, 0, 1)); material.SetTexture("_AG_ColorNoiseTex", Resources.Load("PerlinSeamless") as Texture2D); material.EnableKeyword("_ALPHATEST_ON"); #if UNITY_5_6_OR_NEWER material.enableInstancing = true; #endif } Mesh meshLod0 = GetCombinedMesh(0); AssetDatabase.CreateAsset(material, materialFilename); GameObject go = new GameObject {name = prefabName}; // "New grass patch_" + patchID; go.transform.position = transform.position; go.transform.rotation = transform.localRotation; go.AddComponent().sharedMesh = meshLod0; go.AddComponent().sharedMaterial = material; AssetDatabase.CreateAsset(meshLod0, meshFilenameLod0); transform.position = oldPosition; transform.rotation = oldRotation; #if UNITY_2018_3_OR_NEWER PrefabUtility.SaveAsPrefabAsset(go, prefabFilename); #else PrefabUtility.CreatePrefab(prefabFilename, go); #endif AssetDatabase.Refresh(); } #endif } public void BuildPrefabLod() { #if UNITY_EDITOR string path = EditorUtility.SaveFilePanelInProject("Save prefab", "", "prefab", "Please enter a file name to save the prefab to"); if (path.Length == 0) return; string prefabName = Path.GetFileNameWithoutExtension(path); string directory = Path.GetDirectoryName(path); string prefabFilename = path; string meshFilenameLod0 = directory + "/" + prefabName + "_LOD0.asset"; string meshFilenameLod1 = directory + "/" + prefabName + "_LOD1.asset"; string meshFilenameLod2 = directory + "/" + prefabName + "_LOD2.asset"; string materialFilename = Path.ChangeExtension(prefabFilename, ".mat"); Vector3 oldPosition = transform.position; Quaternion oldRotation = transform.rotation; transform.position = Vector3.zero; transform.rotation = Quaternion.identity; //string patchID = Time.realtimeSinceStartup.ToString(); Material material; if (CustomMaterial) { material = new Material(CustomMaterial); material.SetTexture("_MainTex", GrassTexture); } else { material = new Material(Shader.Find("AwesomeTechnologies/Release/Grass/Grass")); material.SetTexture("_MainTex", GrassTexture); material.SetColor("_Color", ColorTint1); material.SetColor("_ColorB", ColorTint2); material.SetFloat("_Cutoff", TextureCutoff); material.SetFloat("_RandomDarkening", RandomDarkening); material.SetFloat("_RootAmbient", RootAmbient); material.SetVector("_AG_ColorNoiseArea", new Vector4(0, 30, 0, 1)); material.SetTexture("_AG_ColorNoiseTex", Resources.Load("PerlinSeamless") as Texture2D); material.EnableKeyword("_ALPHATEST_ON"); } AssetDatabase.CreateAsset(material, materialFilename); GameObject go = new GameObject {name = prefabName}; // "New grass patch_" + patchID; go.transform.position = transform.position; go.transform.rotation = transform.localRotation; Mesh meshLod0 = GetCombinedMesh(0); Mesh meshLod1 = GetCombinedMesh(1); Mesh meshLod2 = GetCombinedMesh(2); GameObject goLod0 = new GameObject {name = prefabName + "_LOD0"}; goLod0.transform.SetParent(go.transform, false); goLod0.AddComponent().sharedMesh = meshLod0; goLod0.AddComponent().sharedMaterial = material; GameObject goLod1 = new GameObject {name = prefabName + "_LOD1"}; goLod1.transform.SetParent(go.transform, false); goLod1.AddComponent().sharedMesh = meshLod1; goLod1.AddComponent().sharedMaterial = material; GameObject goLod2 = new GameObject {name = prefabName + "_LOD2"}; goLod2.transform.SetParent(go.transform, false); goLod2.AddComponent().sharedMesh = meshLod2; goLod2.AddComponent().sharedMaterial = material; AssetDatabase.CreateAsset(meshLod0, meshFilenameLod0); AssetDatabase.CreateAsset(meshLod1, meshFilenameLod1); AssetDatabase.CreateAsset(meshLod2, meshFilenameLod2); LODGroup lodGroup = go.AddComponent(); LOD[] lods = new LOD[3]; lods[0] = CreateLOD(goLod0, 1f); lods[1] = CreateLOD(goLod1, 0.50f); lods[2] = CreateLOD(goLod2, 0.15f); //lods[3] = CreateLOD(goLod2, 0.05f); lodGroup.SetLODs(lods); transform.position = oldPosition; transform.rotation = oldRotation; #if UNITY_2018_3_OR_NEWER PrefabUtility.SaveAsPrefabAsset(go, prefabFilename); #else PrefabUtility.CreatePrefab(prefabFilename, go); #endif AssetDatabase.Refresh(); #endif } private LOD CreateLOD(GameObject go, float screenRelativeTransitionHeight) { Renderer[] renderers; if (go) { renderers = new Renderer[1]; renderers[0] = go.GetComponent(); } else { renderers = new Renderer[0]; } return new LOD(screenRelativeTransitionHeight, renderers); } } }