Мигает mesh при мультипотоке c# unity 2020
При изменении ландшафта редактируется mesh через еще один поток (потому что 12ms такое себе) И вот вроде без многопотока все было нормально, но когда добавил вычисление на новом потоке. Mesh начинает мигать.
Gif: https://imgur.com/Sz9Qoac (прямо тут не могу вставить потому что >2mb)
Скрипт:
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
/*Thanks to: b3agz*/
/* https://www.youtube.com/channel/UC3Ej26l1kXBPIq0fEEMwxQw */
public class Chunk
{
public GameObject chunkObject;
MeshFilter meshFilter;
MeshCollider meshCollider;
MeshRenderer meshRenderer;
WorldGenerator wg;
bool clampPosition = true;
Thread aThread;
public bool DataComplete = false;
[SerializeField] bool threadWork = false;
[SerializeField] bool threadQueue = false;
Vector3Int chunkPosition;
float[,,] terrainMap;
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
int width { get { return CubeData.ChunkWidth; } }
int height { get { return CubeData.ChunkHeight; } }
float terrainSurface { get { return CubeData.terrainSurface; } }
public Chunk(Vector3Int _position)
{
chunkObject = new GameObject();
chunkObject.name = string.Format("Chunk {0}, {1}", _position.x, _position.z);
chunkPosition = _position;
chunkObject.transform.position = chunkPosition;
meshFilter = chunkObject.AddComponent<MeshFilter>();
meshCollider = chunkObject.AddComponent<MeshCollider>();
meshRenderer = chunkObject.AddComponent<MeshRenderer>();
meshRenderer.material = Resources.Load<Material>("Materials/NoTextureMaterial");
wg = GameObject.FindGameObjectWithTag("World").GetComponent<WorldGenerator>();
chunkObject.transform.tag = "Terrain";
terrainMap = new float[width + 1, height + 1, width + 1];
ClearMeshData();
PopulateTerrainMap();
CreateMeshData();
}
void CreateMeshData()
{
ClearMeshData();
aThread = new Thread(() => BuildMeshDataForMesh());
aThread.Start();
BuildMesh();
}
void BuildMeshDataForMesh()
{
threadWork = true;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < width; z++)
{
MarchCube(new Vector3Int(x, y, z));
}
}
}
threadWork = false;
DataComplete = true;
}
void PopulateTerrainMap()
{
// The data points for terrain are stored at the corners of our "cubes", so the terrainMap needs to be 1 larger
// than the width/height of our mesh.
for (int x = 0; x < width + 1; x++)
{
for (int z = 0; z < width + 1; z++)
{
for (int y = 0; y < height + 1; y++)
{
// Get a terrain height using regular old Perlin noise.
float thisHeight = CubeData.GetTerrainHeight(x + chunkPosition.x, z + chunkPosition.z);
// Set the value of this point in the terrainMap.
terrainMap[x, y, z] = (float)y - thisHeight;
}
}
}
}
void MarchCube(Vector3Int position)
{
// Sample terrain values at each corner of the cube.
float[] cube = new float[8];
for (int i = 0; i < 8; i++)
{
cube[i] = SampleTerrain(position + CubeData.CornerTable[i]);
}
// Get the configuration index of this cube.
int configIndex = GetCubeConfiguration(cube);
// If the configuration of this cube is 0 or 255 (completely inside the terrain or completely outside of it) we don't need to do anything.
if (configIndex == 0 || configIndex == 255)
return;
// Loop through the triangles. There are never more than 5 triangles to a cube and only three vertices to a triangle.
int edgeIndex = 0;
for (int i = 0; i < 5; i++)
{
for (int p = 0; p < 3; p++)
{
// Get the current indice. We increment triangleIndex through each loop.
int indice = CubeData.TriangleTable[configIndex, edgeIndex];
// If the current edgeIndex is -1, there are no more indices and we can exit the function.
if (indice == -1)
return;
// Get the vertices for the start and end of this edge.
Vector3 vert1 = position + CubeData.CornerTable[CubeData.EdgeIndexes[indice, 0]];
Vector3 vert2 = position + CubeData.CornerTable[CubeData.EdgeIndexes[indice, 1]];
Vector3 vertPosition;
{
// Get the terrain values at either end of our current edge from the cube array created above.
float vert1Sample = cube[CubeData.EdgeIndexes[indice, 0]];
float vert2Sample = cube[CubeData.EdgeIndexes[indice, 1]];
// Calculate the difference between the terrain values.
float difference = vert2Sample - vert1Sample;
// If the difference is 0, then the terrain passes through the middle.
if (difference == 0)
difference = terrainSurface;
else
difference = (terrainSurface - vert1Sample) / difference;
// Calculate the point along the edge that passes through.
vertPosition = vert1 + ((vert2 - vert1) * difference);
}
// Add to our vertices and triangles list and incremement the edgeIndex.
vertices.Add(vertPosition);
triangles.Add(vertices.Count - 1);
edgeIndex++;
}
}
}
int GetCubeConfiguration(float[] cube)
{
// Starting with a configuration of zero, loop through each point in the cube and check if it is below the terrain surface.
int configurationIndex = 0;
for (int i = 0; i < 8; i++)
{
// If it is, use bit-magic to the set the corresponding bit to 1. So if only the 3rd point in the cube was below
// the surface, the bit would look like 00100000, which represents the integer value 32.
if (cube[i] > terrainSurface)
configurationIndex |= 1 << i;
}
return configurationIndex;
}
public void PlaceTerrain(Vector3 pos, int brushSize)
{
if (!aThread.IsAlive)
{
for (int x = 0; x < brushSize; x++)
{
for (int y = 0; y < brushSize; y++)
{
for (int z = 0; z < brushSize; z++)
{
float halfSize = brushSize / 2;
Vector3 dropPos = new Vector3((pos.x + x) - halfSize, (pos.y + y) - halfSize, (pos.z + z) - halfSize);
Vector3Int v3Int = new Vector3Int(Mathf.CeilToInt(dropPos.x), Mathf.CeilToInt(dropPos.y), Mathf.CeilToInt(dropPos.z));
v3Int -= chunkPosition;
dropPos -= chunkPosition;
if (clampPosition)
{
if (dropPos.x + 1 >= 0 && dropPos.x <= terrainMap.GetLength(0) - 1 && dropPos.y + 1 >= 0 && dropPos.y <= terrainMap.GetLength(1) - 1 && dropPos.z + 1 >= 0 && dropPos.z <= terrainMap.GetLength(2) - 1)
{
terrainMap[v3Int.x, v3Int.y, v3Int.z] = 0f;
}
else
{
if (dropPos.x + 1 >= 0 && dropPos.x <= terrainMap.GetLength(0) - 1)
{
}
}
}
else
{
terrainMap[v3Int.x, v3Int.y, v3Int.z] = 0f;
}
}
}
}
CreateMeshData();
}
}
public void RemoveTerrain(Vector3 pos, int brushSize)
{
if (!aThread.IsAlive)
{
for (int x = 0; x < brushSize; x++)
{
for (int y = 0; y < brushSize; y++)
{
for (int z = 0; z < brushSize; z++)
{
float halfSize = brushSize / 2;
Vector3 dropPos = new Vector3((pos.x + x) - halfSize, (pos.y + y) - halfSize, (pos.z + z) - halfSize);
Vector3Int v3Int = new Vector3Int(Mathf.CeilToInt(dropPos.x), Mathf.CeilToInt(dropPos.y), Mathf.CeilToInt(dropPos.z));
v3Int -= chunkPosition;
dropPos -= chunkPosition;
if (clampPosition)
{
if (dropPos.x + 1 >= 0 && dropPos.x <= terrainMap.GetLength(0) - 1)
{
if (dropPos.y + 1 >= 0 && dropPos.y <= terrainMap.GetLength(1) - 1)
{
if (dropPos.z + 1 >= 0 && dropPos.z <= terrainMap.GetLength(2) - 1)
{
terrainMap[v3Int.x, v3Int.y, v3Int.z] = 1f;
}
}
}
}
else
{
terrainMap[v3Int.x, v3Int.y, v3Int.z] = 1f;
}
}
}
}
CreateMeshData();
}
}
float SampleTerrain(Vector3Int point)
{
return terrainMap[point.x, point.y, point.z];
}
void ClearMeshData()
{
vertices = new List<Vector3>();
triangles = new List<int>();
}
public void BuildMesh()
{
if(vertices != null && triangles != null)
{
if (triangles.Count % 3 == 0)
{
Mesh mesh = new Mesh();
Vector3[] vertNew = new Vector3[vertices.Count];
int[] triNew = new int[triangles.Count];
Array.Copy(vertices.ToArray(), vertNew, vertices.Count);
Array.Copy(triangles.ToArray(), triNew, triangles.Count);
mesh.vertices = vertNew;
mesh.triangles = triNew;
mesh.RecalculateNormals();
meshFilter.mesh = mesh;
meshCollider.sharedMesh = mesh;
}
else
{
LogWriter.Log("ERROR: Chunk triangles % 3 == 0");
}
}
else
{
LogWriter.Log("ERROR: Chunk triangles or verices == null");
}
}
public void ConstUpdate()
{
if (threadQueue)
{
if (!threadWork)
{
BuildMesh();
threadQueue = false;
}
}
}
public void CallChunkUpdate()
{
threadQueue = true;
}
}