192 lines
5.5 KiB
C#
192 lines
5.5 KiB
C#
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<MeshSurfaceSampler>();
|
|
if (_meshSurfaceSampler == null)
|
|
Debug.LogError("MeshGenerator is null");
|
|
|
|
var meshRenderer = GetComponent<MeshRenderer>();
|
|
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<LeafData>());
|
|
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
|