- euclidean (left), manhattan (right)
Last active
November 9, 2023 22:11
-
-
Save andrew-raphael-lukasik/cc9d61edbbb44ecb4956c6cb69363a8e to your computer and use it in GitHub Desktop.
How to create a Voronoi diagram texture, IJobParallelFor + Burst
This file contains hidden or 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
| // web* src = https://gist.github.com/andrew-raphael-lukasik/cc9d61edbbb44ecb4956c6cb69363a8e | |
| using UnityEngine; | |
| using Unity.Mathematics; | |
| using Unity.Jobs; | |
| using Unity.Collections; | |
| using Unity.Profiling; | |
| [ExecuteInEditMode] | |
| public class VoronoiTextureBurstJobComponent : MonoBehaviour | |
| { | |
| [SerializeField][Min(1)] uint _seed = 769; | |
| [SerializeField][Range(2,512)] int _width = 300, _height = 300; | |
| [SerializeField][Range(1,64)] int _numRandomPoints = 3; | |
| Texture2D _texture; | |
| NativeList<ushort2> _points; | |
| uint lastSeedUsed = 0; | |
| void OnValidate () | |
| { | |
| bool inputChanged = false; | |
| if( !_points.IsCreated ) | |
| { | |
| _points = new NativeList<ushort2>( Allocator.Persistent ); | |
| inputChanged = true; | |
| } | |
| if( _points.Length!=_numRandomPoints ) | |
| { | |
| _points.Length = _numRandomPoints; | |
| inputChanged = true; | |
| } | |
| if( _texture==null || _texture.width!=_width || _texture.height!=_height ) | |
| { | |
| if( _texture==null ) DestroyItWillYou( _texture ); | |
| _texture = new Texture2D( _width , _height , TextureFormat.RGBA32 , mipCount:0 , linear:true ); | |
| inputChanged = true; | |
| } | |
| if( inputChanged || _seed!=lastSeedUsed ) | |
| { | |
| lastSeedUsed = _seed; | |
| var watch = System.Diagnostics.Stopwatch.StartNew(); | |
| var dep = new FillRandomPointsJob{ | |
| TextureWidth = _width , | |
| TextureHeight = _height , | |
| Seed = _seed , | |
| Points = _points , | |
| }.Schedule(); | |
| NativeArray<RGBA32> textureData = _texture.GetRawTextureData<RGBA32>(); | |
| new GenerateVoronoiDiagramJob{ | |
| Points = _points , | |
| TextureWidth = _width , | |
| TextureHeight = _height , | |
| TextureOutput = textureData , | |
| }.Schedule( textureData.Length , _width , dep ).Complete(); | |
| _texture.Apply(); | |
| Debug.Log( $"jobs took {watch.ElapsedMilliseconds} [ms] to complete"); | |
| } | |
| } | |
| void OnEnable () | |
| => OnValidate(); | |
| void OnDisable () | |
| { | |
| if( _texture!=null ) DestroyItWillYou(_texture); | |
| if( _points.IsCreated ) _points.Dispose(); | |
| } | |
| void OnGUI () | |
| => GUI.DrawTexture( new Rect(0,0,_width,_height) , _texture ); | |
| void DestroyItWillYou ( Object obj ) | |
| { | |
| if( Application.isPlaying ) Destroy( obj ); | |
| else DestroyImmediate( obj ); | |
| } | |
| [Unity.Burst.BurstCompile] | |
| struct FillRandomPointsJob : IJob | |
| { | |
| public int TextureWidth, TextureHeight; | |
| public uint Seed; | |
| [WriteOnly] public NativeArray<ushort2> Points; | |
| void IJob.Execute () | |
| { | |
| var rnd = new Unity.Mathematics.Random( Seed ); | |
| int len = Points.Length; | |
| for( int i=0 ; i<len ; i++ ) | |
| Points[i] = new ushort2{ x=(ushort)rnd.NextInt(TextureWidth) , y=(ushort)rnd.NextInt(TextureHeight) }; | |
| } | |
| } | |
| [Unity.Burst.BurstCompile] | |
| struct GenerateVoronoiDiagramJob : IJobParallelFor | |
| { | |
| [ReadOnly] public NativeArray<ushort2> Points; | |
| public int TextureWidth, TextureHeight; | |
| [WriteOnly] public NativeArray<RGBA32> TextureOutput; | |
| void IJobParallelFor.Execute ( int jobIndex ) | |
| { | |
| float2 pixelPos = new int2{ x=jobIndex%TextureWidth , y=jobIndex/TextureWidth }; | |
| float pointDist = float.MaxValue; | |
| int pointIndex = -1; | |
| for( int i=Points.Length-1 ; i!=-1 ; i-- ) | |
| { | |
| float2 pointPos = new float2{ x=Points[i].x , y=Points[i].y }; | |
| float dist = math.lengthsq( pointPos - pixelPos );// dist sq | |
| // float2 vec = math.abs( pointPos - pixelPos ); | |
| // float dist = vec.x + vec.y;// manhattan distance | |
| if( dist<pointDist ) | |
| { | |
| pointDist = dist; | |
| pointIndex = i; | |
| } | |
| if( dist<math.max(math.min(TextureWidth*0.01f,TextureHeight*0.01f),1) ) | |
| { | |
| TextureOutput[jobIndex] = new RGBA32{ r=0 , g=0 , b=0 , a=255 }; | |
| return; | |
| } | |
| } | |
| RGBA32 colorFromCoord; | |
| { | |
| ushort2 pointCoord = Points[pointIndex]; | |
| float2 f = new float2{ x=pointCoord.x , y=pointCoord.y } / ( new float2{ x=TextureWidth , y=TextureHeight } - new float2{ x=1 , y=1 } ); | |
| byte r = (byte)( 255*f.x ); | |
| byte g = (byte)( 255*f.y ); | |
| byte b = (byte)( (r+g)%255 ); | |
| colorFromCoord = new RGBA32{ r=r , g=g , b=b , a=255 }; | |
| } | |
| TextureOutput[jobIndex] = colorFromCoord; | |
| } | |
| } | |
| struct ushort2 { public ushort x, y; } | |
| struct RGBA32 { public byte r, g, b, a; } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

