Files
2026-03-04 09:37:33 +08:00

349 lines
9.3 KiB
C#

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<VertexAttributeDescriptor> _vertexLayout;
private NativeArray<int> _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<MeshFilter>().sharedMesh = _targetMesh;
GenerateVertexLayout();
Mesh sharedMesh = SourceMeshFilter.sharedMesh;
SourceMeshFilter.GetComponent<MeshRenderer>().enabled = false;
sharedMesh.RecalculateTangents();
Vector3[] vertices = sharedMesh.vertices;
Vector3[] normals = sharedMesh.normals;
Color[] colors = sharedMesh.colors;
Vector4[] tangents = sharedMesh.tangents;
List<Vector4> list = new List<Vector4>();
List<Vector4> list2 = new List<Vector4>();
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<int>(_numVertices, Allocator.Persistent);
for (int i = 0; i < _numVertices; i++)
{
_indexArray[i] = i;
}
}
private void GenerateVertexLayout()
{
_vertexLayout = new NativeArray<VertexAttributeDescriptor>(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<int>()[0] * 3;
AsyncGPUReadback.Request(_drawBuffer, DrawBufferRead);
}
private void DrawBufferRead(AsyncGPUReadbackRequest drawBufferRequest)
{
if (drawBufferRequest.hasError)
{
DisposeBuffers();
return;
}
NativeArray<DrawVertex> data = drawBufferRequest.GetData<DrawVertex>();
GenerateMeshNative(data, _verticesCount);
if (Application.isPlaying)
{
StartCoroutine(WaitForCalculateLods());
}
}
private IEnumerator WaitForCalculateLods()
{
yield return new WaitForSeconds(RefreshTime);
UpdateLod();
}
private void GenerateMeshNative(NativeArray<DrawVertex> 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;
}
}
}