using UnityEditor; using UnityEngine; using static System.Runtime.InteropServices.Marshal; [RequireComponent(typeof(MeshSurfaceSampler))] public class LeafInstancer : MonoBehaviour { [SerializeField] LayerMask _grassLayer; [SerializeField] Mesh _leafMesh; [SerializeField] Material _material; MeshSurfaceSampler _meshSurfaceSampler; RenderParams _renderParams; GraphicsBuffer commandBuffer; GraphicsBuffer.IndirectDrawIndexedArgs[] commandData; LeafData[] leafData; ComputeBuffer leafBuffer; const int commandCount = 1; struct LeafData { public Vector3 position; public Vector3 normal; } void OnEnable() { Setup(); } void OnDisable() { commandBuffer?.Release(); commandBuffer = null; leafBuffer?.Release(); leafBuffer = null; } void Setup() { _meshSurfaceSampler = GetComponent(); if (_meshSurfaceSampler == null) Debug.LogError("MeshGenerator is null"); var meshRenderer = GetComponent(); if (meshRenderer == null) Debug.LogError("MeshRenderer is null"); if (_material == null) Debug.LogError("Material is null"); if (_leafMesh == null) Debug.LogError("Grass mesh is null"); } public void GeneratePoints() { _meshSurfaceSampler.GeneratePoints(); } public Bounds GetBoundingBox() { Bounds boundingBox = new Bounds(); if (_meshSurfaceSampler.points != null && _meshSurfaceSampler.points.Length > 0) { Vector3 minPoint = _meshSurfaceSampler.points[0]; Vector3 maxPoint = _meshSurfaceSampler.points[0]; for (int i = 1; i < _meshSurfaceSampler.points.Length; i++) { minPoint = Vector3.Min(minPoint, _meshSurfaceSampler.points[i]); maxPoint = Vector3.Max(maxPoint, _meshSurfaceSampler.points[i]); } boundingBox.SetMinMax(minPoint, maxPoint); } var extrude = _material.GetFloat("_Extrude"); boundingBox.Expand(boundingBox.size * extrude); return boundingBox; } public void Generate() { var count = _meshSurfaceSampler.points.Length; // var count = 1; leafData = new LeafData[count]; leafBuffer?.Release(); leafBuffer = new ComputeBuffer(count, SizeOf()); commandBuffer?.Release(); commandBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, commandCount, GraphicsBuffer.IndirectDrawIndexedArgs.size); commandData = new GraphicsBuffer.IndirectDrawIndexedArgs[commandCount]; for (int i = 0; i < count; i++) { leafData[i] = new LeafData { position = _meshSurfaceSampler.points[i], normal = _meshSurfaceSampler.normals[i] }; } // Set the buffers var indirectDrawIndexedArgs = new GraphicsBuffer.IndirectDrawIndexedArgs { indexCountPerInstance = _leafMesh.GetIndexCount(0), instanceCount = (uint)count, startIndex = _leafMesh.GetIndexStart(0), baseVertexIndex = _leafMesh.GetBaseVertex(0), startInstance = 0, }; for (int i = 0; i < commandCount; i++) commandData[i] = indirectDrawIndexedArgs; commandBuffer.SetData(commandData); leafBuffer.SetData(leafData); // Set the render params var block = new MaterialPropertyBlock(); block.SetBuffer("_LeafData", leafBuffer); _renderParams = new RenderParams(_material) { layer = (int)Mathf.Log(_grassLayer.value, 2), worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one), matProps = block, receiveShadows = true, }; UpdateRotation(); } // Update the rotation of the grass void UpdateRotation() { var target = Camera.main.transform.position; var rotation = Quaternion.LookRotation(transform.position - target, Vector3.up); var quaternion = new Vector4(rotation.x, rotation.y, rotation.z, rotation.w); _renderParams.matProps.SetVector("_Rotation", quaternion); } void Update() { // Validation checks if (_meshSurfaceSampler == null) Setup(); if (!_meshSurfaceSampler.ArePointsGenerated()) _meshSurfaceSampler.GeneratePoints(); // if (transform.lossyScale != Vector3.one) // Debug.LogWarning("GrassInstancer does not support scaling"); if (leafData == null || leafData.Length == 0) Generate(); UpdateRotation(); // Frustum culling check for the chunk var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main); if (!GeometryUtility.TestPlanesAABB(frustumPlanes, GetBoundingBox())) return; // Render the grass Graphics.RenderMeshIndirect(_renderParams, _leafMesh, commandBuffer, commandCount); } } #if UNITY_EDITOR // Custom editor which adds a button to generate the terrain [CustomEditor(typeof(LeafInstancer))] public class LeafInstancerEditor : Editor { public override void OnInspectorGUI() { var script = (LeafInstancer)target; DrawDefaultInspector(); GUILayout.Space(10); if (GUILayout.Button("Generate Leaves")) { script.GeneratePoints(); script.Generate(); } } } #endif