public class GPUNeighbourSearch : MonoBehaviour
public int particleCount = 100_000;
public float domainSize = 100f;
public float neighbourRadius = 1.0f;
public int binsX = 256; // must cover domainSize
public ComputeShader shader;
ComputeBuffer particles, binA, binB, binOffsets,
sortedIdx, particlesSorted, forces;
int kClear, kCount, kScan, kScatter, kNeighbours;
// here: run whatever boid integration kernel you like,
// then swap particles ↔ particlesSorted, render, etc.
int binCount = binsX * binsY;
particles = new ComputeBuffer(particleCount, sizeof(float) * 4);
binA = new ComputeBuffer(binCount + 1, sizeof(uint));
binB = new ComputeBuffer(binCount + 1, sizeof(uint));
binOffsets = new ComputeBuffer(binCount + 1, sizeof(uint));
sortedIdx = new ComputeBuffer(particleCount, sizeof(uint));
particlesSorted = new ComputeBuffer(particleCount, sizeof(float) * 4);
forces = new ComputeBuffer(particleCount, sizeof(float) * 4);
// fill particles with random positions inside domain
Vector4[] init = new Vector4[particleCount];
for (int i = 0; i < particleCount; ++i)
Random.value * domainSize,
Random.value * domainSize,
kClear = shader.FindKernel("ClearBins");
kCount = shader.FindKernel("CountBins");
kScan = shader.FindKernel("PrefixSum");
kScatter = shader.FindKernel("Scatter");
kNeighbours = shader.FindKernel("FindNeighbours");
// constant bindings shared by all passes
shader.SetInt("_ParticleCount", particleCount);
shader.SetInts("_GridSize", binsX, binsY);
shader.SetFloat("_BinSize", domainSize / binsX);
shader.SetFloats("_DomainMin", 0, 0);
shader.SetFloat("_NeighbourRadius", neighbourRadius);
// buffers (same layout for every kernel)
BindBuffers(kClear); BindBuffers(kCount);
BindBuffers(kScan); BindBuffers(kScatter);
BindBuffers(kNeighbours);
void BindBuffers (int kernel)
shader.SetBuffer(kernel, "_Particles", particles);
shader.SetBuffer(kernel, "_BinCountsA", binA);
shader.SetBuffer(kernel, "_BinCountsB", binB);
shader.SetBuffer(kernel, "_BinOffsets", binOffsets);
shader.SetBuffer(kernel, "_SortedIndices", sortedIdx);
shader.SetBuffer(kernel, "_ParticlesSorted", particlesSorted);
shader.SetBuffer(kernel, "_Forces", forces);
int binCount = binsX * binsY;
int groupsBins = Mathf.CeilToInt((binCount + 1) / 256f);
int groupsParticles = Mathf.CeilToInt(particleCount / 256f);
shader.Dispatch(kClear, groupsBins, 1, 1);
shader.Dispatch(kCount, groupsParticles, 1, 1);
// Phase 2 – prefix sum (ping‑pong A/B)
int iterations = Mathf.CeilToInt(Mathf.Log(binCount + 1, 2));
for (int i = 0, step = 1; i < iterations; ++i, step <<= 1)
shader.SetInt("_Step", step);
shader.Dispatch(kScan, groupsBins, 1, 1);
(binA, binB) = (binB, binA);
shader.SetBuffer(kScan, "_BinCountsA", binA);
shader.SetBuffer(kScan, "_BinCountsB", binB);
shader.SetBuffer(kScatter, "_BinCountsA", binA); // keep Scatter in sync
// the last buffer with data is binA
ComputeBuffer.CopyCount(binA, binOffsets, 0); // copy to offsets buffer
shader.Dispatch(kScatter, groupsParticles, 1, 1);
shader.Dispatch(kNeighbours, groupsParticles, 1, 1);
particles?.Release(); binA?.Release(); binB?.Release();
binOffsets?.Release(); sortedIdx?.Release();
particlesSorted?.Release();forces?.Release();