349 lines
9.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|