Skip to content

Instantly share code, notes, and snippets.

@unitycoder
Last active January 31, 2024 21:27
Show Gist options
  • Save unitycoder/6527bb08fe588d7589803c8f7c395115 to your computer and use it in GitHub Desktop.
Save unitycoder/6527bb08fe588d7589803c8f7c395115 to your computer and use it in GitHub Desktop.
GPU-based 2D Voronoi diagram generation, technique Cone Projection (unity)
// 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)));
}
// 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();
    }
}
// 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
        }
    }
}
// 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
// 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
        }
    }
}
// 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();
    }
}
// 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();
    }
}
// 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