Advertisement
JontePonte

Single threaded meshgen

Oct 5th, 2024
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 16.41 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.Rendering;
  5. using System.Threading.Tasks;
  6. using Unity.Mathematics;
  7. using Unity.Jobs;
  8. using Unity.Collections;
  9. using Unity.Burst;
  10. using UnityEngine.Profiling;
  11. using System.Diagnostics;
  12. using System.Xml.Serialization;
  13.  
  14. public class MeshGenerator : MonoBehaviour
  15. {
  16.     public static Vector2Int chunkDimensions = new Vector2Int(32, 192);
  17.  
  18.     public Material material;
  19.     public ComputeShader voxelGridShader;
  20.  
  21.     public int brushSize;
  22.     public float brushStrength;
  23.  
  24.     public int lod;
  25.     public int maxLod;
  26.  
  27.     public int maxChunksProcessing;
  28.  
  29.     public float surfaceLevel;
  30.  
  31.     public bool weldedVertices;
  32.     public bool hideMeshOnPlay;
  33.     public bool interpolate;
  34.     public bool stopWatch;
  35.  
  36.  
  37.     [Header("Noise Settings")]
  38.     public float heightMultiplier;
  39.     public int curveResolution;
  40.     public AnimationCurve heightCurve;
  41.     public float frequency;
  42.     public float lacunarity;
  43.     public float persistance;
  44.     public int octaves;
  45.     public float density;
  46.     public float warpingStrength;
  47.     public float warpingFrequency;
  48.     public Vector2 offset;
  49.  
  50.     [Header("")]
  51.     public bool autoUpdate;
  52.     public string executionTime;
  53.  
  54.     NativeArray<int> nativeTriTable;
  55.     NativeArray<int> nativeCaseToNumPolys;
  56.  
  57.     Camera cam;
  58.  
  59.     VoxelGrid voxelGrid;
  60.  
  61.     List<ChunkData> chunksToProcess;
  62.     int chunksProcessing;
  63.  
  64.     private void Start()
  65.     {
  66.         Init();
  67.  
  68.         if (GameObject.Find("Mesh") != null)
  69.         {
  70.             GameObject.Find("Mesh").SetActive(!hideMeshOnPlay);
  71.         }
  72.  
  73.     }
  74.  
  75.     private void Update()
  76.     {
  77.         if (Input.GetKeyDown(KeyCode.Space))
  78.         {
  79.             EditorMesh();
  80.         }
  81.  
  82.         //Limit the number of chunks that can be processed at once, preventing lag spikes
  83.         for (int i = 0; i < chunksToProcess.Count; i++)
  84.         {
  85.             if(chunksProcessing < maxChunksProcessing)
  86.             {
  87.                 ProcessChunk(chunksToProcess[i]);
  88.                 chunksProcessing++;
  89.                 chunksToProcess.RemoveAt(i);
  90.             }
  91.         }
  92.     }
  93.  
  94.     public GameObject ChunkObject(Vector2 position)
  95.     {
  96.         GameObject chunk = new GameObject("Chunk");
  97.         chunk.AddComponent<MeshFilter>().mesh = ChunkMesh(position, 0);
  98.         chunk.AddComponent<MeshRenderer>().material = material;
  99.         chunk.GetComponent<MeshRenderer>().receiveShadows = false;
  100.         chunk.GetComponent<MeshRenderer>().shadowCastingMode = ShadowCastingMode.Off;
  101.  
  102.         return chunk;
  103.     }
  104.  
  105.     public Mesh ChunkMesh(Vector2 position, int lod)
  106.     {
  107.         Mesh mesh = new Mesh();
  108.         mesh.indexFormat = IndexFormat.UInt32;
  109.  
  110.         lod = lod == 0 ? 1 : lod * 2;
  111.  
  112.         chunksToProcess.Add(new ChunkData
  113.         {
  114.             mesh = mesh,
  115.             position = position,
  116.             lod = lod
  117.         });
  118.  
  119.         return mesh;
  120.     }
  121.  
  122.     public void EditorMesh()
  123.     {
  124.         Init();
  125.  
  126.         Mesh mesh = ChunkMesh(offset / 10, lod);
  127.         ProcessChunk(chunksToProcess[0]);
  128.         chunksToProcess.Clear();
  129.  
  130.         if (GameObject.Find("Mesh") == null)
  131.         {
  132.             GameObject obj = new GameObject("Mesh");
  133.             obj.transform.parent = transform;
  134.             obj.AddComponent<MeshFilter>().mesh = mesh;
  135.             obj.AddComponent<MeshRenderer>().material = material;
  136.             obj.AddComponent<MeshCollider>();
  137.         }
  138.  
  139.         else
  140.         {
  141.             var mf = GameObject.Find("Mesh").GetComponent<MeshFilter>();
  142.             var collider = GameObject.Find("Mesh").GetComponent<MeshCollider>();
  143.  
  144.             mf.mesh = mesh;
  145.             collider.sharedMesh = null;
  146.             collider.sharedMesh = mesh;
  147.         }
  148.     }
  149.  
  150.     void Init()
  151.     {
  152.         chunksToProcess = new();
  153.         cam = Camera.main;
  154.  
  155.         nativeTriTable = new NativeArray<int>(256 * 15, Allocator.Persistent);
  156.         nativeCaseToNumPolys = new NativeArray<int>(256, Allocator.Persistent);
  157.  
  158.  
  159.         for (int i = 0; i < Tables.triTable.Length; i++)
  160.         {
  161.             nativeCaseToNumPolys[i] = Tables.caseToNumPolys[i];
  162.  
  163.             for (int j = 0; j < 15; j++)
  164.             {
  165.                 nativeTriTable[(i * 15) + j] = Tables.triTable[i][j];
  166.             }
  167.         }
  168.  
  169.         voxelGrid = new VoxelGrid(frequency, lacunarity, persistance, octaves, surfaceLevel, density, heightMultiplier, warpingFrequency, warpingStrength, curveResolution, heightCurve, voxelGridShader);
  170.     }
  171.  
  172.     void ProcessChunk(ChunkData cd)
  173.     {
  174.         //Getting back data from the GPU is slow, so first the voxel grid is requested then
  175.         //whenever the data has reached the CPU the mesh is constructed
  176.         Profiler.BeginSample("Requesting Voxel grid");
  177.         voxelGrid.RequestVoxelGrid(cd.position, cd.lod, out ShaderData shaderData);
  178.         Profiler.EndSample();
  179.  
  180.         Stopwatch watch = new Stopwatch();
  181.         watch.Start();
  182.         AsyncGPUReadback.Request(shaderData.voxelGridBuffer);
  183.         CompleteReadBack(shaderData, cd.mesh, cd.lod, watch);
  184.  
  185.         //    AsyncGPUReadback.Request(shaderData.voxelGridBuffer, request =>
  186.         //    {
  187.         //        if (request.hasError)
  188.         //        {
  189.         //            UnityEngine.Debug.LogWarning("GPU readback error detected.");
  190.         //        }
  191.         //        else
  192.         //        {
  193.         //            watch.Stop();
  194.         //            if (stopWatch)
  195.         //                UnityEngine.Debug.Log("Getting voxelGrid took " + watch.ElapsedMilliseconds + " ms");
  196.  
  197.         //            Profiler.BeginSample("Readback after completion");
  198.         //            NativeArray<float> voxelGrid = request.GetData<float>();
  199.         //            ConstructMesh(voxelGrid, cd.mesh, cd.lod);
  200.         //            Profiler.EndSample();
  201.         //        }
  202.         //    });
  203.  
  204.         //    //CompleteReadBack(shaderData, cd.mesh, cd.lod, watch);
  205.         //}
  206.  
  207.     }
  208.  
  209.     async void CompleteReadBack(ShaderData sd, Mesh mesh, int lod, Stopwatch watch)
  210.     {
  211.         var request = AsyncGPUReadback.Request(sd.voxelGridBuffer);
  212.  
  213.         // Wait for the request to complete
  214.         while (!request.done)
  215.         {
  216.             await Task.Yield();
  217.         }
  218.  
  219.         if (request.hasError)
  220.         {
  221.             UnityEngine.Debug.LogWarning("GPU readback error detected.");
  222.         }
  223.         else
  224.         {
  225.             watch.Stop();
  226.             if (stopWatch)
  227.             {
  228.                 UnityEngine.Debug.Log("Getting voxelGrid took " + watch.ElapsedMilliseconds + " ms");
  229.             }
  230.  
  231.             Profiler.BeginSample("Readback after completion");
  232.             //Get voxelGrid after readback is complete
  233.             sd.voxelGridBuffer.Release();
  234.             sd.noiseMap2DBuffer.Release();
  235.             sd.noiseMap2D.Dispose();
  236.  
  237.             float[] voxelGrid = request.GetData<float>().ToArray();
  238.             ConstructMesh(voxelGrid, mesh, lod);
  239.             Profiler.EndSample();
  240.         }
  241.     }
  242.  
  243.     async void ConstructMesh(float[] voxelGrid, Mesh mesh, int lod)
  244.     {
  245.         List<Vector3> vertices = new();
  246.         List<int> indices = new();
  247.         Dictionary<Vector3, int> vertDictionary = new();
  248.  
  249.         MeshData meshData = new MeshData
  250.         {
  251.             vertices = vertices,
  252.             indices = indices,
  253.             vertDictionary = vertDictionary,
  254.             voxelGrid = voxelGrid,
  255.             lod = lod
  256.         };
  257.  
  258.         //Offloads mesh creation to another thread so the main thread is not halted
  259.         await Task.Run(() =>
  260.         {
  261.             for (int z = 0, i = 0; z < chunkDimensions.x; z += lod)
  262.             {
  263.                 for (int y = 0; y < chunkDimensions.y; y += lod)
  264.                 {
  265.                     for (int x = 0; x < chunkDimensions.x; x += lod, i++)
  266.                     {
  267.                         AddVertices(i, new Vector3Int(x, y, z), meshData);
  268.                     }
  269.                 }
  270.             }
  271.         });
  272.  
  273.         mesh.vertices = vertices.ToArray();
  274.         mesh.triangles = indices.ToArray();
  275.         mesh.RecalculateNormals();
  276.  
  277.         chunksProcessing--;
  278.     }
  279.  
  280.     void AddVertices(int index, Vector3Int pos, MeshData md)
  281.     {
  282.         float[] voxelCornerValues = new float[8];
  283.  
  284.         for (int i = 0; i < 8; i++)
  285.         {
  286.             voxelCornerValues[i] = md.voxelGrid[(index * 8) + i];
  287.         }
  288.  
  289.         int triCase = CalculateCase(voxelCornerValues);
  290.         int[] edges = Tables.triTable[triCase];
  291.  
  292.         for (int j = 0, i = 0; j < Tables.caseToNumPolys[triCase]; j++, i += 3)
  293.         {
  294.             for (int h = 0; h < 3; h++)
  295.             {
  296.                 Vector3 vertex = GetEdgePosition(edges[i + (2 - h)], voxelCornerValues, md.lod, pos);
  297.  
  298.                 //Checks for duplicate vertices
  299.                 if (md.vertDictionary.TryGetValue(vertex, out int triIndex))
  300.                 {
  301.                     md.indices.Add(triIndex);
  302.                 }
  303.                 else
  304.                 {
  305.                     md.vertDictionary.Add(vertex, md.vertices.Count);
  306.                     md.vertices.Add(vertex);
  307.                     md.indices.Add(md.vertices.Count - 1);
  308.                 }
  309.             }
  310.         }
  311.     }
  312.  
  313.     Vector3 GetEdgePosition(int edge, float[] voxelData, int lod, Vector3Int pos)
  314.     {
  315.         int x = pos.x;
  316.         int y = pos.y;
  317.         int z = pos.z;
  318.  
  319.  
  320.         switch (edge)
  321.         {
  322.             case 0: return GetInterpolatedEdgePosition(x, y, z, voxelData[0], voxelData[1], 0, 1, 0);
  323.             case 1: return GetInterpolatedEdgePosition(x, y + lod, z, voxelData[1], voxelData[2], 1, 0, 0);
  324.             case 2: return GetInterpolatedEdgePosition(x + lod, y, z, voxelData[3], voxelData[2], 0, 1, 0);
  325.             case 3: return GetInterpolatedEdgePosition(x, y, z, voxelData[0], voxelData[3], 1, 0, 0);
  326.  
  327.             case 4: return GetInterpolatedEdgePosition(x, y, z + lod, voxelData[4], voxelData[5], 0, 1, 0);
  328.             case 5: return GetInterpolatedEdgePosition(x, y + lod, z + lod, voxelData[5], voxelData[6], 1, 0, 0);
  329.             case 6: return GetInterpolatedEdgePosition(x + lod, y, z + lod, voxelData[7], voxelData[6], 0, 1, 0);
  330.             case 7: return GetInterpolatedEdgePosition(x, y, z + lod, voxelData[4], voxelData[7], 1, 0, 0);
  331.  
  332.             case 8: return GetInterpolatedEdgePosition(x, y, z, voxelData[0], voxelData[4], 0, 0, 1);
  333.             case 9: return GetInterpolatedEdgePosition(x, y + lod, z, voxelData[1], voxelData[5], 0, 0, 1);
  334.             case 10: return GetInterpolatedEdgePosition(x + lod, y + lod, z, voxelData[2], voxelData[6], 0, 0, 1);
  335.             case 11: return GetInterpolatedEdgePosition(x + lod, y, z, voxelData[3], voxelData[7], 0, 0, 1);
  336.  
  337.             default: throw new ArgumentOutOfRangeException(nameof(edge), "Invalid edge index: " + edge);
  338.         }
  339.     }
  340.     Vector3 GetInterpolatedEdgePosition(int x, int y, int z, float voxelA, float voxelB, int offsetX, int offsetY, int offsetZ)
  341.     {
  342.         float interpolation = (0 - voxelA) / (voxelB - voxelA);
  343.  
  344.         return new Vector3(x + offsetX * interpolation, y + offsetY * interpolation, z + offsetZ * interpolation);
  345.     }
  346.  
  347.     int CalculateCase(float[] voxelData)
  348.     {
  349.         int triCase = 0;
  350.  
  351.         for (int i = 0; i < 8; i++)
  352.         {
  353.             int bit = voxelData[i] < 0 ? 1 : 0;
  354.             triCase |= bit << i;
  355.         }
  356.  
  357.         return triCase;
  358.     }
  359.  
  360.     struct MeshData
  361.     {
  362.         public List<Vector3> vertices;
  363.         public List<int> indices;
  364.         public Dictionary<Vector3, int> vertDictionary;
  365.         public float[] voxelGrid;
  366.         public int lod;
  367.     }
  368.  
  369.     struct ChunkData
  370.     {
  371.         public Mesh mesh;
  372.         public Vector2 position;
  373.         public int lod;
  374.     }
  375. }
  376.  
  377. [BurstCompile]
  378. struct MeshJob : IJobParallelFor
  379. {
  380.     [WriteOnly]
  381.     public NativeQueue<Triangle>.ParallelWriter triangles;
  382.  
  383.     [ReadOnly]
  384.     public NativeArray<float> voxelGrid;
  385.  
  386.     [ReadOnly]
  387.     public int lod;
  388.  
  389.     [ReadOnly]
  390.     public int2 chunkDimensions;
  391.  
  392.     [ReadOnly]
  393.     public bool interpolate;
  394.  
  395.     [ReadOnly]
  396.     public NativeArray<int> triTable;
  397.     [ReadOnly]
  398.     public NativeArray<int> caseToNumTris;
  399.  
  400.  
  401.     public void Execute(int i)
  402.     {
  403.         int z = i / (chunkDimensions.x / lod * chunkDimensions.y / lod) * lod;
  404.         int y = (i / (chunkDimensions.x / lod) * lod) - z / lod * chunkDimensions.y;
  405.         int x = i % (chunkDimensions.x / lod) * lod;
  406.  
  407.         AddTriangles(i, x, y, z);
  408.     }
  409.  
  410.     void AddTriangles(int index, int x, int y, int z)
  411.     {
  412.         int triCase = CalculateCase(index);
  413.  
  414.         int triIndex = 0;
  415.  
  416.         for (int i = 0; i < caseToNumTris[triCase]; i++)
  417.         {
  418.             int edge0 = triTable[(triCase * 15) + triIndex + 2];
  419.             float3 vertex0 = CalculateVertexPosition(edge0, index, lod, x, y, z);
  420.  
  421.             int edge1 = triTable[(triCase * 15) + triIndex + 1];
  422.             float3 vertex1 = CalculateVertexPosition(edge1, index, lod, x, y, z);
  423.  
  424.             int edge2 = triTable[(triCase * 15) + triIndex];
  425.             float3 vertex2 = CalculateVertexPosition(edge2, index, lod, x, y, z);
  426.  
  427.             triIndex += 3;
  428.  
  429.             triangles.Enqueue(new Triangle
  430.             {
  431.                 vertex0 = vertex0,
  432.                 vertex1 = vertex1,
  433.                 vertex2 = vertex2
  434.             });
  435.         }
  436.     }
  437.  
  438.     //Returns vertex position at on of the 12 edges of a cube
  439.     float3 CalculateVertexPosition(int edge, int index, int lod, int x, int y, int z)
  440.     {
  441.         int i = index * 8;
  442.  
  443.         switch (edge)
  444.         {
  445.             case 0:
  446.                 return interpolate ? InterpolatePosition(x, y, z, voxelGrid[i], voxelGrid[i + 1], 0, 1, 0) : new float3(x, y + 1, z);
  447.             case 1:
  448.                 return interpolate ? InterpolatePosition(x, y + lod, z, voxelGrid[i + 1], voxelGrid[i + 2], 1, 0, 0) : new float3(x + 1, y + lod, z);
  449.             case 2:
  450.                 return interpolate ? InterpolatePosition(x + lod, y, z, voxelGrid[i + 3], voxelGrid[i + 2], 0, 1, 0) : new float3(x + lod, y + 1, z);
  451.             case 3:
  452.                 return interpolate ? InterpolatePosition(x, y, z, voxelGrid[i], voxelGrid[i + 3], 1, 0, 0) : new float3(x + 1, y, z);
  453.  
  454.             case 4:
  455.                 return interpolate ? InterpolatePosition(x, y, z + lod, voxelGrid[i + 4], voxelGrid[i + 5], 0, 1, 0) : new float3(x, y + 1, z + lod);
  456.             case 5:
  457.                 return interpolate ? InterpolatePosition(x, y + lod, z + lod, voxelGrid[i + 5], voxelGrid[i + 6], 1, 0, 0) : new float3(x + 1, y + lod, z + lod);
  458.             case 6:
  459.                 return interpolate ? InterpolatePosition(x + lod, y, z + lod, voxelGrid[i + 7], voxelGrid[i + 6], 0, 1, 0) : new float3(x + lod, y + 1, z + lod);
  460.             case 7:
  461.                 return interpolate ? InterpolatePosition(x, y, z + lod, voxelGrid[i + 4], voxelGrid[i + 7], 1, 0, 0) : new float3(x + 1, y, z + lod);
  462.  
  463.             case 8:
  464.                 return interpolate ? InterpolatePosition(x, y, z, voxelGrid[i], voxelGrid[i + 4], 0, 0, 1) : new float3(x, y, z + 1);
  465.             case 9:
  466.                 return interpolate ? InterpolatePosition(x, y + lod, z, voxelGrid[i + 1], voxelGrid[i + 5], 0, 0, 1) : new float3(x, y + lod, z + 1);
  467.             case 10:
  468.                 return interpolate ? InterpolatePosition(x + lod, y + lod, z, voxelGrid[i + 2], voxelGrid[i + 6], 0, 0, 1) : new float3(x + lod, y + lod, z + 1);
  469.             case 11:
  470.                 return interpolate ? InterpolatePosition(x + lod, y, z, voxelGrid[i + 3], voxelGrid[i + 7], 0, 0, 1) : new float3(x + lod, y, z + 1);
  471.  
  472.             default:
  473.                 throw new ArgumentOutOfRangeException(nameof(edge), "Invalid edge index: " + edge);
  474.         }
  475.  
  476.     }
  477.  
  478.     public float3 InterpolatePosition(int x, int y, int z, float cornerValue0, float cornerValue1, int offsetX, int offsetY, int offsetZ)
  479.     {
  480.         float interpolation = (0 - cornerValue0) / (cornerValue1 - cornerValue0);
  481.  
  482.         return new float3(x + offsetX * interpolation, y + offsetY * interpolation, z + offsetZ * interpolation);
  483.     }
  484.  
  485.     public int CalculateCase(int index)
  486.     {
  487.         int triCase = 0;
  488.  
  489.         for (int i = 0; i < 8; i++)
  490.         {
  491.             int bit = voxelGrid[(index * 8) + i] < 0 ? 1 : 0;
  492.             triCase |= bit << i;
  493.         }
  494.  
  495.         return triCase;
  496.     }
  497. }
  498.  
  499. public struct Triangle
  500. {
  501.     public float3 vertex0;
  502.     public float3 vertex1;
  503.     public float3 vertex2;
  504. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement