Last active
July 26, 2019 06:57
-
-
Save nofishleft/057170cba135ae9d71d8ce99face1306 to your computer and use it in GitHub Desktop.
Multithreaded PerlinNoise & Runtime Mesh Editing Alternative
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Uses Coroutines & LateUpdate to do processing over a larger percentage of the game loop | |
//Uses WaitForEndOfFrame (called after rending before next frame) instead of Update | |
// and LateUpdate (called before rendering) | |
// see: https://docs.unity3d.com/Manual/ExecutionOrder.html | |
using System.Collections; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using UnityEngine; | |
[RequireComponent(typeof(MeshFilter))] | |
[RequireComponent(typeof(MeshRenderer))] | |
public class PerlinMesh : MonoBehaviour | |
{ | |
public Vector3 pos; | |
public Mesh m; | |
public MeshFilter f; | |
public MeshRenderer r; | |
public int R; | |
void Start() | |
{ | |
m = new Mesh(); | |
int R1 = R + 1; | |
Vector3[] vertices = new Vector3[R1 * R1]; | |
for (int x = 0; x < R1; ++x) | |
{ | |
for (int y = 0; y < R1; ++y) | |
{ | |
vertices[x * R1 + y] = new Vector3(x - R / 2f, 0, y - R / 2f); | |
} | |
} | |
m.vertices = vertices; | |
int[] triangles = new int[6 * R * R]; | |
for (int x = 0; x < R; ++x) | |
{ | |
for (int y = 0; y < R; ++y) | |
{ | |
triangles[(x * R + y) * 6] = x * R1 + y; | |
triangles[(x * R + y) * 6 + 1] = x * R1 + y + 1; | |
triangles[(x * R + y) * 6 + 2] = (x + 1) * R1 + y; | |
triangles[(x * R + y) * 6 + 3] = (x + 1) * R1 + y; | |
triangles[(x * R + y) * 6 + 4] = x * R1 + y + 1; | |
triangles[(x * R + y) * 6 + 5] = (x + 1) * R1 + y + 1; | |
} | |
} | |
m.triangles = triangles; | |
Vector3[] normals = new Vector3[vertices.Length]; | |
for (int i = 0; i < normals.Length; ++i) | |
{ | |
normals[i] = Vector3.up; | |
} | |
m.normals = normals; | |
if (f == null) f = GetComponent<MeshFilter>() ?? gameObject.AddComponent<MeshFilter>(); | |
if (r == null) r = GetComponent<MeshRenderer>() ?? gameObject.AddComponent<MeshRenderer>(); | |
f.sharedMesh = m; | |
pos.x += Random.Range(0f, 100f); | |
pos.z += Random.Range(0f, 100f); | |
Debug.Log("Starting Coroutine"); | |
StartCoroutine(nameof(EndOfRendering)); | |
Debug.Log("Starting Thread"); | |
thread = new Thread(ThreadLoop); | |
thread.Start(); | |
} | |
bool _looping = true; | |
Thread thread; | |
//We start processing at the end of rendering of the first frame | |
//So we initialize true to stop main thread from freezing during the first frame | |
private readonly EventWaitHandle _processedHandle = new EventWaitHandle(true, EventResetMode.AutoReset); | |
private readonly EventWaitHandle _startHandle = new EventWaitHandle(false, EventResetMode.AutoReset); | |
public void ThreadLoop() | |
{ | |
while (_looping) | |
{ | |
MeshUpdater(); | |
} | |
} | |
IEnumerator EndOfRendering() | |
{ | |
while (_looping) | |
{ | |
//Start | |
pos.x += Time.deltaTime; | |
pos.z += Time.deltaTime; | |
//Fetch mesh vertices | |
vertices = m.vertices; | |
//Signal thread to start updating mesh vertices | |
Debug.Log("Set start handle"); | |
_startHandle.Set(); | |
//Waits until Rendering of current frame is finished | |
// (before physics and update of next frame) | |
Debug.Log("Waiting for next frame"); | |
yield return new WaitForEndOfFrame(); | |
} | |
} | |
Vector3[] vertices; | |
/// <summary> | |
/// Updates the mesh vertices in the background. | |
/// </summary> | |
/// | |
/// FYI: Should never operate directly on Unity Object's in secondary threads | |
public void MeshUpdater() | |
{ | |
Debug.Log("Waiting for start of frame signal"); | |
//Wait for start of frame/update | |
_startHandle.WaitOne(); | |
Debug.Log("Received start of frame signal"); | |
Parallel.For(0, vertices.Length, (i) => { | |
vertices[i].y = | |
Mathf.PerlinNoise(vertices[i].x + pos.x, vertices[i].z + pos.z); | |
}); | |
Debug.Log("Set processing handle"); | |
//Signal that we are finished updating the vertices in the secondary thread | |
_processedHandle.Set(); | |
} | |
//Called just before rendering | |
void LateUpdate() | |
{ | |
//Wait for mesh updating to finish | |
Debug.Log("Waiting for processing signal"); | |
_processedHandle.WaitOne(); | |
Debug.Log("Received processing signal"); | |
//Assign vertices to mesh object | |
m.vertices = vertices; | |
m.RecalculateNormals(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment