Last active
January 31, 2024 21:27
-
-
Save unitycoder/6527bb08fe588d7589803c8f7c395115 to your computer and use it in GitHub Desktop.
GPU-based 2D Voronoi diagram generation, technique Cone Projection (unity)
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
#pragma kernel ClearVoxelsKernel | |
#pragma kernel BuildVoxelsKernel | |
#pragma kernel JumpFloodKernel | |
struct Seed | |
{ | |
float3 Location; | |
float3 Color; | |
}; | |
Texture3D<float4> _Texture3D; | |
RWTexture3D<float4> _RWTexture3D; | |
RWStructuredBuffer<float3> _Voxels; | |
RWStructuredBuffer<Seed> _Seeds; | |
uint _Frame, _Resolution, _Animation; | |
float _MaxSteps, _Time; | |
float RGBAToFloat( float4 rgba ) | |
{ | |
uint r = (uint)(rgba.x * 255); | |
uint g = (uint)(rgba.y * 255); | |
uint b = (uint)(rgba.z * 255); | |
uint a = (uint)(rgba.w * 255); | |
uint q = (r << 24) + (g << 16) + (b << 8) + a; | |
return q / (256.0 * 256.0 * 256.0 * 256.0); | |
} | |
float4 FloatToRGBA( float f ) | |
{ | |
uint q = (uint)(f * 256.0 * 256.0 * 256.0 * 256.0); | |
uint r = (uint)(q / (256 * 256 * 256) % 256); | |
uint g = (uint)((q / (256 * 256)) % 256); | |
uint b = (uint)((q / (256)) % 256); | |
uint a = (uint)(q % 256); | |
return float4(r / 255.0, g / 255.0, b / 255.0, a / 255.0); | |
} | |
float4 JFA3D (float3 fragCoord, float level) | |
{ | |
float range = clamp(level - 1.0, 0.0, _MaxSteps); | |
float stepwidth = floor(exp2(_MaxSteps - range) + 0.5); | |
float bestDistance = 9999.0; | |
float3 bestCoord = float3(0.0, 0.0, 0.0); | |
float3 bestColor = float3(0.0, 0.0, 0.0); | |
for (int z = -1; z <= 1; ++z) | |
{ | |
for (int y = -1; y <= 1; ++y) | |
{ | |
for (int x = -1; x <= 1; ++x) | |
{ | |
float3 neighbour = fragCoord + float3(x,y,z) * stepwidth; | |
float4 source = _Texture3D.Load(int4(neighbour, 0)); | |
float3 seedCoord = source.xyz; | |
float3 seedColor = FloatToRGBA( source.w ).xyz; | |
float magnitude = length(seedCoord - fragCoord); | |
if ((seedCoord.x != 0.0 || seedCoord.y != 0.0 || seedCoord.z != 0.0) && magnitude < bestDistance) | |
{ | |
bestDistance = magnitude; | |
bestCoord = seedCoord; | |
bestColor = seedColor; | |
} | |
} | |
} | |
} | |
return float4(bestCoord, RGBAToFloat(float4(bestColor, 1.0))); | |
} | |
[numthreads(8,8,8)] | |
void ClearVoxelsKernel (uint3 id : SV_DispatchThreadID) | |
{ | |
uint instance = id.x * _Resolution * _Resolution + id.y * _Resolution + id.z; | |
_Voxels[instance] = float3(-1.0, -1.0, -1.0); | |
} | |
[numthreads(8,1,1)] | |
void BuildVoxelsKernel (uint3 id : SV_DispatchThreadID) | |
{ | |
float factor = pow(_Resolution / 128.0, 4.0); | |
float angle = _Time * 3.0 + id.x; | |
_Seeds[id.x].Location += _Animation * float3(sin(angle), cos(angle), cos(1.0 - angle)) * factor; | |
_Seeds[id.x].Location = clamp(_Seeds[id.x].Location, (float3)0.0, (float3)(_Resolution - 1)); | |
int3 location = int3(_Seeds[id.x].Location); | |
int instance = location.x * _Resolution * _Resolution + location.y * _Resolution + location.z; | |
_Voxels[instance] = _Seeds[id.x].Color; | |
} | |
[numthreads(8,8,8)] | |
void JumpFloodKernel (uint3 id : SV_DispatchThreadID) | |
{ | |
float3 fragCoord = float3(id.x, id.y, id.z); | |
if (_Frame == 0u) | |
{ | |
uint instance = id.x * _Resolution * _Resolution + id.y * _Resolution + id.z; | |
float3 buffer = _Voxels[instance]; | |
_RWTexture3D[id] = (buffer.x < 0.0) ? float4(0,0,0,1) : float4(fragCoord, RGBAToFloat(float4(buffer, 1.0))); | |
return; | |
} | |
_RWTexture3D[id] = JFA3D(fragCoord, floor(float(_Frame))); | |
} |
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
using UnityEngine; | |
using UnityEngine.Rendering; | |
using System.Runtime.InteropServices; | |
public class JumpFlooding3D : MonoBehaviour | |
{ | |
[SerializeField] ComputeShader _ComputeShader; | |
[SerializeField] Shader _PixelShader; | |
[SerializeField] int _Resolution = 128; | |
[SerializeField] bool _Animation = true; | |
[SerializeField] [Range(1, 100000)] int _SeedCount = 2048; | |
[SerializeField] [Range(-0.5f, 0.5f)] float _SliceXMin = -0.5f, _SliceXMax = 0.5f; | |
[SerializeField] [Range(-0.5f, 0.5f)] float _SliceYMin = -0.5f, _SliceYMax = 0.5f; | |
[SerializeField] [Range(-0.5f, 0.5f)] float _SliceZMin = -0.5f, _SliceZMax = 0.5f; | |
[SerializeField] [Range( 0.0f, 1.0f)] float _Alpha = 1.0f; | |
ComputeBuffer _Seeds, _Voxels; | |
Material _Material; | |
RenderTexture[] _RenderTextures = new RenderTexture[2]; | |
int _CVID, _BVID, _JFID; | |
bool _Swap = true; | |
struct Seed | |
{ | |
public Vector3 Location; | |
public Vector3 Color; | |
}; | |
void Start() | |
{ | |
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); | |
cube.transform.position = Vector3.zero; | |
_Material = new Material(_PixelShader); | |
cube.GetComponent<Renderer>().sharedMaterial = _Material; | |
RenderTextureDescriptor descriptor = new RenderTextureDescriptor(_Resolution, _Resolution, RenderTextureFormat.ARGBFloat); | |
descriptor.dimension = TextureDimension.Tex3D; | |
descriptor.volumeDepth = _Resolution; | |
for (int i = 0; i < 2; i++) | |
{ | |
_RenderTextures[i] = new RenderTexture(descriptor); | |
_RenderTextures[i].enableRandomWrite = true; | |
_RenderTextures[i].Create(); | |
_RenderTextures[i].filterMode = FilterMode.Point; | |
} | |
_Voxels = new ComputeBuffer(_Resolution * _Resolution * _Resolution, sizeof(float) * 3, ComputeBufferType.Default); | |
Seed[] seeds = new Seed[_SeedCount]; | |
for (int i = 0; i < seeds.Length; i++) | |
{ | |
int x = Random.Range(0, _Resolution); | |
int y = Random.Range(0, _Resolution); | |
int z = Random.Range(0, _Resolution); | |
float r = Random.Range(0f, 1f); | |
float g = Random.Range(0f, 1f); | |
float b = Random.Range(0f, 1f); | |
seeds[i] = new Seed{Location = new Vector3(x, y, z), Color = new Vector3(r, g, b)}; | |
} | |
_Seeds = new ComputeBuffer(seeds.Length, Marshal.SizeOf(typeof(Seed)), ComputeBufferType.Default); | |
_Seeds.SetData(seeds); | |
_CVID = _ComputeShader.FindKernel("ClearVoxelsKernel"); | |
_BVID = _ComputeShader.FindKernel("BuildVoxelsKernel"); | |
_JFID = _ComputeShader.FindKernel("JumpFloodKernel"); | |
} | |
void Update() | |
{ | |
_Material.SetVector("_SliceMin", new Vector3(_SliceXMin, _SliceYMin, _SliceZMin)); | |
_Material.SetVector("_SliceMax", new Vector3(_SliceXMax, _SliceYMax, _SliceZMax)); | |
_Material.SetFloat("_Alpha", _Alpha); | |
_ComputeShader.SetInt("_Resolution", _Resolution); | |
_ComputeShader.SetInt("_Animation", System.Convert.ToInt32(_Animation)); | |
_ComputeShader.SetFloat("_MaxSteps", Mathf.Log((float)_Resolution, 2.0f)); | |
_ComputeShader.SetFloat("_Time", Time.time); | |
_ComputeShader.SetBuffer(_CVID, "_Voxels", _Voxels); | |
_ComputeShader.Dispatch(_CVID, _Resolution / 8, _Resolution / 8, _Resolution / 8); | |
_ComputeShader.SetBuffer(_BVID, "_Seeds", _Seeds); | |
_ComputeShader.SetBuffer(_BVID, "_Voxels", _Voxels); | |
_ComputeShader.Dispatch(_BVID, (_Seeds.count + 8) / 8, 1, 1); | |
int frameCount = 0; | |
for (int i = 0; i < _Resolution; i++) | |
{ | |
_ComputeShader.SetInt("_Frame", frameCount); | |
int r = System.Convert.ToInt32(!_Swap); | |
int w = System.Convert.ToInt32(_Swap); | |
_ComputeShader.SetTexture(_JFID, "_Texture3D", _RenderTextures[r]); | |
_ComputeShader.SetTexture(_JFID, "_RWTexture3D", _RenderTextures[w]); | |
_ComputeShader.SetBuffer(_JFID, "_Voxels", _Voxels); | |
_ComputeShader.Dispatch(_JFID, _Resolution / 8, _Resolution / 8, _Resolution / 8); | |
_Material.SetTexture("_Volume", _RenderTextures[w]); | |
_Swap = !_Swap; | |
frameCount++; | |
} | |
} | |
void OnDestroy() | |
{ | |
Destroy(_Material); | |
_Seeds.Release(); | |
_Voxels.Release(); | |
for (int i = 0; i < 2; i++) _RenderTextures[i].Release(); | |
} | |
} |
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
Shader "JumpFlooding3D" | |
{ | |
SubShader | |
{ | |
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" } | |
Blend One OneMinusSrcAlpha | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex VSMain | |
#pragma fragment PSMain | |
sampler3D _Volume; | |
float _Alpha; | |
float3 _SliceMin, _SliceMax; | |
float4 FloatToRGBA( float f ) | |
{ | |
uint q = (uint)(f * 256.0 * 256.0 * 256.0 * 256.0); | |
uint r = (uint)(q / (256 * 256 * 256) % 256); | |
uint g = (uint)((q / (256 * 256)) % 256); | |
uint b = (uint)((q / (256)) % 256); | |
uint a = (uint)(q % 256); | |
return float4(r / 255.0, g / 255.0, b / 255.0, a / 255.0); | |
} | |
float4 VSMain (float4 vertex : POSITION, out float4 localPos : LOCALPOS, out float3 direction : DIRECTION) : SV_POSITION | |
{ | |
localPos = vertex; | |
direction = mul(unity_ObjectToWorld, vertex).xyz - _WorldSpaceCameraPos; | |
return UnityObjectToClipPos(vertex); | |
} | |
float4 PSMain (float4 vertex : SV_POSITION, float4 localPos : LOCALPOS, float3 direction : DIRECTION) : SV_Target | |
{ | |
float3 ro = localPos; | |
float3 rd = mul(unity_WorldToObject, float4(normalize(direction), 1)); | |
float4 result = float4(0, 0, 0, 0); | |
int steps = 256; | |
float t = 2.0 / float(steps); | |
for (int i = 0; i < steps; i++) | |
{ | |
if(max(abs(ro.x), max(abs(ro.y), abs(ro.z))) < 0.500001f) | |
{ | |
float4 voxel = tex3D(_Volume, ro + float3(0.5f, 0.5f, 0.5f)); | |
float4 color = float4(FloatToRGBA( voxel.w ).rgb, 1.0); | |
color.a *= _Alpha; | |
bool blend = all(ro > _SliceMin) && all(ro < _SliceMax); | |
result.rgb += blend ? (1.0 - result.a) * color.a * color.rgb : 0..xxx; | |
result.a += blend ? (1.0 - result.a) * color.a : 0.0; | |
ro += rd * t; | |
} | |
} | |
return result; | |
} | |
ENDCG | |
} | |
} | |
} |
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
using UnityEngine; | |
using System.Collections.Generic; | |
using System.Runtime.InteropServices; | |
#if UNITY_EDITOR | |
using UnityEditor; | |
#endif | |
public class VoronoiCones : MonoBehaviour | |
{ | |
[Header("Cone Settings")] | |
[SerializeField] Shader _Shader; | |
[SerializeField] int _Resolution = 4096; | |
[SerializeField] bool _Animation = true; | |
[SerializeField] [Range(8, 128)] int _ConeSides = 64; | |
[SerializeField] [Range(0f, 10f)] float _ConeRadius = 1f; | |
[SerializeField] [Range(0f, 12f)] float _ConeHeight = 10f; | |
[Header("Seed Settings")] | |
[SerializeField] [Range(1f, 1048576f)] int _SeedCount = 2048; | |
[SerializeField] [Range(0f, 1f)] float _SeedSize = 0.2f; | |
Material _Material; | |
ComputeBuffer _Cone, _Seeds; | |
Matrix4x4 _ModelViewProjection; | |
RenderTexture _RenderTexture; | |
GameObject _Plane; | |
struct Seed | |
{ | |
public Vector2 Location; | |
public Vector3 Color; | |
}; | |
List<Vector3> GenerateCone(int sides, float radius, float height) | |
{ | |
List<Vector3> vertices = new List<Vector3>(); | |
List<Vector2> circle = new List<Vector2>(); | |
float radians = 0.01745329251f; | |
float step = 360f / (float) sides * radians; | |
for (int i = 0; i <= sides; i++) | |
{ | |
float x = radius * Mathf.Cos(i * step); | |
float y = radius * Mathf.Sin(i * step); | |
circle.Add(new Vector2(x, y)); | |
} | |
for (int i = 0; i < sides; i++) | |
{ | |
vertices.Add(new Vector3(0f, 0f, -height)); | |
vertices.Add(new Vector3(circle[i + 1].x, circle[i + 1].y, 0f)); | |
vertices.Add(new Vector3(circle[i + 0].x, circle[i + 0].y, 0f)); | |
} | |
return vertices; | |
} | |
void CreateCones() | |
{ | |
if (_Shader == null) _Shader = Shader.Find("Hidden/VoronoiCones"); | |
_Material = new Material(_Shader); | |
_RenderTexture = new RenderTexture(_Resolution, _Resolution, 16, RenderTextureFormat.ARGB32); | |
_RenderTexture.Create(); | |
List<Vector3> vertices = GenerateCone(_ConeSides, _ConeRadius, _ConeHeight); | |
_Cone = new ComputeBuffer(vertices.Count, Marshal.SizeOf(typeof(Vector3)), ComputeBufferType.Default); | |
_Cone.SetData(vertices); | |
_ModelViewProjection.SetRow(0, new Vector4(0.2f, 0.0f, 0.0f, 0.0f)); //orto size = 5, near = -15 and far = 0 | |
_ModelViewProjection.SetRow(1, new Vector4(0.0f, -0.2f, 0.0f, 0.0f)); | |
_ModelViewProjection.SetRow(2, new Vector4(0.0f, 0.0f, -0.0666667f, 0.0f)); | |
_ModelViewProjection.SetRow(3, new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); | |
_Plane.GetComponent<Renderer>().sharedMaterial.mainTexture = _RenderTexture; | |
} | |
void CreateSeeds() | |
{ | |
Seed[] seeds = new Seed[_SeedCount]; | |
for (int i = 0; i < seeds.Length; i++) | |
{ | |
float x = UnityEngine.Random.Range(-5f, 5f); | |
float y = UnityEngine.Random.Range(-5f, 5f); | |
float r = UnityEngine.Random.Range( 0f, 1f); | |
float g = UnityEngine.Random.Range( 0f, 1f); | |
float b = UnityEngine.Random.Range( 0f, 1f); | |
seeds[i] = new Seed{Location = new Vector2(x, y), Color = new Vector3(r, g, b)}; | |
} | |
_Seeds = new ComputeBuffer(seeds.Length, Marshal.SizeOf(typeof(Seed)), ComputeBufferType.Default); | |
_Seeds.SetData(seeds); | |
} | |
void DeleteCones() | |
{ | |
if (_Cone != null) _Cone.Release(); | |
if (_Material != null) Destroy(_Material); | |
if (_RenderTexture != null) _RenderTexture.Release(); | |
} | |
void DeleteSeeds() | |
{ | |
if (_Seeds != null) _Seeds.Release(); | |
} | |
public void ApplyCones() | |
{ | |
DeleteCones(); | |
CreateCones(); | |
} | |
public void ApplySeeds() | |
{ | |
DeleteSeeds(); | |
CreateSeeds(); | |
} | |
void Start() | |
{ | |
_Plane = GameObject.CreatePrimitive(PrimitiveType.Plane); | |
CreateCones(); | |
CreateSeeds(); | |
} | |
void OnRenderObject() | |
{ | |
RenderTexture current = RenderTexture.active; | |
RenderTexture.active = _RenderTexture; | |
_Material.SetPass(0); | |
_Material.SetBuffer("_Cone", _Cone); | |
_Material.SetBuffer("_Seeds", _Seeds); | |
_Material.SetMatrix("_ModelViewProjection", _ModelViewProjection); | |
_Material.SetInt("_Animation", System.Convert.ToInt32(_Animation)); | |
_Material.SetFloat("_SeedSize", _SeedSize); | |
_Material.SetFloat("_ConeHeight", _ConeHeight); | |
GL.Clear(true, true, Color.clear); | |
Graphics.DrawProceduralNow(MeshTopology.Triangles, _Cone.count, _Seeds.count); | |
RenderTexture.active = current; | |
} | |
void OnDestroy() | |
{ | |
DeleteCones(); | |
DeleteSeeds(); | |
} | |
} | |
#if UNITY_EDITOR | |
[CustomEditor(typeof(VoronoiCones))] | |
public class VoronoiConesEditor : Editor | |
{ | |
public override void OnInspectorGUI() | |
{ | |
DrawDefaultInspector(); | |
VoronoiCones vc = (VoronoiCones)target; | |
if(GUILayout.Button("Apply Cone Settings")) vc.ApplyCones(); | |
if(GUILayout.Button("Apply Seed Settings")) vc.ApplySeeds(); | |
} | |
} | |
#endif |
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
Shader "Hidden/VoronoiCones" | |
{ | |
Subshader | |
{ | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex VSMain | |
#pragma fragment PSMain | |
#pragma target 5.0 | |
struct Seed | |
{ | |
float2 Location; | |
float3 Color; | |
}; | |
StructuredBuffer<float3> _Cone; | |
StructuredBuffer<Seed> _Seeds; | |
float4x4 _ModelViewProjection; | |
int _Animation; | |
float _SeedSize, _ConeHeight; | |
float4 VSMain (uint id : SV_VertexID, uint instance : SV_InstanceID, out float3 color : COLOR, out float4 worldPos : WORLDPOS) : SV_POSITION | |
{ | |
worldPos = float4(_Cone[id] + float3(_Seeds[instance].Location, 0.0), 1.0); | |
worldPos.xy += _Animation * float2(sin(_Time.g + instance), cos(_Time.g - instance)) * 0.5; | |
color = _Seeds[instance].Color; | |
return mul(_ModelViewProjection, worldPos); | |
} | |
float4 PSMain (float4 vertex : SV_POSITION, float3 color : COLOR, float4 worldPos : WORLDPOS) : SV_Target | |
{ | |
return worldPos.z <= (-_ConeHeight + _SeedSize) ? float4(0, 0, 0, 1) : float4(color, 1); | |
} | |
ENDCG | |
} | |
} | |
} |
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
#pragma kernel VoronoiKernel | |
#pragma kernel DelaunayKernel | |
struct Seed | |
{ | |
float2 Location; | |
float3 Color; | |
}; | |
struct Triangle | |
{ | |
float2 A; | |
float2 B; | |
float2 C; | |
}; | |
Texture2D<float4> _Texture2D; | |
RWTexture2D<float4> _RWTexture2D; | |
StructuredBuffer<Seed> _Seeds; | |
AppendStructuredBuffer<Triangle> _Triangles; | |
RWStructuredBuffer<uint> _CounterBuffer; | |
uint _SeedsCount, _Resolution; | |
float RGBAToFloat( float4 rgba ) | |
{ | |
uint r = (uint)(rgba.x * 255.0); | |
uint g = (uint)(rgba.y * 255.0); | |
uint b = (uint)(rgba.z * 255.0); | |
uint a = (uint)(rgba.w * 255.0); | |
uint q = (r << 24) + (g << 16) + (b << 8) + a; | |
return float(q) / 4294967296.0; | |
} | |
float4 FloatToRGBA( float f ) | |
{ | |
uint q = (uint)(f * 4294967296.0); | |
uint r = (uint)((q / 16777216u) % 256u); | |
uint g = (uint)((q / 65536u) % 256u); | |
uint b = (uint)((q / 256u) % 256u); | |
uint a = (uint)(q % 256u); | |
return float4(r, g, b, a) / 255.0; | |
} | |
float Circle (float2 p, float2 c, float r) | |
{ | |
return step(length(p - c) - r, 0.0); | |
} | |
[numthreads(8,8,1)] | |
void VoronoiKernel (uint3 id : SV_DispatchThreadID) | |
{ | |
float2 fragCoord = float2(id.x, id.y); | |
float4 result = float4(9999.0, 0.0, 0.0, 0.0); | |
uint index = 0; | |
for (uint i = 0; i < _SeedsCount; i++) | |
{ | |
float3 seed = float3(_Seeds[i].Location, RGBAToFloat(float4(_Seeds[i].Color, 1.0))); | |
float magnitude = distance(fragCoord.xy, seed.xy); | |
if (magnitude < result.x) | |
{ | |
result = float4(magnitude, seed); | |
index = i; | |
} | |
} | |
float3 circle = Circle(fragCoord, result.yz, 1.0).xxx; | |
_RWTexture2D[id.xy] = float4(FloatToRGBA(result.w).rgb - circle, float(index)); | |
} | |
[numthreads(8,8,1)] | |
void DelaunayKernel (uint3 id : SV_DispatchThreadID) | |
{ | |
float2 fragCoord = float2(id.x, id.y); | |
float4 source = _Texture2D.Load(int3(fragCoord, 0)); | |
float4 neighbours[9]; | |
int cells[3] = {int(floor(source.a)), 0, 0}; | |
int count = 1; | |
int index = 0; | |
float2 border = float2(0.0, _Resolution - 1u); | |
for (int y = -1; y <= 1; y++) // get all neighbour pixels | |
{ | |
for (int x = -1; x <= 1; x++) | |
{ | |
float2 coords = fragCoord + float2(x, y); | |
bool off = (coords.x < border.x || coords.x > border.y || coords.y < border.x || coords.y > border.y); | |
neighbours[index] = off ? source : _Texture2D.Load(int3(coords, 0)); | |
index++; | |
} | |
} | |
for (int i = 1; i < 9; i++) // count distinct pixels in an array | |
{ | |
int j = 0; | |
for (j = 0; j < i; j++) | |
{ | |
if (all(abs(neighbours[i].rgb - neighbours[j].rgb) < 0.001)) | |
break; | |
} | |
if (i == j) | |
{ | |
cells[count] = int(floor(neighbours[i].a)); | |
count += 1; | |
} | |
} | |
if (count == 3) // if we found a contact point between three Voronoi cells, we can generate new triangle | |
{ | |
Triangle polygon; | |
polygon.A = _Seeds[cells[0]].Location; | |
polygon.B = _Seeds[cells[1]].Location; | |
polygon.C = _Seeds[cells[2]].Location; | |
_Triangles.Append(polygon); | |
_CounterBuffer.IncrementCounter(); | |
_CounterBuffer.IncrementCounter(); | |
_CounterBuffer.IncrementCounter(); | |
} | |
} |
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
using System.Runtime.InteropServices; | |
public class VoronoiDualGraph : MonoBehaviour | |
{ | |
[SerializeField] ComputeShader _ComputeShader; | |
[SerializeField] Shader _VertexPixelShader; | |
[SerializeField] int _SeedCount = 2048; | |
[SerializeField] int _Resolution = 1024; | |
[SerializeField] bool _Animation = true; | |
ComputeBuffer _Seeds, _Triangles, _IndirectBuffer, _CounterBuffer; | |
Material _Material; | |
RenderTexture _RenderTexture; | |
int _VK, _DK; | |
Seed[] _SeedArray; | |
struct Seed | |
{ | |
public Vector2 Location; | |
public Vector3 Color; | |
}; | |
struct Triangle | |
{ | |
public Vector2 A; | |
public Vector2 B; | |
public Vector2 C; | |
} | |
void Start() | |
{ | |
_Material = new Material(_VertexPixelShader); | |
_RenderTexture = new RenderTexture(_Resolution, _Resolution, 0, RenderTextureFormat.ARGBFloat); | |
_RenderTexture.enableRandomWrite = true; | |
_RenderTexture.Create(); | |
_RenderTexture.filterMode = FilterMode.Point; | |
_RenderTexture.wrapMode = TextureWrapMode.Clamp; | |
_SeedArray = new Seed[_SeedCount]; | |
for (int i = 0; i < _SeedArray.Length; i++) | |
{ | |
float x = UnityEngine.Random.Range(16f, _Resolution - 16); | |
float y = UnityEngine.Random.Range(16f, _Resolution - 16); | |
float r = UnityEngine.Random.Range(0.1f, 0.9f); | |
float g = UnityEngine.Random.Range(0.1f, 0.9f); | |
float b = UnityEngine.Random.Range(0.1f, 0.9f); | |
_SeedArray[i] = new Seed{Location = new Vector2(x, y), Color = new Vector3(r, g, b)}; | |
} | |
_Seeds = new ComputeBuffer(_SeedArray.Length, Marshal.SizeOf(typeof(Seed)), ComputeBufferType.Default); | |
_Seeds.SetData(_SeedArray); | |
_Triangles = new ComputeBuffer(_SeedArray.Length * 16, Marshal.SizeOf(typeof(Triangle)), ComputeBufferType.Append); | |
_IndirectBuffer = new ComputeBuffer (4, sizeof(int), ComputeBufferType.IndirectArguments); | |
_IndirectBuffer.SetData(new int[] { 0, 1, 0, 0 }); | |
_CounterBuffer = new ComputeBuffer(1, 4, ComputeBufferType.Counter); | |
GameObject plane = GameObject.CreatePrimitive(PrimitiveType.Plane); | |
plane.transform.localScale = new Vector3(_Resolution / 10f, _Resolution / 10f, _Resolution / 10f); | |
plane.transform.position = new Vector3(_Resolution / 2f, 0f, _Resolution / 2f); | |
plane.transform.eulerAngles = new Vector3(0, 180f, 0f); | |
plane.GetComponent<Renderer>().sharedMaterial = new Material(Shader.Find("Legacy Shaders/Diffuse")); | |
plane.GetComponent<Renderer>().sharedMaterial.mainTexture = _RenderTexture; | |
_VK = _ComputeShader.FindKernel("VoronoiKernel"); | |
_DK = _ComputeShader.FindKernel("DelaunayKernel"); | |
} | |
void OnRenderObject() | |
{ | |
if (_Animation) | |
{ | |
for (int i = 0; i < _SeedArray.Length; i++) | |
{ | |
_SeedArray[i].Location += new Vector2(Mathf.Cos(Time.time + i + 2), Mathf.Sin(Time.time + i + 2)) * 0.08f; | |
} | |
_Seeds.SetData(_SeedArray); | |
} | |
_ComputeShader.SetInt("_SeedsCount", _Seeds.count); | |
_ComputeShader.SetInt("_Resolution", _Resolution); | |
_ComputeShader.SetTexture(_VK,"_RWTexture2D", _RenderTexture); | |
_ComputeShader.SetBuffer(_VK, "_Seeds", _Seeds); | |
_ComputeShader.Dispatch(_VK, _Resolution / 8, _Resolution / 8, 1); | |
_Triangles.SetCounterValue(0); | |
_CounterBuffer.SetCounterValue(0); | |
_ComputeShader.SetTexture(_DK,"_Texture2D", _RenderTexture); | |
_ComputeShader.SetBuffer(_DK, "_Seeds", _Seeds); | |
_ComputeShader.SetBuffer(_DK, "_Triangles", _Triangles); | |
_ComputeShader.SetBuffer(_DK, "_CounterBuffer", _CounterBuffer); | |
_ComputeShader.Dispatch(_DK, _Resolution / 8, _Resolution / 8, 1); | |
int[] args = new int[] { 0, 1, 0, 0 }; | |
_IndirectBuffer.SetData(args); | |
ComputeBuffer.CopyCount(_CounterBuffer, _IndirectBuffer, 0); | |
_Material.SetPass(0); | |
_Material.SetBuffer("_TriangleBuffer", _Triangles); | |
Graphics.DrawProceduralIndirectNow(MeshTopology.Triangles, _IndirectBuffer); | |
} | |
void OnDestroy() | |
{ | |
if (_Material != null) Destroy(_Material); | |
if (_RenderTexture != null) _RenderTexture.Release(); | |
if (_Seeds != null) _Seeds.Release(); | |
if (_Triangles != null) _Triangles.Release(); | |
if (_IndirectBuffer != null) _IndirectBuffer.Release(); | |
if (_CounterBuffer != null) _CounterBuffer.Release(); | |
} | |
} |
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
// https://forum.unity.com/threads/programming-tools-constrained-delaunay-triangulation.1066148/#post-9181676 | |
Shader "Hidden/VoronoiDualGraph" | |
{ | |
SubShader | |
{ | |
Cull Off | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex VSMain | |
#pragma fragment PSMain | |
#pragma target 5.0 | |
struct Triangle | |
{ | |
float2 Vertices[3]; | |
}; | |
uniform StructuredBuffer<Triangle> _TriangleBuffer; | |
float4 VSMain (float4 vertex : POSITION, uint id : SV_VertexID, out float2 barycentric : BARYCENTRIC) : SV_Position | |
{ | |
uint index = id % 3u; | |
float3 worldPos = float3(_TriangleBuffer[id / 3u].Vertices[index], 0.02); | |
barycentric = float2(fmod(index, 2.0), step(2.0, index)); | |
return UnityObjectToClipPos(float4(worldPos.xzy, 1.0)); | |
} | |
float4 PSMain (float4 vertex : SV_POSITION, float2 barycentric : BARYCENTRIC) : SV_Target | |
{ | |
float3 coords = float3(barycentric, 1.0 - barycentric.x - barycentric.y); | |
float3 df = fwidth(coords); | |
float3 wireframe = smoothstep(df * 0.1, df * 0.1 + df, coords); | |
if ((1.0 - min(wireframe.x, min(wireframe.y, wireframe.z))) < 0.01) discard; | |
return float4(0.0, 0.0, 0.0, 1.0); | |
} | |
ENDCG | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment