using System.Collections; using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using UnityEngine; using UnityEngine.Rendering; namespace NatureManufacture.RAM { [ExecuteInEditMode] public class GPULodManager : MonoBehaviour { private struct SourceVertex { public Vector3 position; public Vector3 normal; public Vector4 tangent; public Color color; public Vector4 uv; public Vector4 uv3; } private struct DrawVertex { private readonly float3 position; private readonly float3 normal; private readonly float4 tangent; private readonly Color color; private readonly float4 uv; private readonly float4 uv3; } [SerializeField] private MeshFilter sourceMeshFilter; [SerializeField] private float refreshTime = 0.1f; [SerializeField] private Vector4 lodDistance = new Vector4(100f, 80f, 50f, 20f); [SerializeField] private Transform cameraLod; [SerializeField] private ComputeShader computeShader; private Mesh _targetMesh; private bool _initialized; private ComputeBuffer _sourceVertBuffer; private ComputeBuffer _sourceTriBuffer; private ComputeBuffer _drawBuffer; private ComputeBuffer _countBuffer; private int _idPyramidKernel; private int _idTriToVertKernel; private int _dispatchSize; private Bounds _localBounds; private int _numTriangles; private int _verticesCount; private NativeArray _vertexLayout; private NativeArray _indexArray; private Vector3 _cameraPosition; private float _executeTime; private static readonly int SourceVertices = Shader.PropertyToID("_SourceVertices"); private static readonly int SourceTriangles = Shader.PropertyToID("_SourceTriangles"); private static readonly int DrawTriangles = Shader.PropertyToID("_DrawTriangles"); private static readonly int NumSourceTriangles = Shader.PropertyToID("_NumSourceTriangles"); private static readonly int LocalToWorld = Shader.PropertyToID("_LocalToWorld"); private static readonly int Distance = Shader.PropertyToID("_LODDistance"); private static readonly int CameraPosition = Shader.PropertyToID("_CameraPosition"); private int _numVertices; private SourceVertex[] _vertices; private int[] _tris; private const int SourceVertStride = 88; private const int SourceTriStride = 4; private const int DrawStride = 88; private const int CountStride = 4; private const float MinRefreshTime = 0.01f; private const MeshUpdateFlags DontNotifyMeshUsers = MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers | MeshUpdateFlags.DontRecalculateBounds; public MeshFilter SourceMeshFilter { get { return sourceMeshFilter; } set { sourceMeshFilter = value; } } public Vector4 LODDistance { get { return lodDistance; } set { lodDistance = value; } } public float RefreshTime { get { return refreshTime; } set { refreshTime = value; } } private void OnEnable() { SetupLod(); } private void SetupLod() { if (!cameraLod && Camera.main != null) { cameraLod = Camera.main.transform; } if (_initialized) { OnDisable(); } _initialized = true; _targetMesh = new Mesh(); GetComponent().sharedMesh = _targetMesh; GenerateVertexLayout(); Mesh sharedMesh = SourceMeshFilter.sharedMesh; SourceMeshFilter.GetComponent().enabled = false; sharedMesh.RecalculateTangents(); Vector3[] vertices = sharedMesh.vertices; Vector3[] normals = sharedMesh.normals; Color[] colors = sharedMesh.colors; Vector4[] tangents = sharedMesh.tangents; List list = new List(); List list2 = new List(); sharedMesh.GetUVs(0, list); sharedMesh.GetUVs(3, list2); _tris = sharedMesh.triangles; _vertices = new SourceVertex[vertices.Length]; for (int i = 0; i < _vertices.Length; i++) { _vertices[i] = new SourceVertex { position = vertices[i], normal = ((normals.Length != 0) ? normals[i] : Vector3.up), tangent = tangents[i], color = ((colors.Length != 0) ? colors[i] : Color.black), uv = list[i], uv3 = ((list2.Count > 0) ? list2[i] : list[i]) }; } _numTriangles = _tris.Length / 3; _numVertices = _tris.Length * 4 * 4 * 4 * 4; GenerateIndexArray(); _localBounds = sharedMesh.bounds; SetupBuffers(); UpdateLod(); } private void GenerateIndexArray() { _indexArray = new NativeArray(_numVertices, Allocator.Persistent); for (int i = 0; i < _numVertices; i++) { _indexArray[i] = i; } } private void GenerateVertexLayout() { _vertexLayout = new NativeArray(6, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); _vertexLayout[0] = new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3, 0); _vertexLayout[1] = new VertexAttributeDescriptor(VertexAttribute.Normal); _vertexLayout[2] = new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4); _vertexLayout[3] = new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4); _vertexLayout[4] = new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 4); _vertexLayout[5] = new VertexAttributeDescriptor(VertexAttribute.TexCoord3, VertexAttributeFormat.Float32, 4); } private void SetupBuffers() { _sourceVertBuffer?.Release(); _sourceTriBuffer?.Release(); _drawBuffer?.Release(); _countBuffer?.Release(); _sourceVertBuffer = new ComputeBuffer(_vertices.Length, 88, ComputeBufferType.Structured, ComputeBufferMode.Immutable); _sourceVertBuffer.SetData(_vertices); _sourceTriBuffer = new ComputeBuffer(_tris.Length, 4, ComputeBufferType.Structured, ComputeBufferMode.Immutable); _sourceTriBuffer.SetData(_tris); _drawBuffer = new ComputeBuffer(_numVertices, 88, ComputeBufferType.Append); _drawBuffer.SetCounterValue(0u); _countBuffer = new ComputeBuffer(1, 4, ComputeBufferType.IndirectArguments); computeShader = Object.Instantiate(computeShader); _idPyramidKernel = computeShader.FindKernel("Main"); computeShader.SetBuffer(_idPyramidKernel, SourceVertices, _sourceVertBuffer); computeShader.SetBuffer(_idPyramidKernel, SourceTriangles, _sourceTriBuffer); computeShader.SetBuffer(_idPyramidKernel, DrawTriangles, _drawBuffer); computeShader.SetInt(NumSourceTriangles, _numTriangles); computeShader.GetKernelThreadGroupSizes(_idPyramidKernel, out var x, out var _, out var _); _dispatchSize = Mathf.CeilToInt((float)_numTriangles / (float)x); } private void UpdateLod() { if (!(cameraLod == null)) { _cameraPosition = cameraLod.position; if (_drawBuffer == null || _countBuffer == null || !_drawBuffer.IsValid() || !_countBuffer.IsValid()) { DisposeBuffers(); return; } _drawBuffer.SetCounterValue(0u); computeShader.SetMatrix(LocalToWorld, base.transform.localToWorldMatrix); computeShader.SetVector(Distance, LODDistance); computeShader.SetVector(CameraPosition, base.transform.InverseTransformPoint(_cameraPosition)); computeShader.Dispatch(_idPyramidKernel, _dispatchSize, 1, 1); ComputeBuffer.CopyCount(_drawBuffer, _countBuffer, 0); AsyncGPUReadback.Request(_countBuffer, CountBufferRead); } } private void CountBufferRead(AsyncGPUReadbackRequest countBufferRequest) { if (countBufferRequest.hasError || _drawBuffer == null || !_drawBuffer.IsValid()) { DisposeBuffers(); return; } _verticesCount = countBufferRequest.GetData()[0] * 3; AsyncGPUReadback.Request(_drawBuffer, DrawBufferRead); } private void DrawBufferRead(AsyncGPUReadbackRequest drawBufferRequest) { if (drawBufferRequest.hasError) { DisposeBuffers(); return; } NativeArray data = drawBufferRequest.GetData(); GenerateMeshNative(data, _verticesCount); if (Application.isPlaying) { StartCoroutine(WaitForCalculateLods()); } } private IEnumerator WaitForCalculateLods() { yield return new WaitForSeconds(RefreshTime); UpdateLod(); } private void GenerateMeshNative(NativeArray data, int verticesCount) { if (_vertexLayout.IsCreated) { _targetMesh.SetIndexBufferParams(verticesCount, IndexFormat.UInt32); _targetMesh.SetVertexBufferParams(verticesCount, _vertexLayout); _targetMesh.SetVertexBufferData(data, 0, 0, verticesCount); _targetMesh.SetIndexBufferData(_indexArray, 0, 0, verticesCount); _targetMesh.subMeshCount = 1; _targetMesh.SetSubMesh(0, new SubMeshDescriptor(0, verticesCount) { bounds = _localBounds, vertexCount = verticesCount }, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers | MeshUpdateFlags.DontRecalculateBounds); _targetMesh.bounds = _localBounds; } } private void OnDisable() { DisposeBuffers(); } private void DisposeBuffers() { if (_initialized) { _sourceVertBuffer?.Release(); _sourceTriBuffer?.Release(); _drawBuffer?.Release(); _countBuffer?.Release(); if (_vertexLayout.IsCreated) { _vertexLayout.Dispose(); } if (_indexArray.IsCreated) { _indexArray.Dispose(); } } _initialized = false; } } }